feat: норм отправка кружком как дфывйцждуозйшо / dev 18
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEefQULnwjVZt2yzvK8CWn
This commit is contained in:
co-authored by
Claude Opus 5
parent
7bbc77192a
commit
08d29ef996
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -5,7 +7,7 @@ import 'package:flutter/widgets.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
import '../../main.dart' show KometApp;
|
||||
|
||||
enum UploadKind { photo, video, file }
|
||||
enum UploadKind { photo, video, videoNote, voice, file }
|
||||
|
||||
class _NotificationJob {
|
||||
_NotificationJob({required this.kind, required this.count, this.filename});
|
||||
@@ -41,6 +43,8 @@ class _NotificationJob {
|
||||
return switch (kind) {
|
||||
UploadKind.photo => l10n.uploadNotificationPhotos(count),
|
||||
UploadKind.video => l10n.uploadNotificationVideo,
|
||||
UploadKind.videoNote => l10n.uploadNotificationVideoNote,
|
||||
UploadKind.voice => l10n.uploadNotificationVoice,
|
||||
UploadKind.file =>
|
||||
name == null || name.isEmpty ? l10n.uploadNotificationFile : name,
|
||||
};
|
||||
@@ -52,8 +56,10 @@ class UploadNotificationService {
|
||||
'ru.komet.app/upload_service',
|
||||
);
|
||||
static const int _minIntervalMs = 350;
|
||||
static const Duration _startDelay = Duration(milliseconds: 700);
|
||||
|
||||
static final Map<String, _NotificationJob> _jobs = {};
|
||||
static Timer? _startTimer;
|
||||
static bool _running = false;
|
||||
static String? _lastTitle;
|
||||
static String? _lastBody;
|
||||
@@ -75,7 +81,14 @@ class UploadNotificationService {
|
||||
count: count < 1 ? 1 : count,
|
||||
filename: filename,
|
||||
);
|
||||
_push(force: true);
|
||||
if (_running) {
|
||||
_push(force: true);
|
||||
return;
|
||||
}
|
||||
_startTimer ??= Timer(_startDelay, () {
|
||||
_startTimer = null;
|
||||
_push(force: true);
|
||||
});
|
||||
}
|
||||
|
||||
static void report(
|
||||
@@ -102,16 +115,20 @@ class UploadNotificationService {
|
||||
}
|
||||
|
||||
static void _stop() {
|
||||
_startTimer?.cancel();
|
||||
_startTimer = null;
|
||||
final wasRunning = _running;
|
||||
_running = false;
|
||||
_lastTitle = null;
|
||||
_lastBody = null;
|
||||
_lastPercent = -1;
|
||||
_lastPushAt = 0;
|
||||
_invoke('stop', const <String, dynamic>{});
|
||||
if (wasRunning) _invoke('stop', const <String, dynamic>{});
|
||||
}
|
||||
|
||||
static void _push({bool force = false}) {
|
||||
if (_jobs.isEmpty) return;
|
||||
if (!_running && _startTimer != null) return;
|
||||
|
||||
var sumSent = 0;
|
||||
var sumTotal = 0;
|
||||
|
||||
@@ -291,6 +291,106 @@ class UploadService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendVoice({
|
||||
required int accountId,
|
||||
required int chatId,
|
||||
required String tempId,
|
||||
required File file,
|
||||
required int durationMs,
|
||||
required Uint8List wave,
|
||||
CachedMessage? placeholder,
|
||||
}) {
|
||||
return _sendRecording(
|
||||
accountId: accountId,
|
||||
chatId: chatId,
|
||||
tempId: tempId,
|
||||
file: file,
|
||||
kind: UploadKind.voice,
|
||||
placeholder: placeholder,
|
||||
requestUpload: () async {
|
||||
final info = await messagesModule.requestAudioUploadUrl();
|
||||
return info == null ? null : (url: info.url, token: info.token);
|
||||
},
|
||||
send: (token) => messagesModule.sendAudioMessage(
|
||||
chatId,
|
||||
token,
|
||||
duration: durationMs,
|
||||
wave: wave,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendVideoNote({
|
||||
required int accountId,
|
||||
required int chatId,
|
||||
required String tempId,
|
||||
required File file,
|
||||
required int durationMs,
|
||||
CachedMessage? placeholder,
|
||||
}) {
|
||||
return _sendRecording(
|
||||
accountId: accountId,
|
||||
chatId: chatId,
|
||||
tempId: tempId,
|
||||
file: file,
|
||||
kind: UploadKind.videoNote,
|
||||
placeholder: placeholder,
|
||||
requestUpload: () async {
|
||||
final info = await messagesModule.requestVideoNoteUploadUrl();
|
||||
return info == null ? null : (url: info.url, token: info.token);
|
||||
},
|
||||
send: (token) => messagesModule.sendVideoNoteMessage(
|
||||
chatId,
|
||||
token,
|
||||
duration: durationMs,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _sendRecording({
|
||||
required int accountId,
|
||||
required int chatId,
|
||||
required String tempId,
|
||||
required File file,
|
||||
required UploadKind kind,
|
||||
required Future<({String url, String token})?> Function() requestUpload,
|
||||
required Future<Map<String, dynamic>?> Function(String token) send,
|
||||
CachedMessage? placeholder,
|
||||
}) {
|
||||
return _run(
|
||||
UploadJob._(
|
||||
id: tempId,
|
||||
accountId: accountId,
|
||||
chatId: chatId,
|
||||
kind: kind,
|
||||
slots: 1,
|
||||
placeholder: placeholder,
|
||||
),
|
||||
(job) async {
|
||||
try {
|
||||
final info = await requestUpload();
|
||||
if (info == null || info.url.isEmpty) {
|
||||
throw const UploadFailure('no_upload_url');
|
||||
}
|
||||
final ok = await fileUploader.uploadMediaFile(
|
||||
Uri.parse(info.url),
|
||||
file,
|
||||
onProgress: (sent, total) => job.report(0, sent, total),
|
||||
);
|
||||
if (!ok) throw const UploadFailure('upload_failed');
|
||||
job.markUploaded();
|
||||
final sent = await send(info.token);
|
||||
if (sent == null) return null;
|
||||
return CachedMessage.fromPushPayload(accountId, chatId, sent);
|
||||
} finally {
|
||||
try {
|
||||
await file.delete();
|
||||
} catch (_) {}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> sendFile({
|
||||
required int accountId,
|
||||
required int chatId,
|
||||
|
||||
Reference in New Issue
Block a user