2024-08-17 16:51:47 -04:00
|
|
|
import 'dart:async';
|
2024-10-21 21:49:43 -04:00
|
|
|
import 'dart:io';
|
2024-08-15 08:52:14 -04:00
|
|
|
import 'package:flutter/foundation.dart';
|
2024-08-13 23:07:35 -04:00
|
|
|
import 'package:http/http.dart' as http;
|
2024-10-21 21:49:43 -04:00
|
|
|
import 'package:just_audio/just_audio.dart';
|
|
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
2024-08-10 16:38:46 -04:00
|
|
|
import '../pb/stillbox.pb.dart';
|
2024-08-15 10:12:26 -04:00
|
|
|
import 'stillbox_none.dart'
|
|
|
|
if (dart.library.io) 'stillbox_io.dart'
|
|
|
|
if (dart.library.html) 'stillbox_web.dart';
|
2024-08-10 14:36:08 -04:00
|
|
|
|
2024-08-17 12:04:42 -04:00
|
|
|
import 'talkgroups.dart';
|
|
|
|
|
2024-08-13 21:03:42 -04:00
|
|
|
class BadAuthException implements Exception {}
|
|
|
|
|
2024-08-13 20:04:37 -04:00
|
|
|
class Stillbox extends ChangeNotifier {
|
2024-08-15 08:52:14 -04:00
|
|
|
final storage = Storer();
|
2024-08-15 10:12:26 -04:00
|
|
|
final channel = Socketer();
|
2024-08-17 12:04:42 -04:00
|
|
|
late TalkgroupCache tgCache;
|
2024-11-08 15:33:53 -05:00
|
|
|
late ServerInfo version;
|
2024-08-13 20:04:37 -04:00
|
|
|
bool connected = false;
|
|
|
|
late Uri _wsUri;
|
|
|
|
LiveState _state = LiveState.LS_LIVE;
|
|
|
|
Filter? currentFilter;
|
2024-08-17 12:04:42 -04:00
|
|
|
SBCall? _currentCall;
|
2024-08-13 23:07:35 -04:00
|
|
|
Uri? baseUri = Uri.base;
|
2024-08-17 21:53:19 -04:00
|
|
|
int queueLen = 0;
|
|
|
|
final StreamController<SBCall> callStream = StreamController<SBCall>();
|
2024-08-18 09:17:10 -04:00
|
|
|
final StreamController<int> callQStream = StreamController<int>();
|
2024-08-13 21:03:42 -04:00
|
|
|
|
2024-08-13 20:04:37 -04:00
|
|
|
set state(LiveState newState) {
|
|
|
|
channel.sink.add(Live(state: newState, filter: currentFilter));
|
|
|
|
_state = newState;
|
|
|
|
notifyListeners();
|
|
|
|
}
|
|
|
|
|
|
|
|
LiveState get state {
|
|
|
|
return _state;
|
|
|
|
}
|
2024-08-10 14:36:08 -04:00
|
|
|
|
2024-08-17 12:04:42 -04:00
|
|
|
SBCall? get currentCall => _currentCall;
|
2024-08-13 20:04:37 -04:00
|
|
|
|
|
|
|
Stillbox() {
|
2024-08-13 23:07:35 -04:00
|
|
|
setUris();
|
|
|
|
}
|
|
|
|
|
|
|
|
void setUris() {
|
2024-08-14 08:28:26 -04:00
|
|
|
if (baseUri != null &&
|
|
|
|
(baseUri!.scheme == 'http' || baseUri!.scheme == 'https')) {
|
2024-08-13 23:07:35 -04:00
|
|
|
String socketUrl;
|
|
|
|
final port = (baseUri!.hasPort ? ':${baseUri!.port}' : '');
|
2024-08-12 09:13:43 -04:00
|
|
|
socketUrl =
|
2024-11-08 15:33:53 -05:00
|
|
|
'${baseUri!.scheme == 'http' ? 'ws' : 'wss'}://${baseUri!.host}$port/api/ws';
|
2024-08-13 23:07:35 -04:00
|
|
|
_wsUri = Uri.parse(socketUrl);
|
|
|
|
} else {
|
|
|
|
baseUri = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<bool> doLogin(String uri, String username, String password) async {
|
2024-08-15 08:52:14 -04:00
|
|
|
baseUri = Uri.parse(uri);
|
|
|
|
setUris();
|
2024-08-14 10:01:55 -04:00
|
|
|
String baseUriString = baseUri.toString();
|
|
|
|
// trim trailing slash since gordio router really dislikes it
|
|
|
|
if (baseUriString.endsWith('/')) {
|
|
|
|
baseUriString = baseUriString.substring(0, baseUriString.length - 1);
|
|
|
|
}
|
2024-11-08 15:33:53 -05:00
|
|
|
Uri loginUri = Uri.parse('$baseUriString/api/login');
|
2024-08-13 23:07:35 -04:00
|
|
|
final form = <String, dynamic>{};
|
|
|
|
form['username'] = username;
|
|
|
|
form['password'] = password;
|
|
|
|
http.Response response = await http.post(
|
|
|
|
loginUri,
|
|
|
|
body: form,
|
|
|
|
);
|
2024-08-14 08:28:26 -04:00
|
|
|
if (response.statusCode == 200) {
|
2024-08-15 10:12:26 -04:00
|
|
|
String? token = channel.updateCookie(response);
|
2024-08-15 08:52:14 -04:00
|
|
|
storage.setKey('baseURL', uri);
|
2024-08-15 10:12:26 -04:00
|
|
|
if (!kIsWeb && token != null) {
|
|
|
|
storage.setKey('token', token);
|
2024-08-15 08:52:14 -04:00
|
|
|
}
|
2024-10-21 21:49:43 -04:00
|
|
|
try {
|
|
|
|
await connect();
|
2024-10-24 20:00:19 -04:00
|
|
|
} on WebSocketChannelException catch (e) {
|
2024-10-21 21:49:43 -04:00
|
|
|
return false;
|
|
|
|
}
|
2024-10-24 20:00:19 -04:00
|
|
|
|
|
|
|
return true;
|
2024-08-13 23:07:35 -04:00
|
|
|
}
|
|
|
|
return false;
|
2024-08-10 16:38:46 -04:00
|
|
|
}
|
2024-08-10 14:36:08 -04:00
|
|
|
|
2024-08-13 21:03:42 -04:00
|
|
|
Future<void> connect() async {
|
2024-08-14 08:28:26 -04:00
|
|
|
if (connected == true) {
|
2024-08-13 23:07:35 -04:00
|
|
|
return;
|
|
|
|
}
|
2024-10-18 09:10:44 -04:00
|
|
|
channel.connect(_wsUri);
|
2024-10-21 21:49:43 -04:00
|
|
|
try {
|
|
|
|
await channel.channel?.ready;
|
2024-10-24 20:00:19 -04:00
|
|
|
} on WebSocketChannelException catch (e) {
|
2024-10-21 21:49:43 -04:00
|
|
|
_handleError(e);
|
|
|
|
}
|
2024-09-29 15:34:53 -04:00
|
|
|
channel.stream.listen((event) => _handleData(event),
|
|
|
|
onDone: () {
|
|
|
|
connected = false;
|
|
|
|
},
|
|
|
|
onError: (error) => _handleError(error));
|
2024-08-13 20:04:37 -04:00
|
|
|
connected = true;
|
2024-08-17 12:04:42 -04:00
|
|
|
tgCache = TalkgroupCache(channel);
|
2024-08-13 20:04:37 -04:00
|
|
|
notifyListeners();
|
2024-08-10 14:36:08 -04:00
|
|
|
}
|
|
|
|
|
2024-08-14 08:28:26 -04:00
|
|
|
Future<void> getBearer() async {
|
2024-08-15 08:52:14 -04:00
|
|
|
String? storedToken = await storage.getKey('token');
|
|
|
|
late String? storedUri;
|
|
|
|
storedUri = await storage.getKey('baseURL');
|
2024-08-15 13:57:26 -04:00
|
|
|
if ((!kIsWeb && storedToken == null) || storedUri == null) {
|
2024-08-13 21:03:42 -04:00
|
|
|
throw (BadAuthException);
|
|
|
|
}
|
2024-08-15 13:57:26 -04:00
|
|
|
if (!kIsWeb) {
|
|
|
|
channel.setCookie(storedToken!);
|
|
|
|
}
|
2024-08-14 08:28:26 -04:00
|
|
|
baseUri = Uri.parse(storedUri);
|
|
|
|
setUris();
|
2024-08-13 21:03:42 -04:00
|
|
|
}
|
|
|
|
|
2024-08-17 21:53:19 -04:00
|
|
|
void dispatchCall(SBCall call) {
|
|
|
|
callStream.add(call);
|
2024-08-18 09:17:10 -04:00
|
|
|
callQStream.add(1);
|
2024-08-17 21:53:19 -04:00
|
|
|
}
|
|
|
|
|
2024-09-29 15:34:53 -04:00
|
|
|
void _handleError(dynamic error) {
|
|
|
|
connected = false;
|
|
|
|
print(error);
|
|
|
|
callStream.addError(error);
|
|
|
|
}
|
|
|
|
|
2024-08-10 14:36:08 -04:00
|
|
|
void _handleData(dynamic event) {
|
2024-08-10 16:38:46 -04:00
|
|
|
final msg = Message.fromBuffer(event);
|
|
|
|
switch (msg.whichToClientMessage()) {
|
|
|
|
case Message_ToClientMessage.call:
|
2024-08-17 21:53:19 -04:00
|
|
|
dispatchCall(SBCall(
|
2024-08-17 16:51:47 -04:00
|
|
|
msg.call, tgCache.getTg(msg.call.system, msg.call.talkgroup)));
|
2024-08-10 16:38:46 -04:00
|
|
|
case Message_ToClientMessage.notification:
|
|
|
|
case Message_ToClientMessage.popup:
|
|
|
|
case Message_ToClientMessage.error:
|
2024-10-18 10:00:55 -04:00
|
|
|
case Message_ToClientMessage.hello:
|
2024-11-08 15:33:53 -05:00
|
|
|
version = msg.hello.serverInfo;
|
2024-10-19 23:26:55 -04:00
|
|
|
case Message_ToClientMessage.response:
|
2024-10-20 08:41:05 -04:00
|
|
|
_handleCommandResponse(msg.response);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void _handleCommandResponse(CommandResponse response) {
|
|
|
|
switch (response.whichCommandResponse()) {
|
|
|
|
case CommandResponse_CommandResponse.tgInfo:
|
2024-10-19 23:26:55 -04:00
|
|
|
tgCache.handleTgInfo(response.tgInfo);
|
2024-08-10 17:07:04 -04:00
|
|
|
default:
|
2024-08-10 16:38:46 -04:00
|
|
|
}
|
2024-08-10 14:36:08 -04:00
|
|
|
}
|
|
|
|
}
|
2024-08-17 12:04:42 -04:00
|
|
|
|
|
|
|
class SBCall {
|
|
|
|
Call call;
|
|
|
|
Future<TalkgroupInfo> tg;
|
|
|
|
|
|
|
|
SBCall(this.call, this.tg);
|
|
|
|
}
|