修复
This commit is contained in:
parent
c810e645dc
commit
53dbacef91
@ -6,9 +6,13 @@
|
|||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="28" />
|
android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
@ -130,6 +134,12 @@
|
|||||||
android:name="io.agora.rtc2.extensions.MediaProjectionMgr$LocalScreenSharingService"
|
android:name="io.agora.rtc2.extensions.MediaProjectionMgr$LocalScreenSharingService"
|
||||||
tools:node="remove" />
|
tools:node="remove" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".VoiceRoomForegroundService"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="mediaPlayback|microphone"
|
||||||
|
android:stopWithTask="false" />
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
android:name="com.google.firebase.messaging.default_notification_channel_id"
|
||||||
android:value="high_importance_channel" />
|
android:value="high_importance_channel" />
|
||||||
|
|||||||
@ -30,6 +30,33 @@ class MainActivity : FlutterActivity() {
|
|||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MethodChannel(
|
||||||
|
flutterEngine.dartExecutor.binaryMessenger,
|
||||||
|
VOICE_ROOM_FOREGROUND_CHANNEL
|
||||||
|
).setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"start" -> {
|
||||||
|
try {
|
||||||
|
VoiceRoomForegroundService.start(
|
||||||
|
applicationContext,
|
||||||
|
call.argument<String>("roomName")
|
||||||
|
)
|
||||||
|
result.success(null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("VOICE_ROOM_FOREGROUND_START_FAILED", e.message, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"stop" -> {
|
||||||
|
try {
|
||||||
|
VoiceRoomForegroundService.stop(applicationContext)
|
||||||
|
result.success(null)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
result.error("VOICE_ROOM_FOREGROUND_STOP_FAILED", e.message, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@ -90,5 +117,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val INSTALL_REFERRER_CHANNEL = "com.org.yumiparty/install_referrer"
|
private const val INSTALL_REFERRER_CHANNEL = "com.org.yumiparty/install_referrer"
|
||||||
private const val MOBILE_CONTEXT_CHANNEL = "com.org.yumiparty/mobile_context"
|
private const val MOBILE_CONTEXT_CHANNEL = "com.org.yumiparty/mobile_context"
|
||||||
|
private const val VOICE_ROOM_FOREGROUND_CHANNEL =
|
||||||
|
"com.org.yumiparty/voice_room_foreground_service"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,131 @@
|
|||||||
|
package com.org.yumiparty
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
|
||||||
|
class VoiceRoomForegroundService : Service() {
|
||||||
|
override fun onBind(intent: Intent?): IBinder? = null
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
ensureNotificationChannel()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
val roomName = intent?.getStringExtra(EXTRA_ROOM_NAME)
|
||||||
|
?.trim()
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
?: DEFAULT_ROOM_NAME
|
||||||
|
startAsForeground(roomName)
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startAsForeground(roomName: String) {
|
||||||
|
val notification = buildNotification(roomName)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
val serviceType =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK or
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
||||||
|
} else {
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
|
||||||
|
}
|
||||||
|
startForeground(
|
||||||
|
NOTIFICATION_ID,
|
||||||
|
notification,
|
||||||
|
serviceType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
startForeground(NOTIFICATION_ID, notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
private fun buildNotification(roomName: String): Notification {
|
||||||
|
val launchIntent = (
|
||||||
|
packageManager.getLaunchIntentForPackage(packageName)
|
||||||
|
?: Intent(this, MainActivity::class.java)
|
||||||
|
).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
|
}
|
||||||
|
val pendingIntent = PendingIntent.getActivity(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
launchIntent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT or immutableFlag()
|
||||||
|
)
|
||||||
|
|
||||||
|
val builder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
Notification.Builder(this, CHANNEL_ID)
|
||||||
|
} else {
|
||||||
|
Notification.Builder(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setContentTitle("Yumi voice room")
|
||||||
|
.setContentText(roomName)
|
||||||
|
.setOngoing(true)
|
||||||
|
.setShowWhen(false)
|
||||||
|
.setOnlyAlertOnce(true)
|
||||||
|
.setPriority(Notification.PRIORITY_LOW)
|
||||||
|
.setCategory(Notification.CATEGORY_CALL)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val manager = getSystemService(NotificationManager::class.java)
|
||||||
|
if (manager.getNotificationChannel(CHANNEL_ID) != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val channel = NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
"Voice room",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
).apply {
|
||||||
|
description = "Keeps voice rooms active while Yumi is in the background."
|
||||||
|
setShowBadge(false)
|
||||||
|
}
|
||||||
|
manager.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun immutableFlag(): Int =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CHANNEL_ID = "yumi_voice_room"
|
||||||
|
private const val NOTIFICATION_ID = 24031
|
||||||
|
private const val EXTRA_ROOM_NAME = "room_name"
|
||||||
|
private const val DEFAULT_ROOM_NAME = "Voice room is running"
|
||||||
|
|
||||||
|
fun start(context: Context, roomName: String?) {
|
||||||
|
val intent = Intent(context, VoiceRoomForegroundService::class.java)
|
||||||
|
.putExtra(EXTRA_ROOM_NAME, roomName)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
context.startForegroundService(intent)
|
||||||
|
} else {
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop(context: Context) {
|
||||||
|
context.stopService(Intent(context, VoiceRoomForegroundService::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -502,7 +502,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 10;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
DEVELOPMENT_TEAM = S9X2AJ2US9;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -693,7 +693,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 10;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@ -722,7 +722,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerRelease.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 10;
|
CURRENT_PROJECT_VERSION = 11;
|
||||||
DEVELOPMENT_TEAM = F33K8VUZ62;
|
DEVELOPMENT_TEAM = F33K8VUZ62;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import 'package:yumi/shared/data_sources/sources/repositories/sc_room_repository
|
|||||||
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
|
import 'package:yumi/shared/data_sources/sources/repositories/sc_vip_repository_imp.dart';
|
||||||
import 'package:yumi/services/room/rc_room_manager.dart';
|
import 'package:yumi/services/room/rc_room_manager.dart';
|
||||||
import 'package:yumi/services/audio/rtm_manager.dart';
|
import 'package:yumi/services/audio/rtm_manager.dart';
|
||||||
|
import 'package:yumi/services/audio/voice_room_foreground_service.dart';
|
||||||
import 'package:yumi/services/auth/user_profile_manager.dart';
|
import 'package:yumi/services/auth/user_profile_manager.dart';
|
||||||
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
import 'package:yumi/ui_kit/components/sc_rotating_dots_loading.dart';
|
||||||
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
import 'package:yumi/ui_kit/widgets/room/room_msg_item.dart';
|
||||||
@ -302,6 +303,11 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
'roomStartupSeatInteractionLoading';
|
'roomStartupSeatInteractionLoading';
|
||||||
static const String _selfMicGoUpRetryLoadingTag = 'selfMicGoUpRetryLoading';
|
static const String _selfMicGoUpRetryLoadingTag = 'selfMicGoUpRetryLoading';
|
||||||
static const int _selfMicGoUpFailureLimit = 3;
|
static const int _selfMicGoUpFailureLimit = 3;
|
||||||
|
static const List<Duration> _roomRedPacketEntryRefreshDelays = [
|
||||||
|
Duration.zero,
|
||||||
|
Duration(seconds: 2),
|
||||||
|
Duration(seconds: 6),
|
||||||
|
];
|
||||||
|
|
||||||
bool needUpDataUserInfo = false;
|
bool needUpDataUserInfo = false;
|
||||||
bool _roomVisualEffectsEnabled = false;
|
bool _roomVisualEffectsEnabled = false;
|
||||||
@ -443,6 +449,21 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
bool get shouldShowRoomVisualEffects =>
|
bool get shouldShowRoomVisualEffects =>
|
||||||
currenRoom != null && _roomVisualEffectsEnabled;
|
currenRoom != null && _roomVisualEffectsEnabled;
|
||||||
|
|
||||||
|
void _startVoiceRoomForegroundService() {
|
||||||
|
final roomProfile = currenRoom?.roomProfile?.roomProfile;
|
||||||
|
final roomName = (roomProfile?.roomName ?? "").trim();
|
||||||
|
final roomAccount = (roomProfile?.roomAccount ?? "").trim();
|
||||||
|
unawaited(
|
||||||
|
SCVoiceRoomForegroundService.start(
|
||||||
|
roomName: roomName.isNotEmpty ? roomName : roomAccount,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopVoiceRoomForegroundService() {
|
||||||
|
unawaited(SCVoiceRoomForegroundService.stop());
|
||||||
|
}
|
||||||
|
|
||||||
bool get _shouldDeferSeatInteractionForRoomStartup =>
|
bool get _shouldDeferSeatInteractionForRoomStartup =>
|
||||||
_roomStartupStatus == RoomStartupStatus.loading;
|
_roomStartupStatus == RoomStartupStatus.loading;
|
||||||
|
|
||||||
@ -1943,6 +1964,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Future<void> releaseRtcEngineForAppTermination() async {
|
Future<void> releaseRtcEngineForAppTermination() async {
|
||||||
final rtcEngine = engine;
|
final rtcEngine = engine;
|
||||||
if (rtcEngine == null) {
|
if (rtcEngine == null) {
|
||||||
|
await SCVoiceRoomForegroundService.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1968,6 +1990,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('rtc释放引擎出错: $e');
|
debugPrint('rtc释放引擎出错: $e');
|
||||||
}
|
}
|
||||||
|
await SCVoiceRoomForegroundService.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeAudioVolumeIndicationCallback(
|
void initializeAudioVolumeIndicationCallback(
|
||||||
@ -2114,6 +2137,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_startVoiceRoomForegroundService();
|
||||||
VoiceRoomRoute.openVoiceRoom(context);
|
VoiceRoomRoute.openVoiceRoom(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2137,6 +2161,9 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
_startRoomStatePolling();
|
_startRoomStatePolling();
|
||||||
_setRoomStartupReady();
|
_setRoomStartupReady();
|
||||||
setRoomVisualEffectsEnabled(true);
|
setRoomVisualEffectsEnabled(true);
|
||||||
|
_startRoomRedPacketPresenceHeartbeat(roomId);
|
||||||
|
_refreshRoomRedPacketListAfterEntry(roomId: roomId);
|
||||||
|
_startVoiceRoomForegroundService();
|
||||||
VoiceRoomRoute.openVoiceRoom(context);
|
VoiceRoomRoute.openVoiceRoom(context);
|
||||||
} else {
|
} else {
|
||||||
if (currenRoom != null) {
|
if (currenRoom != null) {
|
||||||
@ -2157,6 +2184,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
previewData: previewData,
|
previewData: previewData,
|
||||||
previewSeatCount: previewSeatCount,
|
previewSeatCount: previewSeatCount,
|
||||||
);
|
);
|
||||||
|
_startVoiceRoomForegroundService();
|
||||||
VoiceRoomRoute.openVoiceRoom(context);
|
VoiceRoomRoute.openVoiceRoom(context);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
final entryRequestSerial = ++_roomEntryRequestSerial;
|
final entryRequestSerial = ++_roomEntryRequestSerial;
|
||||||
@ -2192,6 +2220,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
currenRoom = _mergeVoiceRoomVipHints(enteredRoom, currenRoom);
|
currenRoom = _mergeVoiceRoomVipHints(enteredRoom, currenRoom);
|
||||||
_syncCurrentUserVipHintFromRoom(currenRoom);
|
_syncCurrentUserVipHintFromRoom(currenRoom);
|
||||||
|
_startVoiceRoomForegroundService();
|
||||||
unawaited(_refreshCurrentUserVipHintForRoom(entryRequestSerial));
|
unawaited(_refreshCurrentUserVipHintForRoom(entryRequestSerial));
|
||||||
_currentRoomIsEntryPreview = false;
|
_currentRoomIsEntryPreview = false;
|
||||||
_previewRoomSeatCount = null;
|
_previewRoomSeatCount = null;
|
||||||
@ -2207,6 +2236,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debugPrint('[RoomStartup] entry room failed: $e\n$stackTrace');
|
debugPrint('[RoomStartup] entry room failed: $e\n$stackTrace');
|
||||||
|
_stopVoiceRoomForegroundService();
|
||||||
_setRoomStartupFailed(RoomStartupFailureType.entry);
|
_setRoomStartupFailed(RoomStartupFailureType.entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2674,6 +2704,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
'[RoomStartup] join IM group failed code=${joinResult?.code} desc=${joinResult?.desc}',
|
'[RoomStartup] join IM group failed code=${joinResult?.code} desc=${joinResult?.desc}',
|
||||||
);
|
);
|
||||||
|
_stopVoiceRoomForegroundService();
|
||||||
_setRoomStartupFailed(RoomStartupFailureType.im);
|
_setRoomStartupFailed(RoomStartupFailureType.im);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2693,6 +2724,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
rtmProvider?.loadRoomHistoryMessages(groupId, count: 20) ??
|
rtmProvider?.loadRoomHistoryMessages(groupId, count: 20) ??
|
||||||
Future<void>.value(),
|
Future<void>.value(),
|
||||||
);
|
);
|
||||||
|
_refreshRoomRedPacketListAfterEntry(roomId: roomId);
|
||||||
|
|
||||||
await Future.wait([microphoneListFuture, onlineUsersFuture]);
|
await Future.wait([microphoneListFuture, onlineUsersFuture]);
|
||||||
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
|
if (!_isActiveRoomStartup(entryRequestSerial, roomId)) {
|
||||||
@ -2748,6 +2780,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debugPrint('[RoomStartup] bootstrap failed: $error\n$stackTrace');
|
debugPrint('[RoomStartup] bootstrap failed: $error\n$stackTrace');
|
||||||
|
_stopVoiceRoomForegroundService();
|
||||||
_setRoomStartupFailed(failureType);
|
_setRoomStartupFailed(failureType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2988,11 +3021,17 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
Future<List<SCRoomRedPacketListRes>> loadRoomRedPacketList(
|
Future<List<SCRoomRedPacketListRes>> loadRoomRedPacketList(
|
||||||
int current,
|
int current,
|
||||||
) async {
|
) async {
|
||||||
|
final roomId = currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||||
|
if (roomId.isEmpty) {
|
||||||
|
return const <SCRoomRedPacketListRes>[];
|
||||||
|
}
|
||||||
var result = await SCChatRoomRepository().roomRedPacketList(
|
var result = await SCChatRoomRepository().roomRedPacketList(
|
||||||
currenRoom?.roomProfile?.roomProfile?.id ?? "",
|
roomId,
|
||||||
current,
|
current,
|
||||||
);
|
);
|
||||||
if (current == 1) {
|
final currentRoomId =
|
||||||
|
currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||||
|
if (current == 1 && currentRoomId == roomId) {
|
||||||
redPacketList = result;
|
redPacketList = result;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@ -3055,19 +3094,18 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _refreshRoomRedPacketListAfterEntry() {
|
void _refreshRoomRedPacketListAfterEntry({String? roomId}) {
|
||||||
final roomId = currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
final normalizedRoomId =
|
||||||
if (roomId.isEmpty) {
|
roomId?.trim() ??
|
||||||
|
currenRoom?.roomProfile?.roomProfile?.id?.trim() ??
|
||||||
|
"";
|
||||||
|
if (normalizedRoomId.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (final delay in const [
|
for (final delay in _roomRedPacketEntryRefreshDelays) {
|
||||||
Duration.zero,
|
|
||||||
Duration(seconds: 2),
|
|
||||||
Duration(seconds: 6),
|
|
||||||
]) {
|
|
||||||
unawaited(
|
unawaited(
|
||||||
Future<void>.delayed(delay).then((_) {
|
Future<void>.delayed(delay).then((_) {
|
||||||
return _refreshRoomRedPacketListIfCurrent(roomId);
|
return _refreshRoomRedPacketListIfCurrent(normalizedRoomId);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -3381,6 +3419,7 @@ class RealTimeCommunicationManager extends ChangeNotifier {
|
|||||||
///清空列表数据
|
///清空列表数据
|
||||||
void _clearData() {
|
void _clearData() {
|
||||||
_roomEntryRequestSerial += 1;
|
_roomEntryRequestSerial += 1;
|
||||||
|
_stopVoiceRoomForegroundService();
|
||||||
_roomEntryEffectTimer?.cancel();
|
_roomEntryEffectTimer?.cancel();
|
||||||
_roomEntryEffectTimer = null;
|
_roomEntryEffectTimer = null;
|
||||||
_stopRoomRedPacketPresenceHeartbeat();
|
_stopRoomRedPacketPresenceHeartbeat();
|
||||||
|
|||||||
@ -571,6 +571,7 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
logined = true;
|
logined = true;
|
||||||
print('tim 登录成功');
|
print('tim 登录成功');
|
||||||
await initConversation();
|
await initConversation();
|
||||||
|
unawaited(syncRoomRedPacketBroadcastGroup());
|
||||||
} else {
|
} else {
|
||||||
// 登录失败逻辑
|
// 登录失败逻辑
|
||||||
//print('timm 需要重新登录2');
|
//print('timm 需要重新登录2');
|
||||||
@ -591,16 +592,26 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
|
|
||||||
_onNewMessage(V2TimMessage message) {
|
_onNewMessage(V2TimMessage message) {
|
||||||
if (message.groupID != null) {
|
if (message.groupID != null) {
|
||||||
|
final groupId = message.groupID!;
|
||||||
|
|
||||||
///全服通知 / 语区广播群
|
///全服通知 / 语区广播群
|
||||||
if (message.groupID == SCGlobalConfig.bigBroadcastGroup ||
|
if (groupId == SCGlobalConfig.bigBroadcastGroup ||
|
||||||
message.groupID == roomRedPacketBroadcastGroupId) {
|
groupId == roomRedPacketBroadcastGroupId) {
|
||||||
_newBroadCastMsgRecv(message.groupID!, message);
|
_newBroadCastMsgRecv(groupId, message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_shouldRouteUntrackedRoomRedPacketBroadcast(groupId, message)) {
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][IM] route untracked group as region broadcast '
|
||||||
|
'group=$groupId cachedRegionGroup=$roomRedPacketBroadcastGroupId',
|
||||||
|
);
|
||||||
|
_newBroadCastMsgRecv(groupId, message, forceRegionBroadcast: true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
///群消息
|
///群消息
|
||||||
for (var element in onNewMessageListenerGroupMap.values) {
|
for (var element in onNewMessageListenerGroupMap.values) {
|
||||||
element?.call(message.groupID!, message);
|
element?.call(groupId, message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
///单聊消息
|
///单聊消息
|
||||||
@ -1780,6 +1791,38 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _currentVoiceRoomGroupId() {
|
||||||
|
final currentContext = context;
|
||||||
|
if (currentContext == null || !currentContext.mounted) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return Provider.of<RealTimeCommunicationManager>(
|
||||||
|
currentContext,
|
||||||
|
listen: false,
|
||||||
|
).currenRoom?.roomProfile?.roomProfile?.roomAccount?.trim() ??
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldRouteUntrackedRoomRedPacketBroadcast(
|
||||||
|
String groupId,
|
||||||
|
V2TimMessage message,
|
||||||
|
) {
|
||||||
|
if (groupId == _currentVoiceRoomGroupId()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final customData = message.customElem?.data;
|
||||||
|
if (customData == null || customData.isEmpty) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
final decoded = json.decode(customData);
|
||||||
|
final payload = _broadcastPayloadMap(decoded);
|
||||||
|
return payload['type'] == SCRoomMsgType.roomRedPacket;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _broadcastPayloadMap(dynamic data) {
|
Map<String, dynamic> _broadcastPayloadMap(dynamic data) {
|
||||||
if (data is Map<String, dynamic>) {
|
if (data is Map<String, dynamic>) {
|
||||||
return data;
|
return data;
|
||||||
@ -1902,11 +1945,16 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
///全服广播消息
|
///全服广播消息
|
||||||
_newBroadCastMsgRecv(String groupID, V2TimMessage message) async {
|
_newBroadCastMsgRecv(
|
||||||
|
String groupID,
|
||||||
|
V2TimMessage message, {
|
||||||
|
bool forceRegionBroadcast = false,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
String? customData = message.customElem?.data;
|
String? customData = message.customElem?.data;
|
||||||
if (customData != null && customData.isNotEmpty) {
|
if (customData != null && customData.isNotEmpty) {
|
||||||
final isRegionBroadcastGroup = groupID == roomRedPacketBroadcastGroupId;
|
final isRegionBroadcastGroup =
|
||||||
|
forceRegionBroadcast || groupID == roomRedPacketBroadcastGroupId;
|
||||||
final data = json.decode(customData);
|
final data = json.decode(customData);
|
||||||
var type = data["type"];
|
var type = data["type"];
|
||||||
if (type == "SYS_ACTIVITY") {
|
if (type == "SYS_ACTIVITY") {
|
||||||
@ -1988,7 +2036,25 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
} else if (type == SCRoomMsgType.roomRedPacket) {
|
} else if (type == SCRoomMsgType.roomRedPacket) {
|
||||||
///红包触发飘屏
|
///红包触发飘屏
|
||||||
var fData = data["data"];
|
var fData = data["data"];
|
||||||
|
final payload = _broadcastPayloadMap(fData);
|
||||||
|
final packetId = _payloadText(payload['packetId']);
|
||||||
|
final packetRoomId = _payloadText(payload["roomId"]);
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][IM] recv broadcast packetId=$packetId '
|
||||||
|
'roomId=$packetRoomId group=$groupID '
|
||||||
|
'regionGroup=$roomRedPacketBroadcastGroupId '
|
||||||
|
'isRegion=$isRegionBroadcastGroup '
|
||||||
|
'payloadRegion=${_payloadText(payload['regionCode'])} '
|
||||||
|
'currentRegion=${roomRedPacketBroadcastRegionCode ?? ''} '
|
||||||
|
'currentRoom=${_currentVoiceRoomId()}',
|
||||||
|
);
|
||||||
if (!_isSameRoomRedPacketRegion(fData)) {
|
if (!_isSameRoomRedPacketRegion(fData)) {
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][IM] skip broadcast region mismatch '
|
||||||
|
'packetId=$packetId '
|
||||||
|
'payloadRegion=${_payloadText(payload['regionCode'])} '
|
||||||
|
'currentRegion=${roomRedPacketBroadcastRegionCode ?? ''}',
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RoomRedPacketPendingCache.instance.upsertPayload(fData);
|
RoomRedPacketPendingCache.instance.upsertPayload(fData);
|
||||||
@ -2007,7 +2073,6 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
: '',
|
: '',
|
||||||
priority: 1000,
|
priority: 1000,
|
||||||
);
|
);
|
||||||
final packetRoomId = _payloadText(fData["roomId"]);
|
|
||||||
if (_isCurrentVisibleVoiceRoom(packetRoomId)) {
|
if (_isCurrentVisibleVoiceRoom(packetRoomId)) {
|
||||||
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||||
context!,
|
context!,
|
||||||
@ -2616,6 +2681,12 @@ class RealTimeMessagingManager extends ChangeNotifier {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][IM] send broadcast packetId=${payload['packetId']} '
|
||||||
|
'roomId=${payload['roomId']} mode=${payload['packetMode']} '
|
||||||
|
'targets=${targetGroupIds.join(',')} regionGroup=$regionGroupId '
|
||||||
|
'region=${payload['regionCode'] ?? roomRedPacketBroadcastRegionCode ?? ''}',
|
||||||
|
);
|
||||||
final message = <String, dynamic>{
|
final message = <String, dynamic>{
|
||||||
'type': SCRoomMsgType.roomRedPacket,
|
'type': SCRoomMsgType.roomRedPacket,
|
||||||
'data': payload,
|
'data': payload,
|
||||||
|
|||||||
38
lib/services/audio/voice_room_foreground_service.dart
Normal file
38
lib/services/audio/voice_room_foreground_service.dart
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class SCVoiceRoomForegroundService {
|
||||||
|
static const MethodChannel _channel = MethodChannel(
|
||||||
|
'com.org.yumiparty/voice_room_foreground_service',
|
||||||
|
);
|
||||||
|
|
||||||
|
static Future<void> start({String? roomName}) async {
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod<void>('start', {
|
||||||
|
'roomName': roomName?.trim(),
|
||||||
|
});
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[VoiceRoomForeground] start failed: ${e.message}');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[VoiceRoomForeground] start failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> stop() async {
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await _channel.invokeMethod<void>('stop');
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
debugPrint('[VoiceRoomForeground] stop failed: ${e.message}');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[VoiceRoomForeground] stop failed: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,12 +24,14 @@ typedef FloatingScreenManager = OverlayManager;
|
|||||||
|
|
||||||
class OverlayManager {
|
class OverlayManager {
|
||||||
static const int _maxLowPerformanceCompensationQueueLength = 8;
|
static const int _maxLowPerformanceCompensationQueueLength = 8;
|
||||||
|
static const int _maxDeferredRegionRedPacketCount = 12;
|
||||||
static const Duration _compensationDedupWindow = Duration(seconds: 3);
|
static const Duration _compensationDedupWindow = Duration(seconds: 3);
|
||||||
static const Duration _redPacketFloatingDedupWindow = Duration(minutes: 30);
|
static const Duration _redPacketFloatingDedupWindow = Duration(minutes: 30);
|
||||||
|
|
||||||
final SCPriorityQueue<SCFloatingMessage> _messageQueue = SCPriorityQueue(
|
final SCPriorityQueue<SCFloatingMessage> _messageQueue = SCPriorityQueue(
|
||||||
(a, b) => b.priority.compareTo(a.priority),
|
(a, b) => b.priority.compareTo(a.priority),
|
||||||
);
|
);
|
||||||
|
final List<SCFloatingMessage> _deferredRegionRedPacketMessages = [];
|
||||||
final Map<String, DateTime> _recentCompensationKeys = {};
|
final Map<String, DateTime> _recentCompensationKeys = {};
|
||||||
final Map<String, DateTime> _recentFloatingKeys = {};
|
final Map<String, DateTime> _recentFloatingKeys = {};
|
||||||
bool _isPlaying = false;
|
bool _isPlaying = false;
|
||||||
@ -53,6 +55,12 @@ class OverlayManager {
|
|||||||
if (_isRecentFloatingDuplicate(message)) {
|
if (_isRecentFloatingDuplicate(message)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (message.type == 4) {
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][Floating] enqueue packetId=${message.toUserId} '
|
||||||
|
'roomId=${message.roomId} region=${message.isRegionBroadcast}',
|
||||||
|
);
|
||||||
|
}
|
||||||
_rememberFloatingMessage(message);
|
_rememberFloatingMessage(message);
|
||||||
_enqueueMessage(
|
_enqueueMessage(
|
||||||
message,
|
message,
|
||||||
@ -61,6 +69,7 @@ class OverlayManager {
|
|||||||
} else {
|
} else {
|
||||||
_removeActiveMessage(scheduleNext: false);
|
_removeActiveMessage(scheduleNext: false);
|
||||||
_messageQueue.clear();
|
_messageQueue.clear();
|
||||||
|
_deferredRegionRedPacketMessages.clear();
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
_isProcessing = false;
|
_isProcessing = false;
|
||||||
_isDisposed = false;
|
_isDisposed = false;
|
||||||
@ -256,9 +265,20 @@ class OverlayManager {
|
|||||||
// 安全地获取第一个消息
|
// 安全地获取第一个消息
|
||||||
if (_messageQueue.isEmpty) return;
|
if (_messageQueue.isEmpty) return;
|
||||||
final messageToProcess = _messageQueue.first;
|
final messageToProcess = _messageQueue.first;
|
||||||
|
if (messageToProcess == null) {
|
||||||
|
_messageQueue.removeFirst();
|
||||||
|
_safeScheduleNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!_shouldDisplayMessage(context, messageToProcess)) {
|
if (!_shouldDisplayMessage(context, messageToProcess)) {
|
||||||
_messageQueue.removeFirst();
|
_messageQueue.removeFirst();
|
||||||
|
if (_shouldDeferRegionRedPacketMessage(context, messageToProcess)) {
|
||||||
|
_deferRegionRedPacketMessage(context, messageToProcess);
|
||||||
|
_safeScheduleNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_logRegionRedPacketDrop(context, messageToProcess);
|
||||||
_safeScheduleNext();
|
_safeScheduleNext();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -401,6 +421,7 @@ class OverlayManager {
|
|||||||
_removeRegionBroadcastMessages();
|
_removeRegionBroadcastMessages();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_restoreDeferredRegionRedPacketMessages();
|
||||||
_safeScheduleNext();
|
_safeScheduleNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,8 +455,10 @@ class OverlayManager {
|
|||||||
if (SCGlobalConfig.isFloatingBroadcastClosed) {
|
if (SCGlobalConfig.isFloatingBroadcastClosed) {
|
||||||
_removeActiveMessage(scheduleNext: false);
|
_removeActiveMessage(scheduleNext: false);
|
||||||
_messageQueue.clear();
|
_messageQueue.clear();
|
||||||
|
_deferredRegionRedPacketMessages.clear();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_restoreDeferredRegionRedPacketMessages();
|
||||||
final context = navigatorKey.currentState?.context;
|
final context = navigatorKey.currentState?.context;
|
||||||
final activeMessage = _currentMessage;
|
final activeMessage = _currentMessage;
|
||||||
if (context != null &&
|
if (context != null &&
|
||||||
@ -456,6 +479,7 @@ class OverlayManager {
|
|||||||
_currentOverlayEntry = null;
|
_currentOverlayEntry = null;
|
||||||
_currentMessage = null;
|
_currentMessage = null;
|
||||||
_messageQueue.clear();
|
_messageQueue.clear();
|
||||||
|
_deferredRegionRedPacketMessages.clear();
|
||||||
_recentCompensationKeys.clear();
|
_recentCompensationKeys.clear();
|
||||||
_isPlaying = false;
|
_isPlaying = false;
|
||||||
_isProcessing = false;
|
_isProcessing = false;
|
||||||
@ -497,6 +521,86 @@ class OverlayManager {
|
|||||||
|
|
||||||
bool get _isSuppressed => _suppressedCount > 0;
|
bool get _isSuppressed => _suppressedCount > 0;
|
||||||
|
|
||||||
|
bool _isRegionRedPacketMessage(SCFloatingMessage message) {
|
||||||
|
return message.type == 4 && message.isRegionBroadcast;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldDeferRegionRedPacketMessage(
|
||||||
|
BuildContext context,
|
||||||
|
SCFloatingMessage message,
|
||||||
|
) {
|
||||||
|
if (!_isRegionRedPacketMessage(message) ||
|
||||||
|
SCGlobalConfig.isFloatingBroadcastClosed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !_shouldDisplayRegionBroadcastMessage(context, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _deferRegionRedPacketMessage(
|
||||||
|
BuildContext context,
|
||||||
|
SCFloatingMessage message,
|
||||||
|
) {
|
||||||
|
final key = _floatingDedupKeyFor(message);
|
||||||
|
if (key != null &&
|
||||||
|
_deferredRegionRedPacketMessages.any(
|
||||||
|
(item) => _floatingDedupKeyFor(item) == key,
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (_deferredRegionRedPacketMessages.length >=
|
||||||
|
_maxDeferredRegionRedPacketCount) {
|
||||||
|
_deferredRegionRedPacketMessages.removeAt(0);
|
||||||
|
}
|
||||||
|
_deferredRegionRedPacketMessages.add(message);
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][Floating] defer packetId=${message.toUserId} '
|
||||||
|
'roomId=${message.roomId} ${_displayStateForLog(context)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _restoreDeferredRegionRedPacketMessages() {
|
||||||
|
if (_deferredRegionRedPacketMessages.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final messages = List<SCFloatingMessage>.from(
|
||||||
|
_deferredRegionRedPacketMessages,
|
||||||
|
);
|
||||||
|
_deferredRegionRedPacketMessages.clear();
|
||||||
|
for (final message in messages) {
|
||||||
|
_messageQueue.add(message);
|
||||||
|
}
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][Floating] restore deferred count=${messages.length}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logRegionRedPacketDrop(
|
||||||
|
BuildContext context,
|
||||||
|
SCFloatingMessage message,
|
||||||
|
) {
|
||||||
|
if (message.type != 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
debugPrint(
|
||||||
|
'[RoomRedPacket][Floating] drop packetId=${message.toUserId} '
|
||||||
|
'roomId=${message.roomId} region=${message.isRegionBroadcast} '
|
||||||
|
'${_displayStateForLog(context)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _displayStateForLog(BuildContext context) {
|
||||||
|
final rtcProvider = Provider.of<RealTimeCommunicationManager>(
|
||||||
|
context,
|
||||||
|
listen: false,
|
||||||
|
);
|
||||||
|
final currentRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? "";
|
||||||
|
return 'homeVisible=$_homeRootTabsVisible '
|
||||||
|
'roomVisible=${rtcProvider.shouldShowRoomVisualEffects} '
|
||||||
|
'currentRoomId=$currentRoomId '
|
||||||
|
'scope=${SCGlobalConfig.floatingBroadcastScope}';
|
||||||
|
}
|
||||||
|
|
||||||
bool _shouldDisplayMessage(BuildContext context, SCFloatingMessage? message) {
|
bool _shouldDisplayMessage(BuildContext context, SCFloatingMessage? message) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
return false;
|
return false;
|
||||||
@ -582,7 +686,7 @@ class OverlayManager {
|
|||||||
|
|
||||||
while (_messageQueue.isNotEmpty) {
|
while (_messageQueue.isNotEmpty) {
|
||||||
final message = _messageQueue.removeFirst();
|
final message = _messageQueue.removeFirst();
|
||||||
if (!message.isRegionBroadcast) {
|
if (!message.isRegionBroadcast || _isRegionRedPacketMessage(message)) {
|
||||||
newQueue.add(message);
|
newQueue.add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -607,6 +607,11 @@ class _RoomRedPacketSendPanelState extends State<RoomRedPacketSendPanel> {
|
|||||||
(_selectedPickupMinutes > 0 ? _selectedPickupMinutes * 60 : 0)
|
(_selectedPickupMinutes > 0 ? _selectedPickupMinutes * 60 : 0)
|
||||||
..['claimStartTime'] = claimStartTime
|
..['claimStartTime'] = claimStartTime
|
||||||
..['grabbed'] = result.grabbed ?? false;
|
..['grabbed'] = result.grabbed ?? false;
|
||||||
|
final roomRegionCode = rtcProvider.currenRoom?.roomProfile?.regionCode;
|
||||||
|
if ((payload['regionCode']?.toString().trim() ?? '').isEmpty &&
|
||||||
|
(roomRegionCode?.trim().isNotEmpty ?? false)) {
|
||||||
|
payload['regionCode'] = roomRegionCode!.trim();
|
||||||
|
}
|
||||||
|
|
||||||
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
final rtmProvider = Provider.of<RtmProvider>(context, listen: false);
|
||||||
rtmProvider.addMsg(
|
rtmProvider.addMsg(
|
||||||
|
|||||||
@ -68,6 +68,7 @@ class _RoomRedPacketFloatingEntryState
|
|||||||
Timer? _entryRefreshTimer;
|
Timer? _entryRefreshTimer;
|
||||||
bool _refreshingList = false;
|
bool _refreshingList = false;
|
||||||
int _elapsedSeconds = 0;
|
int _elapsedSeconds = 0;
|
||||||
|
String _observedRoomId = '';
|
||||||
String _verifiedRoomId = '';
|
String _verifiedRoomId = '';
|
||||||
int _verifiedRoomRefreshCount = 0;
|
int _verifiedRoomRefreshCount = 0;
|
||||||
Set<String> _verifiedKnownPacketIds = const <String>{};
|
Set<String> _verifiedKnownPacketIds = const <String>{};
|
||||||
@ -80,6 +81,11 @@ class _RoomRedPacketFloatingEntryState
|
|||||||
const Duration(seconds: 1),
|
const Duration(seconds: 1),
|
||||||
(_) => _onEntryTimerTick(),
|
(_) => _onEntryTimerTick(),
|
||||||
);
|
);
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_refreshRoomRedPacketList();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -93,6 +99,7 @@ class _RoomRedPacketFloatingEntryState
|
|||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_refreshRoomRedPacketListIfRoomChanged();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
_elapsedSeconds++;
|
_elapsedSeconds++;
|
||||||
if (_elapsedSeconds % _listRefreshIntervalSeconds == 0) {
|
if (_elapsedSeconds % _listRefreshIntervalSeconds == 0) {
|
||||||
@ -100,6 +107,24 @@ class _RoomRedPacketFloatingEntryState
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _refreshRoomRedPacketListIfRoomChanged() {
|
||||||
|
try {
|
||||||
|
final rtcProvider = Provider.of<RtcProvider>(context, listen: false);
|
||||||
|
final currentRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id?.trim() ?? '';
|
||||||
|
if (currentRoomId.isEmpty || currentRoomId == _observedRoomId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_observedRoomId = currentRoomId;
|
||||||
|
_elapsedSeconds = 0;
|
||||||
|
_verifiedRoomId = '';
|
||||||
|
_verifiedRoomRefreshCount = 0;
|
||||||
|
_verifiedKnownPacketIds = const <String>{};
|
||||||
|
_verifiedVisiblePacketIds = const <String>{};
|
||||||
|
_refreshRoomRedPacketList();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
void _refreshRoomRedPacketList() {
|
void _refreshRoomRedPacketList() {
|
||||||
if (_refreshingList || !mounted) {
|
if (_refreshingList || !mounted) {
|
||||||
return;
|
return;
|
||||||
@ -116,6 +141,13 @@ class _RoomRedPacketFloatingEntryState
|
|||||||
rtcProvider
|
rtcProvider
|
||||||
.loadRoomRedPacketList(1)
|
.loadRoomRedPacketList(1)
|
||||||
.then((result) {
|
.then((result) {
|
||||||
|
final activeRoomId =
|
||||||
|
rtcProvider.currenRoom?.roomProfile?.roomProfile?.id
|
||||||
|
?.trim() ??
|
||||||
|
'';
|
||||||
|
if (activeRoomId != currentRoomId) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
final serverPackets =
|
final serverPackets =
|
||||||
result.map(RoomRedPacketUiData.fromListRes).toList();
|
result.map(RoomRedPacketUiData.fromListRes).toList();
|
||||||
final serverPacketIds =
|
final serverPacketIds =
|
||||||
|
|||||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.3.0+10
|
version: 1.3.0+11
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
1
需求进度.md
1
需求进度.md
@ -562,6 +562,7 @@
|
|||||||
- 已修正红包领取成功公屏消息的多语言问题:领取后不再发送已翻译好的普通文本,改为 `ROOM_RED_PACKET_CLAIM` 结构化房间 IM,只传领取金额、红包发送人名和 `packetId`,公屏渲染时按当前客户端语言生成文案,避免英文用户看到阿语消息。
|
- 已修正红包领取成功公屏消息的多语言问题:领取后不再发送已翻译好的普通文本,改为 `ROOM_RED_PACKET_CLAIM` 结构化房间 IM,只传领取金额、红包发送人名和 `packetId`,公屏渲染时按当前客户端语言生成文案,避免英文用户看到阿语消息。
|
||||||
- 已补强房外延时红包飘屏和领取通知去重:首页四个一级 Tab 可见/回到前台时会重新同步当前语区红包 IM 群;语区红包飘屏在首页 Tab 中不再受“只看房间飘屏”设置影响;领取成功公屏通知按 `packetId + 领取人ID` 生成稳定去重键,避免本地插入和 IM 回包/历史拉取造成两条一模一样的领取消息。
|
- 已补强房外延时红包飘屏和领取通知去重:首页四个一级 Tab 可见/回到前台时会重新同步当前语区红包 IM 群;语区红包飘屏在首页 Tab 中不再受“只看房间飘屏”设置影响;领取成功公屏通知按 `packetId + 领取人ID` 生成稳定去重键,避免本地插入和 IM 回包/历史拉取造成两条一模一样的领取消息。
|
||||||
- 已定位并修复公屏上滑历史反复弹 `user info not found`:历史消息 item 重建时会重复调用用户资料接口,缺失用户返回错误后被网络层 toast;现在房间公屏补拉用户资料改为静默请求,并对同一 userId 的请求/失败结果做缓存,5 分钟内不再反复请求同一个不存在用户。
|
- 已定位并修复公屏上滑历史反复弹 `user info not found`:历史消息 item 重建时会重复调用用户资料接口,缺失用户返回错误后被网络层 toast;现在房间公屏补拉用户资料改为静默请求,并对同一 userId 的请求/失败结果做缓存,5 分钟内不再反复请求同一个不存在用户。
|
||||||
|
- 已继续排查房外/跨房红包飘屏缺失:IM 重登成功后会立即重新同步当前语区红包群;收到未登记但内容为 `ROOM_RED_PACKET` 的群消息时会按语区红包飘屏兜底处理,避免语区群 id 尚未写入时被普通群消息分支吞掉;区域红包在首页 Tab 暂不可见时不再直接从飘屏队列丢弃,而是挂起后在回到首页 Tab 时恢复播放;发送红包 payload 也会补齐当前房间 `regionCode`,便于日志核对和前端兜底过滤。
|
||||||
|
|
||||||
## 已知问题
|
## 已知问题
|
||||||
- 已接入语音房表情包素材第一版:桌面 `表情素材` 中的 `fluent_emoji` 与 `monkey` gif 已纳入项目资源,底部聊天入口改为图稿里的短白色胶囊并移除内部输入图标,右侧新增独立表情按钮;同一个房间输入弹层现在可从表情按钮直接打开表情包面板,原 unicode emoji 面板已替换为本地 gif 表情。发送逻辑同步按最新规则分流:用户在麦上发表情时只在自己麦位播放,不进入聊天列表;用户未上麦发表情时进入房间聊天框展示,不触发麦位动画。
|
- 已接入语音房表情包素材第一版:桌面 `表情素材` 中的 `fluent_emoji` 与 `monkey` gif 已纳入项目资源,底部聊天入口改为图稿里的短白色胶囊并移除内部输入图标,右侧新增独立表情按钮;同一个房间输入弹层现在可从表情按钮直接打开表情包面板,原 unicode emoji 面板已替换为本地 gif 表情。发送逻辑同步按最新规则分流:用户在麦上发表情时只在自己麦位播放,不进入聊天列表;用户未上麦发表情时进入房间聊天框展示,不触发麦位动画。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user