2024-03-12 20:41:04 +00:00
|
|
|
import 'dart:async';
|
|
|
|
import 'dart:convert';
|
2024-11-05 21:24:03 +00:00
|
|
|
import 'package:anyhow/anyhow.dart';
|
2024-03-12 20:41:04 +00:00
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
|
|
import 'package:ot_viewer_app/owntracks_api.dart';
|
|
|
|
import 'package:ot_viewer_app/settings_page.dart';
|
|
|
|
import 'package:ws/ws.dart';
|
|
|
|
import 'package:rust_core/option.dart';
|
|
|
|
|
|
|
|
// Define the state. For simplicity, we're just using Map<String, dynamic> directly.
|
|
|
|
// You might want to define a more specific state class based on your application's needs.
|
|
|
|
abstract class LocationUpdateState {}
|
|
|
|
|
|
|
|
class LocationUpdateUnconnected extends LocationUpdateState {}
|
|
|
|
|
|
|
|
class LocationUpdateConnected extends LocationUpdateState {}
|
|
|
|
|
|
|
|
class LocationUpdateReceived extends LocationUpdateState {
|
|
|
|
Point position;
|
|
|
|
(String, String) deviceId;
|
|
|
|
|
|
|
|
LocationUpdateReceived(this.position, this.deviceId);
|
|
|
|
}
|
|
|
|
|
|
|
|
class LocationSubscribeCubit extends Cubit<LocationUpdateState> {
|
|
|
|
Option<WebSocketClient> _wsClient = None;
|
2024-11-05 21:24:03 +00:00
|
|
|
Option<Completer<void>> _connectionCompleter = None;
|
2024-03-14 19:50:30 +00:00
|
|
|
String url = '';
|
|
|
|
String username = '';
|
|
|
|
String pass = '';
|
2024-03-12 20:41:04 +00:00
|
|
|
|
|
|
|
LocationSubscribeCubit() : super(LocationUpdateUnconnected());
|
|
|
|
|
2024-11-05 21:24:03 +00:00
|
|
|
// TODO: handle ongoing connection attempt by canceling? the loop? or smth
|
2024-03-12 20:41:04 +00:00
|
|
|
subscribe(SettingsState settings) async {
|
2024-03-14 19:50:30 +00:00
|
|
|
// check if resubscribe is necessary (different URL)
|
2024-11-05 21:24:03 +00:00
|
|
|
if (settings.url == url &&
|
|
|
|
settings.username == username &&
|
|
|
|
settings.password == pass) {
|
2024-03-14 19:50:30 +00:00
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
url = settings.url;
|
|
|
|
username = settings.username;
|
|
|
|
pass = settings.password;
|
|
|
|
}
|
2024-11-05 21:24:03 +00:00
|
|
|
|
|
|
|
await _wsConnectionEstablish();
|
|
|
|
}
|
|
|
|
|
|
|
|
reconnect() async {
|
|
|
|
print("reconnecting...");
|
|
|
|
if (_wsClient.isSome()) {
|
|
|
|
await _wsClient.unwrap().close();
|
|
|
|
_wsClient = None;
|
|
|
|
} else {
|
|
|
|
print("not connected, not reconnecting");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-03-14 19:50:30 +00:00
|
|
|
await _wsConnectionEstablish();
|
|
|
|
}
|
2024-03-12 20:41:04 +00:00
|
|
|
|
2024-03-14 19:50:30 +00:00
|
|
|
Future<void> _wsConnectionEstablish() async {
|
2024-11-05 21:24:03 +00:00
|
|
|
// If there's an ongoing connection attempt, wait for it
|
|
|
|
if (_connectionCompleter.isSome()) {
|
|
|
|
await _connectionCompleter.unwrap().future;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start new connection attempt
|
|
|
|
_connectionCompleter = Some(Completer<void>());
|
|
|
|
|
2024-03-14 19:50:30 +00:00
|
|
|
if (_wsClient.isSome()) {
|
2024-03-12 20:41:04 +00:00
|
|
|
await _wsClient.unwrap().close();
|
|
|
|
_wsClient = None;
|
|
|
|
}
|
2024-11-05 21:24:03 +00:00
|
|
|
|
|
|
|
Result<WebSocketClient> ws = bail('Not done yet');
|
|
|
|
|
|
|
|
while (!ws.isOk()) {
|
|
|
|
ws = await OwntracksApi(baseUrl: url, username: username, pass: pass)
|
|
|
|
.createWebSocketConnection(
|
|
|
|
wsPath: 'last',
|
|
|
|
onMessage: (msg) {
|
|
|
|
if (msg is String) {
|
|
|
|
if (msg == 'LAST') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
final Map<String, dynamic> map = jsonDecode(msg);
|
|
|
|
|
|
|
|
if (map['_type'] == 'location') {
|
|
|
|
// filter points (only the ones for this device pls!)
|
|
|
|
final topic = (map['topic'] as String?)?.split('/');
|
|
|
|
if (topic == null || topic.length < 3) {
|
|
|
|
// couldn't reconstruct ID, bail
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// build device_id
|
|
|
|
final deviceId = (topic[1], topic[2]);
|
|
|
|
|
|
|
|
print(map);
|
|
|
|
|
|
|
|
// build point
|
|
|
|
final p = Point(
|
|
|
|
lat: map['lat'] as double,
|
|
|
|
lon: map['lon'] as double,
|
|
|
|
timestamp: DateTime.fromMillisecondsSinceEpoch(
|
|
|
|
(map['tst'] as int) * 1000));
|
|
|
|
|
|
|
|
print(p);
|
|
|
|
|
|
|
|
emit(LocationUpdateReceived(p, deviceId));
|
2024-03-12 20:41:04 +00:00
|
|
|
}
|
2024-11-05 21:24:03 +00:00
|
|
|
} catch (e) {
|
|
|
|
print('BUG: Couldn\'t parse WS message: $msg ($e)');
|
2024-03-12 20:41:04 +00:00
|
|
|
}
|
|
|
|
}
|
2024-11-05 21:24:03 +00:00
|
|
|
},
|
|
|
|
onStateChange: (sc) {
|
|
|
|
switch (sc) {
|
|
|
|
case WebSocketClientState$Open(:final url):
|
|
|
|
_wsClient.map((wsc) => wsc.add('LAST'));
|
|
|
|
emit(LocationUpdateConnected());
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
emit(LocationUpdateUnconnected());
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
print(sc);
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!ws.isOk()) {
|
|
|
|
print(
|
|
|
|
'Failed to connect to WebSocket: ${ws.unwrapErr()}\n, retrying in 1s');
|
|
|
|
await Future.delayed(const Duration(seconds: 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
_wsClient =
|
|
|
|
ws.expect("Estabilshing Websocket Conenction failed").toOption();
|
|
|
|
|
|
|
|
_connectionCompleter.unwrap().complete();
|
|
|
|
_connectionCompleter = None;
|
2024-03-12 20:41:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void onChange(Change<LocationUpdateState> change) {
|
|
|
|
print('loc_sub_cubit change: $change');
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Future<void> close() async {
|
|
|
|
await _wsClient.toFutureOption().map((conn) => conn.close());
|
|
|
|
return super.close();
|
|
|
|
}
|
|
|
|
}
|