2024-12-15 00:52:28 +01:00
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:developer' show log;
|
2024-12-18 20:12:14 +01:00
|
|
|
import 'dart:io';
|
2024-12-15 00:52:28 +01:00
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
import 'package:flutter/material.dart';
|
2024-12-15 00:52:28 +01:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
|
|
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
import 'package:gergle/api/commands.dart';
|
|
|
|
import 'package:gergle/api/events.dart';
|
|
|
|
import 'package:gergle/state/player_state_bloc.dart';
|
2024-12-15 00:52:28 +01:00
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
@immutable
|
|
|
|
sealed class PlayerConnectionState {}
|
2024-12-15 00:52:28 +01:00
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
@immutable
|
|
|
|
class Disconnected extends PlayerConnectionState {}
|
2024-12-15 00:52:28 +01:00
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
@immutable
|
|
|
|
class Connecting extends PlayerConnectionState {
|
|
|
|
final String uri;
|
2024-12-15 00:52:28 +01:00
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
Connecting(this.uri);
|
|
|
|
}
|
|
|
|
|
|
|
|
@immutable
|
|
|
|
class Connected extends PlayerConnectionState {
|
|
|
|
final String uri;
|
|
|
|
final WebSocketChannel channel;
|
|
|
|
|
|
|
|
Connected(this.uri, this.channel);
|
|
|
|
}
|
|
|
|
|
|
|
|
@immutable
|
|
|
|
class ConnectionError extends PlayerConnectionState {
|
|
|
|
final String message;
|
|
|
|
final String uri;
|
|
|
|
|
|
|
|
ConnectionError(this.message, this.uri);
|
|
|
|
}
|
|
|
|
|
2024-12-19 12:26:28 +01:00
|
|
|
class ConnectionStateBloc
|
|
|
|
extends Bloc<PlayerConnectionEvent, PlayerConnectionState> {
|
2024-12-18 20:12:14 +01:00
|
|
|
final PlayerStateBloc playerStateBloc;
|
|
|
|
|
|
|
|
ConnectionStateBloc(this.playerStateBloc) : super(Disconnected()) {
|
|
|
|
on<Connect>((event, emit) async {
|
|
|
|
if (state is Connected) {
|
|
|
|
if ((state as Connected).uri == event.uri) {
|
|
|
|
log('Already connected to ${event.uri}');
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
// Clear connection, and reconnect
|
|
|
|
(state as Connected).channel.sink.close();
|
|
|
|
playerStateBloc.add(const ClearPlayerState());
|
|
|
|
}
|
2024-12-15 00:52:28 +01:00
|
|
|
}
|
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
emit(Connecting(event.uri));
|
2024-12-15 00:52:28 +01:00
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
final channel = WebSocketChannel.connect(
|
2024-12-15 00:52:28 +01:00
|
|
|
Uri.parse(event.uri),
|
|
|
|
);
|
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
try {
|
|
|
|
await channel.ready;
|
|
|
|
} on WebSocketChannelException catch (e) {
|
|
|
|
late final String message;
|
|
|
|
if (e.inner is WebSocketException) {
|
|
|
|
message = (e.inner as WebSocketException).message;
|
|
|
|
} else {
|
|
|
|
message = e.message ?? e.toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
log('Error connecting to ${event.uri}: $message');
|
|
|
|
emit(ConnectionError(message, event.uri));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
channel.stream.listen(
|
2024-12-15 00:52:28 +01:00
|
|
|
(event) {
|
|
|
|
final jsonData = jsonDecode(event as String);
|
|
|
|
if (jsonData is Map) {
|
|
|
|
switch (jsonData['type']) {
|
|
|
|
case 'initial_state':
|
|
|
|
playerStateBloc.add(
|
|
|
|
InitialPlayerState.fromJson(jsonData['value']),
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
case 'event':
|
|
|
|
final event = parseEvent(jsonData['value']);
|
|
|
|
if (event == null) {
|
|
|
|
log('Unknown event: ${jsonData['value']}');
|
|
|
|
} else {
|
|
|
|
log('Handling event: $event');
|
|
|
|
playerStateBloc.add(event);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
log('Unknown message type: ${jsonData['type']}');
|
|
|
|
log('Message: $jsonData');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2024-12-18 20:12:14 +01:00
|
|
|
onError: (error, stackTrace) {
|
2024-12-15 00:52:28 +01:00
|
|
|
log('Error: $error');
|
2024-12-18 20:12:14 +01:00
|
|
|
log('Stack trace: $stackTrace');
|
2024-12-15 00:52:28 +01:00
|
|
|
},
|
|
|
|
onDone: () {
|
|
|
|
add(Disconnect());
|
|
|
|
log('Connection closed, reconnecting...');
|
2024-12-18 20:12:14 +01:00
|
|
|
add(Connect(event.uri));
|
2024-12-15 00:52:28 +01:00
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
emit(Connected(event.uri, channel));
|
2024-12-15 00:52:28 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
on<Disconnect>((event, emit) {
|
2024-12-18 20:12:14 +01:00
|
|
|
if (state is! Connected) {
|
|
|
|
log('Cannot disconnect when not connected');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
(state as Connected).channel.sink.close();
|
2024-12-15 00:52:28 +01:00
|
|
|
playerStateBloc.add(const ClearPlayerState());
|
2024-12-18 20:12:14 +01:00
|
|
|
emit(Disconnected());
|
2024-12-15 00:52:28 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
on<Command>((event, emit) {
|
2024-12-18 20:12:14 +01:00
|
|
|
if (state is! Connected) {
|
2024-12-15 00:52:28 +01:00
|
|
|
log('Cannot send command when not connected');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-12-18 20:12:14 +01:00
|
|
|
(state as Connected).channel.sink.add(event.toJsonString());
|
2024-12-15 00:52:28 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|