fix(mobile-next): 按 B-91 收口 Android 通话视觉打回项

呼出/通话中按钮、计时器、底色、60s 未接与进出场动效对齐视觉规范。

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-20 09:58:28 +08:00
co-authored by Cursor multica-agent
parent 714794f7fe
commit 9bde926c81
20 changed files with 640 additions and 210 deletions
@@ -10,6 +10,7 @@ import 'package:just_audio/just_audio.dart';
import 'package:openim_common/openim_common.dart';
import 'package:rxdart/rxdart.dart';
import 'package:uuid/uuid.dart';
import 'package:vibration/vibration.dart';
import '../openim_live.dart';
@@ -53,12 +54,11 @@ mixin OpenIMLive {
final _ring = 'assets/audio/live_ring.wav';
final _audioPlayer = AudioPlayer(
// Handle audio_session events ourselves for the purpose of this demo.
handleInterruptions: false,
// androidApplyAudioAttributes: false,
// handleAudioSessionActivation: false,
);
Timer? _incomingVibrateTimer;
bool get isBusy => OpenIMLiveClient().isBusy;
onCloseLive() {
@@ -94,7 +94,7 @@ mixin OpenIMLive {
(event) async {
_beCalledEvent = null;
if (event.state == CallState.beCalled) {
_playSound();
_playSound(vibrate: true);
final mediaType = event.data.invitation!.mediaType;
final sessionType = event.data.invitation!.sessionType;
final callType = mediaType == 'audio' ? CallType.audio : CallType.video;
@@ -135,6 +135,7 @@ mixin OpenIMLive {
onError: onError,
onRoomDisconnected: () => onRoomDisconnected(event.data),
onStartCalling: onLiveConnected,
onInviteTimeout: () => _onInviteTimeout(event.data),
onClose: () {
_stopSound();
onLiveSessionClosed();
@@ -195,7 +196,7 @@ mixin OpenIMLive {
inviterUserID: inviterUserID,
inviteeUserIDList: inviteeUserIDList,
roomID: roomID ?? groupID ?? const Uuid().v4(),
timeout: 30,
timeout: Styles.callInviteTimeout.inSeconds,
mediaType: mediaType,
sessionType: sessionType,
platformID: IMUtils.getPlatform(),
@@ -233,6 +234,7 @@ mixin OpenIMLive {
},
onError: onError,
onRoomDisconnected: () => onRoomDisconnected(signal),
onInviteTimeout: () => _onInviteTimeout(signal),
onClose: () {
_stopSound();
onLiveSessionClosed();
@@ -242,7 +244,6 @@ mixin OpenIMLive {
onError(error, stack) {
Logger.print('onError=====> $error $stack');
OpenIMLiveClient().close();
_stopSound();
if (error is PlatformException) {
if (int.parse(error.code) == SDKErrorCode.hasBeenBlocked) {
@@ -250,7 +251,7 @@ mixin OpenIMLive {
return;
}
}
IMViews.showToast(StrRes.networkError);
IMViews.showToast(StrRes.callNetworkInterrupted);
}
onRoomDisconnected(SignalingInfo signalingInfo) {}
@@ -332,12 +333,20 @@ mixin OpenIMLive {
OpenIM.iMManager.messageManager.sendMessage(
message: message,
offlinePushInfo: OfflinePushInfo(),
userID: signaling.invitation!.inviterUserID,
userID: signaling.invitation!.inviterUserID == OpenIM.iMManager.userID
? signaling.invitation!.inviteeUserIDList!.first
: signaling.invitation!.inviterUserID,
isOnlineOnly: true);
return true;
}
Future<void> _onInviteTimeout(SignalingInfo signaling) async {
_stopSound();
insertSignalingMessageSubject.add(CallEvent(CallState.timeout, signaling));
await onTimeoutCancelled(signaling);
}
onTapHangup(SignalingInfo signaling, int duration, bool isPositive) async {
if (isPositive) {
final data = {'customType': CustomMessageType.callingHungup, 'data': signaling.invitation!.toJson()};
@@ -388,16 +397,44 @@ mixin OpenIMLive {
return list;
}
void _playSound() async {
void _playSound({bool vibrate = false}) async {
if (!_audioPlayer.playerState.playing) {
_audioPlayer.setAsset(_ring, package: 'openim_common');
_audioPlayer.setLoopMode(LoopMode.one);
_audioPlayer.setVolume(1.0);
_audioPlayer.play();
}
if (vibrate) {
_startIncomingVibrate();
}
}
void _startIncomingVibrate() {
_stopIncomingVibrate();
_pulseVibrate();
_incomingVibrateTimer = Timer.periodic(Styles.callVibrate, (_) => _pulseVibrate());
}
void _stopIncomingVibrate() {
_incomingVibrateTimer?.cancel();
_incomingVibrateTimer = null;
try {
Vibration.cancel();
} catch (_) {}
}
Future<void> _pulseVibrate() async {
try {
if (await Vibration.hasVibrator() == true) {
await Vibration.vibrate(duration: 200);
return;
}
} catch (_) {}
HapticFeedback.vibrate();
}
void _stopSound() async {
_stopIncomingVibrate();
if (_audioPlayer.playerState.playing) {
_audioPlayer.stop();
}