Multiple changes:

- Split up UI into several files
- Handle connection state better
- Add images
- Granular rebuilding of state dependent widgets
- Fix usage of alert dialogs
- Add some basic theming
- Add dialog for adding multiple links at once
This commit is contained in:
2024-12-18 20:12:14 +01:00
parent 540b504c2e
commit 6b82280c14
15 changed files with 719 additions and 525 deletions

View File

@@ -1,40 +1,83 @@
import 'dart:convert';
import 'dart:developer' show log;
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../api/commands.dart';
import '../api/events.dart';
import 'player_state_bloc.dart';
import 'package:gergle/api/commands.dart';
import 'package:gergle/api/events.dart';
import 'package:gergle/state/player_state_bloc.dart';
class ConnectionStateBloc extends Bloc<ConnectionEvent, WebSocketChannel?> {
@immutable
sealed class PlayerConnectionState {}
@immutable
class Disconnected extends PlayerConnectionState {}
@immutable
class Connecting extends PlayerConnectionState {
final String uri;
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);
}
class ConnectionStateBloc extends Bloc<PlayerConnectionEvent, PlayerConnectionState> {
final PlayerStateBloc playerStateBloc;
String? _uri;
WebSocketChannel? _channel;
ConnectionStateBloc(this.playerStateBloc) : super(null) {
on<Connect>((event, emit) {
if (_channel != null && _uri == event.uri) {
log('Already connected to ${event.uri}');
return;
} else if (_channel != null) {
// Clear connection, and reconnect
state?.sink.close();
playerStateBloc.add(const ClearPlayerState());
emit(null);
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());
}
}
_uri = event.uri;
emit(Connecting(event.uri));
_channel = WebSocketChannel.connect(
final channel = WebSocketChannel.connect(
Uri.parse(event.uri),
);
_channel!.stream.listen(
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(
(event) {
final jsonData = jsonDecode(event as String);
if (jsonData is Map) {
@@ -60,33 +103,37 @@ class ConnectionStateBloc extends Bloc<ConnectionEvent, WebSocketChannel?> {
}
}
},
onError: (error) {
onError: (error, stackTrace) {
log('Error: $error');
log('Stack trace: $stackTrace');
},
onDone: () {
add(Disconnect());
log('Connection closed, reconnecting...');
add(Connect(_uri!));
add(Connect(event.uri));
},
);
emit(_channel);
emit(Connected(event.uri, channel));
});
on<Disconnect>((event, emit) {
_uri = null;
state?.sink.close(0, 'Disconnecting');
if (state is! Connected) {
log('Cannot disconnect when not connected');
return;
}
(state as Connected).channel.sink.close();
playerStateBloc.add(const ClearPlayerState());
emit(null);
emit(Disconnected());
});
on<Command>((event, emit) {
if (_channel == null) {
if (state is! Connected) {
log('Cannot send command when not connected');
return;
}
_channel!.sink.add(event.toJsonString());
(state as Connected).channel.sink.add(event.toJsonString());
});
}
}

View File

@@ -2,8 +2,8 @@ import 'dart:developer' show log;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'player_state.dart';
import '../api/events.dart';
import 'package:gergle/api/events.dart';
import 'package:gergle/state/player_state.dart';
class PlayerStateBloc extends Bloc<Event, PlayerState?> {
PlayerStateBloc() : super(null) {