Files
SashegDev 2347f382c6 сборка: APK (Android) + Linux Desktop
- Исправлены ошибки Flutter анализа (withOpacity, pbkdf2, импорты)
- Упрощён pubspec.yaml (только нужные зависимости)
- Android APK собран: build/app/outputs/flutter-apk/app-release.apk (22MB)
- Linux Desktop собран: build/linux/x64/release/bundle/jam_client

Для Windows: кросс-компиляция невозможна с Linux.
Нужен Windows хост для flutter build windows.
2026-06-06 23:07:57 +00:00

198 lines
4.7 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
enum ConnectionState { disconnected, connecting, connected }
class ConnectionService extends ChangeNotifier {
WebSocketChannel? _channel;
ConnectionState _state = ConnectionState.disconnected;
String _serverUrl = 'ws://localhost:8443/ws';
String? _userId;
String? _username;
String? _token;
Timer? _heartbeatTimer;
int _seq = 0;
final StreamController<Map<String, dynamic>> _messageController =
StreamController<Map<String, dynamic>>.broadcast();
Stream<Map<String, dynamic>> get messageStream => _messageController.stream;
ConnectionState get state => _state;
String? get userId => _userId;
String? get username => _username;
String? get token => _token;
bool get isConnected => _state == ConnectionState.connected;
String get serverUrl => _serverUrl;
set serverUrl(String url) {
_serverUrl = url;
notifyListeners();
}
@override
void dispose() {
disconnect();
_messageController.close();
super.dispose();
}
void init() {
notifyListeners();
}
Future<void> connect() async {
_state = ConnectionState.connecting;
notifyListeners();
try {
_channel = WebSocketChannel.connect(Uri.parse(_serverUrl));
await _channel!.ready;
_channel!.stream.listen(
(data) {
_handleMessage(data);
},
onError: (error) {
_state = ConnectionState.disconnected;
notifyListeners();
},
onDone: () {
_state = ConnectionState.disconnected;
_heartbeatTimer?.cancel();
notifyListeners();
},
);
_state = ConnectionState.connected;
_startHeartbeat();
notifyListeners();
} catch (e) {
_state = ConnectionState.disconnected;
notifyListeners();
rethrow;
}
}
void disconnect() {
_heartbeatTimer?.cancel();
_channel?.sink.close();
_channel = null;
_state = ConnectionState.disconnected;
notifyListeners();
}
void _startHeartbeat() {
_heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (_) {
send({'op': 0});
});
}
void _handleMessage(dynamic data) {
try {
final Map<String, dynamic> packet = jsonDecode(data as String);
final int op = packet['op'] as int;
switch (op) {
case 1: // Hello
break;
case 3: // Authenticated
final d = packet['d'] as Map<String, dynamic>;
_userId = d['user_id'] as String?;
_username = d['username'] as String?;
_token = d['token'] as String?;
notifyListeners();
break;
case 10: // MessageCreate
_messageController.add(packet['d'] as Map<String, dynamic>);
break;
case 13: // Reaction
_messageController.add(packet['d'] as Map<String, dynamic>);
break;
case 40: // VoiceState
_messageController.add(packet['d'] as Map<String, dynamic>);
break;
case 60: // Typing
_messageController.add(packet['d'] as Map<String, dynamic>);
break;
}
} catch (e) {
debugPrint('Failed to handle message: $e');
}
}
void send(Map<String, dynamic> packet) {
if (_channel == null) return;
packet['s'] = ++_seq;
_channel!.sink.add(jsonEncode(packet));
}
void authenticate({String? token, String? username}) {
send({
'op': 2,
'd': {
if (token != null) 'token': token,
if (username != null) 'username': username,
},
});
}
void sendMessage({
required String channelId,
required Uint8List content,
bool encrypted = false,
Uint8List? nonce,
String messageType = 'text',
String? replyTo,
}) {
send({
'op': 10,
'd': {
'channel_id': channelId,
'content': content.toList(),
'encrypted': encrypted,
if (nonce != null) 'nonce': nonce.toList(),
'message_type': messageType,
if (replyTo != null) 'reply_to': replyTo,
},
});
}
void sendReaction(String messageId, String emoji, {bool add = true}) {
send({
'op': 13,
'd': {
'message_id': messageId,
'emoji': emoji,
'add': add,
},
});
}
void updateVoiceState({
required String guildId,
String? channelId,
bool muted = false,
bool deafened = false,
}) {
send({
'op': 40,
'd': {
'guild_id': guildId,
'channel_id': channelId ?? '',
'muted': muted,
'deafened': deafened,
},
});
}
void sendTyping(String channelId) {
send({
'op': 60,
'd': {'channel_id': channelId},
});
}
}