✨ Chat room details, invitions and members management
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/user.dart';
|
||||
@ -21,6 +23,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
||||
final user = SnAccount.fromJson(response.data);
|
||||
state = AsyncValue.data(user);
|
||||
} catch (error, stackTrace) {
|
||||
log("[UserInfo] Failed to fetch user info: $error");
|
||||
state = AsyncValue.error(error, stackTrace);
|
||||
}
|
||||
}
|
||||
@ -29,6 +32,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
||||
state = const AsyncValue.data(null);
|
||||
final prefs = _ref.read(sharedPreferencesProvider);
|
||||
await prefs.remove(kTokenPairStoreKey);
|
||||
_ref.refresh(userInfoProvider.notifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@ -8,6 +11,7 @@ import 'package:web_socket_channel/io.dart';
|
||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||
|
||||
part 'websocket.freezed.dart';
|
||||
part 'websocket.g.dart';
|
||||
|
||||
@freezed
|
||||
class WebSocketState with _$WebSocketState {
|
||||
@ -17,15 +21,35 @@ class WebSocketState with _$WebSocketState {
|
||||
const factory WebSocketState.error(String message) = _Error;
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class WebSocketPacket with _$WebSocketPacket {
|
||||
const factory WebSocketPacket({
|
||||
required String type,
|
||||
required Map<String, dynamic>? data,
|
||||
required String? errorMessage,
|
||||
}) = _WebSocketPacket;
|
||||
|
||||
factory WebSocketPacket.fromJson(Map<String, dynamic> json) =>
|
||||
_$WebSocketPacketFromJson(json);
|
||||
}
|
||||
|
||||
final websocketProvider = Provider<WebSocketService>((ref) {
|
||||
return WebSocketService();
|
||||
});
|
||||
|
||||
class WebSocketService {
|
||||
WebSocketChannel? _channel;
|
||||
Stream<dynamic>? _broadcastStream;
|
||||
final StreamController<WebSocketPacket> _streamController =
|
||||
StreamController<WebSocketPacket>.broadcast();
|
||||
String? _lastUrl;
|
||||
String? _lastAtk;
|
||||
Timer? _reconnectTimer;
|
||||
|
||||
Stream<WebSocketPacket> get dataStream => _streamController.stream;
|
||||
|
||||
Future<void> connect(String url, String atk) async {
|
||||
_lastUrl = url;
|
||||
_lastAtk = atk;
|
||||
log('[WebSocket] Trying connecting to $url');
|
||||
try {
|
||||
_channel = IOWebSocketChannel.connect(
|
||||
@ -33,20 +57,48 @@ class WebSocketService {
|
||||
headers: {'Authorization': 'Bearer $atk'},
|
||||
);
|
||||
await _channel!.ready;
|
||||
_broadcastStream = _channel!.stream.asBroadcastStream();
|
||||
_channel!.stream.listen(
|
||||
(data) {
|
||||
final dataStr =
|
||||
data is Uint8List ? utf8.decode(data) : data.toString();
|
||||
final packet = WebSocketPacket.fromJson(jsonDecode(dataStr));
|
||||
_streamController.sink.add(packet);
|
||||
log("[WebSocket] Received packet: ${packet.type}");
|
||||
},
|
||||
onDone: () {
|
||||
log('[WebSocket] Connection closed, attempting to reconnect...');
|
||||
_scheduleReconnect();
|
||||
},
|
||||
onError: (error) {
|
||||
log('[WebSocket] Error occurred: $error, attempting to reconnect...');
|
||||
_scheduleReconnect();
|
||||
},
|
||||
);
|
||||
} catch (err) {
|
||||
log('[WebSocket] Failed to connect: $err');
|
||||
_scheduleReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleReconnect() {
|
||||
_reconnectTimer?.cancel();
|
||||
_reconnectTimer = Timer(const Duration(milliseconds: 500), () {
|
||||
if (_lastUrl != null && _lastAtk != null) {
|
||||
connect(_lastUrl!, _lastAtk!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
WebSocketChannel? get ws => _channel;
|
||||
Stream<dynamic> get stream => _broadcastStream!;
|
||||
|
||||
void sendMessage(String message) {
|
||||
_channel!.sink.add(message);
|
||||
}
|
||||
|
||||
void close() {
|
||||
_reconnectTimer?.cancel();
|
||||
_lastUrl = null;
|
||||
_lastAtk = null;
|
||||
_channel?.sink.close();
|
||||
}
|
||||
}
|
||||
|
@ -202,6 +202,153 @@ as String,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
mixin _$WebSocketPacket {
|
||||
|
||||
String get type; Map<String, dynamic>? get data; String? get errorMessage;
|
||||
/// Create a copy of WebSocketPacket
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$WebSocketPacketCopyWith<WebSocketPacket> get copyWith => _$WebSocketPacketCopyWithImpl<WebSocketPacket>(this as WebSocketPacket, _$identity);
|
||||
|
||||
/// Serializes this WebSocketPacket to a JSON map.
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is WebSocketPacket&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(data),errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WebSocketPacket(type: $type, data: $data, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $WebSocketPacketCopyWith<$Res> {
|
||||
factory $WebSocketPacketCopyWith(WebSocketPacket value, $Res Function(WebSocketPacket) _then) = _$WebSocketPacketCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String type, Map<String, dynamic>? data, String? errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$WebSocketPacketCopyWithImpl<$Res>
|
||||
implements $WebSocketPacketCopyWith<$Res> {
|
||||
_$WebSocketPacketCopyWithImpl(this._self, this._then);
|
||||
|
||||
final WebSocketPacket _self;
|
||||
final $Res Function(WebSocketPacket) _then;
|
||||
|
||||
/// Create a copy of WebSocketPacket
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? data = freezed,Object? errorMessage = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
|
||||
class _WebSocketPacket implements WebSocketPacket {
|
||||
const _WebSocketPacket({required this.type, required final Map<String, dynamic>? data, required this.errorMessage}): _data = data;
|
||||
factory _WebSocketPacket.fromJson(Map<String, dynamic> json) => _$WebSocketPacketFromJson(json);
|
||||
|
||||
@override final String type;
|
||||
final Map<String, dynamic>? _data;
|
||||
@override Map<String, dynamic>? get data {
|
||||
final value = _data;
|
||||
if (value == null) return null;
|
||||
if (_data is EqualUnmodifiableMapView) return _data;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(value);
|
||||
}
|
||||
|
||||
@override final String? errorMessage;
|
||||
|
||||
/// Create a copy of WebSocketPacket
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$WebSocketPacketCopyWith<_WebSocketPacket> get copyWith => __$WebSocketPacketCopyWithImpl<_WebSocketPacket>(this, _$identity);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$WebSocketPacketToJson(this, );
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _WebSocketPacket&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._data, _data)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_data),errorMessage);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'WebSocketPacket(type: $type, data: $data, errorMessage: $errorMessage)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$WebSocketPacketCopyWith<$Res> implements $WebSocketPacketCopyWith<$Res> {
|
||||
factory _$WebSocketPacketCopyWith(_WebSocketPacket value, $Res Function(_WebSocketPacket) _then) = __$WebSocketPacketCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String type, Map<String, dynamic>? data, String? errorMessage
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$WebSocketPacketCopyWithImpl<$Res>
|
||||
implements _$WebSocketPacketCopyWith<$Res> {
|
||||
__$WebSocketPacketCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _WebSocketPacket _self;
|
||||
final $Res Function(_WebSocketPacket) _then;
|
||||
|
||||
/// Create a copy of WebSocketPacket
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? data = freezed,Object? errorMessage = freezed,}) {
|
||||
return _then(_WebSocketPacket(
|
||||
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as String,data: freezed == data ? _self._data : data // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, dynamic>?,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable
|
||||
as String?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
|
21
lib/pods/websocket.g.dart
Normal file
21
lib/pods/websocket.g.dart
Normal file
@ -0,0 +1,21 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'websocket.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_WebSocketPacket _$WebSocketPacketFromJson(Map<String, dynamic> json) =>
|
||||
_WebSocketPacket(
|
||||
type: json['type'] as String,
|
||||
data: json['data'] as Map<String, dynamic>?,
|
||||
errorMessage: json['error_message'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$WebSocketPacketToJson(_WebSocketPacket instance) =>
|
||||
<String, dynamic>{
|
||||
'type': instance.type,
|
||||
'data': instance.data,
|
||||
'error_message': instance.errorMessage,
|
||||
};
|
Reference in New Issue
Block a user