317 lines
8.1 KiB
Dart
317 lines
8.1 KiB
Dart
import 'dart:async';
|
|
import 'dart:collection';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/gift_res.dart';
|
|
import 'package:yumi/shared/business_logic/models/res/mic_res.dart';
|
|
|
|
typedef RoomGiftComboSendExecutor =
|
|
Future<void> Function(
|
|
RoomGiftComboSendRequest request, {
|
|
required String trigger,
|
|
});
|
|
|
|
class RoomGiftComboSendRequest {
|
|
const RoomGiftComboSendRequest({
|
|
required this.acceptUserIds,
|
|
required this.acceptUsers,
|
|
required this.gift,
|
|
required this.quantity,
|
|
required this.clickCount,
|
|
required this.roomId,
|
|
required this.roomAccount,
|
|
required this.isLuckyGiftRequest,
|
|
});
|
|
|
|
final List<String> acceptUserIds;
|
|
final List<MicRes> acceptUsers;
|
|
final SocialChatGiftRes gift;
|
|
final int quantity;
|
|
final int clickCount;
|
|
final String roomId;
|
|
final String roomAccount;
|
|
final bool isLuckyGiftRequest;
|
|
|
|
RoomGiftComboSendRequest copyWith({
|
|
List<String>? acceptUserIds,
|
|
List<MicRes>? acceptUsers,
|
|
SocialChatGiftRes? gift,
|
|
int? quantity,
|
|
int? clickCount,
|
|
String? roomId,
|
|
String? roomAccount,
|
|
bool? isLuckyGiftRequest,
|
|
}) {
|
|
return RoomGiftComboSendRequest(
|
|
acceptUserIds: List<String>.from(acceptUserIds ?? this.acceptUserIds),
|
|
acceptUsers: List<MicRes>.from(acceptUsers ?? this.acceptUsers),
|
|
gift: gift ?? this.gift,
|
|
quantity: quantity ?? this.quantity,
|
|
clickCount: clickCount ?? this.clickCount,
|
|
roomId: roomId ?? this.roomId,
|
|
roomAccount: roomAccount ?? this.roomAccount,
|
|
isLuckyGiftRequest: isLuckyGiftRequest ?? this.isLuckyGiftRequest,
|
|
);
|
|
}
|
|
|
|
String get batchKey {
|
|
final sortedAcceptUserIds = List<String>.from(acceptUserIds)..sort();
|
|
return '${isLuckyGiftRequest ? "lucky" : "gift"}'
|
|
'|${gift.id ?? ""}'
|
|
'|$roomId'
|
|
'|${sortedAcceptUserIds.join(",")}';
|
|
}
|
|
}
|
|
|
|
class RoomGiftComboSendController extends ChangeNotifier {
|
|
static final RoomGiftComboSendController _instance =
|
|
RoomGiftComboSendController._internal();
|
|
|
|
factory RoomGiftComboSendController() => _instance;
|
|
|
|
RoomGiftComboSendController._internal();
|
|
|
|
static const Duration comboFeedbackDuration = Duration(seconds: 3);
|
|
static const Duration _comboSendBatchWindow = Duration(milliseconds: 200);
|
|
static const Duration _countdownRefreshInterval = Duration(milliseconds: 50);
|
|
|
|
final ListQueue<_PendingRoomGiftComboSendBatch> _queue =
|
|
ListQueue<_PendingRoomGiftComboSendBatch>();
|
|
|
|
RoomGiftComboSendRequest? _activeRequest;
|
|
RoomGiftComboSendExecutor? _executor;
|
|
Timer? _countdownTimer;
|
|
Timer? _batchTimer;
|
|
DateTime? _countdownDeadline;
|
|
bool _isBatchInFlight = false;
|
|
bool _visible = false;
|
|
|
|
bool get isVisible => _visible && _activeRequest != null && _executor != null;
|
|
|
|
double get countdownProgress {
|
|
if (!isVisible) {
|
|
return 0;
|
|
}
|
|
|
|
final deadline = _countdownDeadline;
|
|
if (deadline == null) {
|
|
return 0;
|
|
}
|
|
|
|
final totalMs = comboFeedbackDuration.inMilliseconds;
|
|
if (totalMs <= 0) {
|
|
return 1;
|
|
}
|
|
|
|
final remainingMs = deadline.difference(DateTime.now()).inMilliseconds;
|
|
if (remainingMs <= 0) {
|
|
return 1;
|
|
}
|
|
|
|
return (1 - remainingMs / totalMs).clamp(0.0, 1.0).toDouble();
|
|
}
|
|
|
|
void show({
|
|
required RoomGiftComboSendRequest request,
|
|
required RoomGiftComboSendExecutor executor,
|
|
}) {
|
|
_activeRequest = request.copyWith();
|
|
_executor = executor;
|
|
_visible = true;
|
|
_restartCountdown();
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> sendActiveRequest() async {
|
|
final request = _activeRequest;
|
|
final executor = _executor;
|
|
if (request == null || executor == null) {
|
|
return;
|
|
}
|
|
|
|
_visible = true;
|
|
_restartCountdown();
|
|
_enqueueRequest(request.copyWith(clickCount: 1));
|
|
notifyListeners();
|
|
}
|
|
|
|
void hide() {
|
|
_countdownTimer?.cancel();
|
|
_countdownTimer = null;
|
|
_countdownDeadline = null;
|
|
if (_visible) {
|
|
_visible = false;
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void clear() {
|
|
_countdownTimer?.cancel();
|
|
_countdownTimer = null;
|
|
_batchTimer?.cancel();
|
|
_batchTimer = null;
|
|
_countdownDeadline = null;
|
|
_queue.clear();
|
|
_activeRequest = null;
|
|
_executor = null;
|
|
_isBatchInFlight = false;
|
|
_visible = false;
|
|
notifyListeners();
|
|
}
|
|
|
|
void _restartCountdown() {
|
|
_countdownDeadline = DateTime.now().add(comboFeedbackDuration);
|
|
_countdownTimer?.cancel();
|
|
_countdownTimer = Timer.periodic(_countdownRefreshInterval, (timer) {
|
|
final deadline = _countdownDeadline;
|
|
if (deadline == null) {
|
|
timer.cancel();
|
|
return;
|
|
}
|
|
|
|
if (!DateTime.now().isBefore(deadline)) {
|
|
timer.cancel();
|
|
_countdownDeadline = null;
|
|
if (_visible) {
|
|
_visible = false;
|
|
notifyListeners();
|
|
}
|
|
return;
|
|
}
|
|
|
|
notifyListeners();
|
|
});
|
|
}
|
|
|
|
void _enqueueRequest(RoomGiftComboSendRequest request) {
|
|
final now = DateTime.now();
|
|
_PendingRoomGiftComboSendBatch? existingBatch;
|
|
for (final batch in _queue) {
|
|
if (batch.batchKey == request.batchKey) {
|
|
existingBatch = batch;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (existingBatch != null) {
|
|
existingBatch.quantity += request.quantity;
|
|
existingBatch.clickCount += request.clickCount;
|
|
existingBatch.readyAt = now.add(_comboSendBatchWindow);
|
|
} else {
|
|
_queue.add(
|
|
_PendingRoomGiftComboSendBatch.fromRequest(
|
|
request,
|
|
readyAt: now.add(_comboSendBatchWindow),
|
|
),
|
|
);
|
|
}
|
|
|
|
_scheduleNextFlush();
|
|
}
|
|
|
|
void _scheduleNextFlush() {
|
|
_batchTimer?.cancel();
|
|
_batchTimer = null;
|
|
if (_isBatchInFlight || _queue.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
final nextBatch = _queue.first;
|
|
final delay = nextBatch.readyAt.difference(DateTime.now());
|
|
if (delay <= Duration.zero) {
|
|
unawaited(_flushNextBatch());
|
|
return;
|
|
}
|
|
|
|
_batchTimer = Timer(delay, () {
|
|
unawaited(_flushNextBatch());
|
|
});
|
|
}
|
|
|
|
Future<void> _flushNextBatch() async {
|
|
_batchTimer?.cancel();
|
|
_batchTimer = null;
|
|
if (_isBatchInFlight || _queue.isEmpty) {
|
|
return;
|
|
}
|
|
|
|
final executor = _executor;
|
|
if (executor == null) {
|
|
_queue.clear();
|
|
return;
|
|
}
|
|
|
|
final batch = _queue.first;
|
|
if (batch.readyAt.isAfter(DateTime.now())) {
|
|
_scheduleNextFlush();
|
|
return;
|
|
}
|
|
|
|
_queue.removeFirst();
|
|
_isBatchInFlight = true;
|
|
try {
|
|
await executor(batch.toRequest(), trigger: 'floating_batched');
|
|
} finally {
|
|
_isBatchInFlight = false;
|
|
_scheduleNextFlush();
|
|
}
|
|
}
|
|
}
|
|
|
|
class _PendingRoomGiftComboSendBatch {
|
|
_PendingRoomGiftComboSendBatch({
|
|
required this.batchKey,
|
|
required this.acceptUserIds,
|
|
required this.acceptUsers,
|
|
required this.gift,
|
|
required this.quantity,
|
|
required this.clickCount,
|
|
required this.roomId,
|
|
required this.roomAccount,
|
|
required this.isLuckyGiftRequest,
|
|
required this.readyAt,
|
|
});
|
|
|
|
factory _PendingRoomGiftComboSendBatch.fromRequest(
|
|
RoomGiftComboSendRequest request, {
|
|
required DateTime readyAt,
|
|
}) {
|
|
return _PendingRoomGiftComboSendBatch(
|
|
batchKey: request.batchKey,
|
|
acceptUserIds: List<String>.from(request.acceptUserIds),
|
|
acceptUsers: List<MicRes>.from(request.acceptUsers),
|
|
gift: request.gift,
|
|
quantity: request.quantity,
|
|
clickCount: request.clickCount,
|
|
roomId: request.roomId,
|
|
roomAccount: request.roomAccount,
|
|
isLuckyGiftRequest: request.isLuckyGiftRequest,
|
|
readyAt: readyAt,
|
|
);
|
|
}
|
|
|
|
final String batchKey;
|
|
final List<String> acceptUserIds;
|
|
final List<MicRes> acceptUsers;
|
|
final SocialChatGiftRes gift;
|
|
int quantity;
|
|
int clickCount;
|
|
final String roomId;
|
|
final String roomAccount;
|
|
final bool isLuckyGiftRequest;
|
|
DateTime readyAt;
|
|
|
|
RoomGiftComboSendRequest toRequest() {
|
|
return RoomGiftComboSendRequest(
|
|
acceptUserIds: List<String>.from(acceptUserIds),
|
|
acceptUsers: List<MicRes>.from(acceptUsers),
|
|
gift: gift,
|
|
quantity: quantity,
|
|
clickCount: clickCount,
|
|
roomId: roomId,
|
|
roomAccount: roomAccount,
|
|
isLuckyGiftRequest: isLuckyGiftRequest,
|
|
);
|
|
}
|
|
}
|