TG futures

This commit is contained in:
Daniel 2024-08-17 12:04:42 -04:00
parent d87b497e30
commit c77b178f85
12 changed files with 779 additions and 51 deletions

View file

@ -1,5 +1,5 @@
proto:
protoc -I protobuf/ --dart_out=lib/pb protobuf/stillbox.proto google/protobuf/timestamp.proto
protoc -I protobuf/ --dart_out=lib/pb protobuf/stillbox.proto google/protobuf/timestamp.proto google/protobuf/struct.proto
patch:
dart run patch_package apply

View file

@ -10,6 +10,7 @@ import '../pb/stillbox.pb.dart';
abstract class AudioDriver {
Future<void> play(Call call);
Stream<justaudio.PlayerState> get playerStateStream;
}
class Player {
@ -47,6 +48,10 @@ class JustAudioDriver implements AudioDriver {
initializer.audioInit();
}
Stream<justaudio.PlayerState> get playerStateStream {
return player.playerStateStream;
}
@override
Future<void> play(Call call) async {
player.setAudioSource(CallBytesSource(call));

View file

@ -1,22 +1,26 @@
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:just_audio/just_audio.dart';
import '../pb/stillbox.pb.dart';
import 'play.dart';
import 'stillbox_none.dart'
if (dart.library.io) 'stillbox_io.dart'
if (dart.library.html) 'stillbox_web.dart';
import 'talkgroups.dart';
class BadAuthException implements Exception {}
class Stillbox extends ChangeNotifier {
final storage = Storer();
Player player = Player();
final channel = Socketer();
late TalkgroupCache tgCache;
bool connected = false;
late Uri _wsUri;
LiveState _state = LiveState.LS_LIVE;
Filter? currentFilter;
Call? _currentCall;
SBCall? _currentCall;
Uri? baseUri = Uri.base;
set state(LiveState newState) {
@ -29,14 +33,23 @@ class Stillbox extends ChangeNotifier {
return _state;
}
Call? get currentCall => _currentCall;
set currentCall(Call? call) {
SBCall? get currentCall => _currentCall;
set currentCall(SBCall? call) {
_currentCall = call;
if (_currentCall != null) {
player.play(_currentCall!.call);
}
notifyListeners();
}
Stillbox() {
setUris();
player.driver.playerStateStream.listen((event) async {
if ((!event.playing && _currentCall != null) &&
event.processingState == ProcessingState.completed) {
currentCall = null;
}
});
}
void setUris() {
@ -92,6 +105,7 @@ class Stillbox extends ChangeNotifier {
print(error);
});
connected = true;
tgCache = TalkgroupCache(channel);
notifyListeners();
}
@ -113,11 +127,21 @@ class Stillbox extends ChangeNotifier {
final msg = Message.fromBuffer(event);
switch (msg.whichToClientMessage()) {
case Message_ToClientMessage.call:
player.play(msg.call);
currentCall = SBCall(
msg.call, tgCache.getTg(msg.call.system, msg.call.talkgroup));
case Message_ToClientMessage.notification:
case Message_ToClientMessage.popup:
case Message_ToClientMessage.error:
case Message_ToClientMessage.tgInfo:
tgCache.handleTgInfo(msg.tgInfo);
default:
}
}
}
class SBCall {
Call call;
Future<TalkgroupInfo> tg;
SBCall(this.call, this.tg);
}

View file

@ -0,0 +1,31 @@
import 'dart:async';
import 'stillbox_none.dart'
if (dart.library.io) 'stillbox_io.dart'
if (dart.library.html) 'stillbox_web.dart';
import '../pb/stillbox.pb.dart';
class TalkgroupCache {
Socketer socketer;
final StreamController<TalkgroupInfo> tgiSt =
StreamController<TalkgroupInfo>.broadcast();
Map<Talkgroup, TalkgroupInfo> cache = {};
TalkgroupCache(this.socketer);
void handleTgInfo(TalkgroupInfo tgi) {
cache[tgi.tg] = tgi;
tgiSt.add(tgi);
}
Future<TalkgroupInfo> getTg(int system, int tgid) async {
final tg = Talkgroup(system: system, talkgroup: tgid);
if (cache[tg] != null) {
return cache[tg]!;
}
socketer.channel.sink.add(Command(tgCommand: tg).writeToBuffer());
return tgiSt.stream
.firstWhere((td) => td.tg.system == system && td.tg.talkgroup == tgid);
}
}

View file

@ -0,0 +1,283 @@
//
// Generated code. Do not modify.
// source: google/protobuf/struct.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
import 'package:protobuf/src/protobuf/mixins/well_known.dart' as $mixin;
import 'struct.pbenum.dart';
export 'struct.pbenum.dart';
/// `Struct` represents a structured data value, consisting of fields
/// which map to dynamically typed values. In some languages, `Struct`
/// might be supported by a native representation. For example, in
/// scripting languages like JS a struct is represented as an
/// object. The details of that representation are described together
/// with the proto support for the language.
///
/// The JSON representation for `Struct` is JSON object.
class Struct extends $pb.GeneratedMessage with $mixin.StructMixin {
factory Struct({
$core.Map<$core.String, Value>? fields,
}) {
final $result = create();
if (fields != null) {
$result.fields.addAll(fields);
}
return $result;
}
Struct._() : super();
factory Struct.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Struct.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Struct', package: const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), createEmptyInstance: create, toProto3Json: $mixin.StructMixin.toProto3JsonHelper, fromProto3Json: $mixin.StructMixin.fromProto3JsonHelper)
..m<$core.String, Value>(1, _omitFieldNames ? '' : 'fields', entryClassName: 'Struct.FieldsEntry', keyFieldType: $pb.PbFieldType.OS, valueFieldType: $pb.PbFieldType.OM, valueCreator: Value.create, valueDefaultOrMaker: Value.getDefault, packageName: const $pb.PackageName('google.protobuf'))
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
Struct clone() => Struct()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Struct copyWith(void Function(Struct) updates) => super.copyWith((message) => updates(message as Struct)) as Struct;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Struct create() => Struct._();
Struct createEmptyInstance() => create();
static $pb.PbList<Struct> createRepeated() => $pb.PbList<Struct>();
@$core.pragma('dart2js:noInline')
static Struct getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Struct>(create);
static Struct? _defaultInstance;
/// Unordered map of dynamically typed values.
@$pb.TagNumber(1)
$core.Map<$core.String, Value> get fields => $_getMap(0);
}
enum Value_Kind {
nullValue,
numberValue,
stringValue,
boolValue,
structValue,
listValue,
notSet
}
/// `Value` represents a dynamically typed value which can be either
/// null, a number, a string, a boolean, a recursive struct value, or a
/// list of values. A producer of value is expected to set one of these
/// variants. Absence of any variant indicates an error.
///
/// The JSON representation for `Value` is JSON value.
class Value extends $pb.GeneratedMessage with $mixin.ValueMixin {
factory Value({
NullValue? nullValue,
$core.double? numberValue,
$core.String? stringValue,
$core.bool? boolValue,
Struct? structValue,
ListValue? listValue,
}) {
final $result = create();
if (nullValue != null) {
$result.nullValue = nullValue;
}
if (numberValue != null) {
$result.numberValue = numberValue;
}
if (stringValue != null) {
$result.stringValue = stringValue;
}
if (boolValue != null) {
$result.boolValue = boolValue;
}
if (structValue != null) {
$result.structValue = structValue;
}
if (listValue != null) {
$result.listValue = listValue;
}
return $result;
}
Value._() : super();
factory Value.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory Value.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static const $core.Map<$core.int, Value_Kind> _Value_KindByTag = {
1 : Value_Kind.nullValue,
2 : Value_Kind.numberValue,
3 : Value_Kind.stringValue,
4 : Value_Kind.boolValue,
5 : Value_Kind.structValue,
6 : Value_Kind.listValue,
0 : Value_Kind.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Value', package: const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), createEmptyInstance: create, toProto3Json: $mixin.ValueMixin.toProto3JsonHelper, fromProto3Json: $mixin.ValueMixin.fromProto3JsonHelper)
..oo(0, [1, 2, 3, 4, 5, 6])
..e<NullValue>(1, _omitFieldNames ? '' : 'nullValue', $pb.PbFieldType.OE, defaultOrMaker: NullValue.NULL_VALUE, valueOf: NullValue.valueOf, enumValues: NullValue.values)
..a<$core.double>(2, _omitFieldNames ? '' : 'numberValue', $pb.PbFieldType.OD)
..aOS(3, _omitFieldNames ? '' : 'stringValue')
..aOB(4, _omitFieldNames ? '' : 'boolValue')
..aOM<Struct>(5, _omitFieldNames ? '' : 'structValue', subBuilder: Struct.create)
..aOM<ListValue>(6, _omitFieldNames ? '' : 'listValue', subBuilder: ListValue.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
Value clone() => Value()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
Value copyWith(void Function(Value) updates) => super.copyWith((message) => updates(message as Value)) as Value;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static Value create() => Value._();
Value createEmptyInstance() => create();
static $pb.PbList<Value> createRepeated() => $pb.PbList<Value>();
@$core.pragma('dart2js:noInline')
static Value getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Value>(create);
static Value? _defaultInstance;
Value_Kind whichKind() => _Value_KindByTag[$_whichOneof(0)]!;
void clearKind() => clearField($_whichOneof(0));
/// Represents a null value.
@$pb.TagNumber(1)
NullValue get nullValue => $_getN(0);
@$pb.TagNumber(1)
set nullValue(NullValue v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasNullValue() => $_has(0);
@$pb.TagNumber(1)
void clearNullValue() => clearField(1);
/// Represents a double value.
@$pb.TagNumber(2)
$core.double get numberValue => $_getN(1);
@$pb.TagNumber(2)
set numberValue($core.double v) { $_setDouble(1, v); }
@$pb.TagNumber(2)
$core.bool hasNumberValue() => $_has(1);
@$pb.TagNumber(2)
void clearNumberValue() => clearField(2);
/// Represents a string value.
@$pb.TagNumber(3)
$core.String get stringValue => $_getSZ(2);
@$pb.TagNumber(3)
set stringValue($core.String v) { $_setString(2, v); }
@$pb.TagNumber(3)
$core.bool hasStringValue() => $_has(2);
@$pb.TagNumber(3)
void clearStringValue() => clearField(3);
/// Represents a boolean value.
@$pb.TagNumber(4)
$core.bool get boolValue => $_getBF(3);
@$pb.TagNumber(4)
set boolValue($core.bool v) { $_setBool(3, v); }
@$pb.TagNumber(4)
$core.bool hasBoolValue() => $_has(3);
@$pb.TagNumber(4)
void clearBoolValue() => clearField(4);
/// Represents a structured value.
@$pb.TagNumber(5)
Struct get structValue => $_getN(4);
@$pb.TagNumber(5)
set structValue(Struct v) { setField(5, v); }
@$pb.TagNumber(5)
$core.bool hasStructValue() => $_has(4);
@$pb.TagNumber(5)
void clearStructValue() => clearField(5);
@$pb.TagNumber(5)
Struct ensureStructValue() => $_ensure(4);
/// Represents a repeated `Value`.
@$pb.TagNumber(6)
ListValue get listValue => $_getN(5);
@$pb.TagNumber(6)
set listValue(ListValue v) { setField(6, v); }
@$pb.TagNumber(6)
$core.bool hasListValue() => $_has(5);
@$pb.TagNumber(6)
void clearListValue() => clearField(6);
@$pb.TagNumber(6)
ListValue ensureListValue() => $_ensure(5);
}
/// `ListValue` is a wrapper around a repeated field of values.
///
/// The JSON representation for `ListValue` is JSON array.
class ListValue extends $pb.GeneratedMessage with $mixin.ListValueMixin {
factory ListValue({
$core.Iterable<Value>? values,
}) {
final $result = create();
if (values != null) {
$result.values.addAll(values);
}
return $result;
}
ListValue._() : super();
factory ListValue.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory ListValue.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'ListValue', package: const $pb.PackageName(_omitMessageNames ? '' : 'google.protobuf'), createEmptyInstance: create, toProto3Json: $mixin.ListValueMixin.toProto3JsonHelper, fromProto3Json: $mixin.ListValueMixin.fromProto3JsonHelper)
..pc<Value>(1, _omitFieldNames ? '' : 'values', $pb.PbFieldType.PM, subBuilder: Value.create)
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
ListValue clone() => ListValue()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
ListValue copyWith(void Function(ListValue) updates) => super.copyWith((message) => updates(message as ListValue)) as ListValue;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static ListValue create() => ListValue._();
ListValue createEmptyInstance() => create();
static $pb.PbList<ListValue> createRepeated() => $pb.PbList<ListValue>();
@$core.pragma('dart2js:noInline')
static ListValue getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ListValue>(create);
static ListValue? _defaultInstance;
/// Repeated field of dynamically typed values.
@$pb.TagNumber(1)
$core.List<Value> get values => $_getList(0);
}
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');

View file

@ -0,0 +1,34 @@
//
// Generated code. Do not modify.
// source: google/protobuf/struct.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
/// `NullValue` is a singleton enumeration to represent the null value for the
/// `Value` type union.
///
/// The JSON representation for `NullValue` is JSON `null`.
class NullValue extends $pb.ProtobufEnum {
static const NullValue NULL_VALUE = NullValue._(0, _omitEnumNames ? '' : 'NULL_VALUE');
static const $core.List<NullValue> values = <NullValue> [
NULL_VALUE,
];
static final $core.Map<$core.int, NullValue> _byValue = $pb.ProtobufEnum.initByValue(values);
static NullValue? valueOf($core.int value) => _byValue[value];
const NullValue._($core.int v, $core.String n) : super(v, n);
}
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');

View file

@ -0,0 +1,90 @@
//
// Generated code. Do not modify.
// source: google/protobuf/struct.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use nullValueDescriptor instead')
const NullValue$json = {
'1': 'NullValue',
'2': [
{'1': 'NULL_VALUE', '2': 0},
],
};
/// Descriptor for `NullValue`. Decode as a `google.protobuf.EnumDescriptorProto`.
final $typed_data.Uint8List nullValueDescriptor = $convert.base64Decode(
'CglOdWxsVmFsdWUSDgoKTlVMTF9WQUxVRRAA');
@$core.Deprecated('Use structDescriptor instead')
const Struct$json = {
'1': 'Struct',
'2': [
{'1': 'fields', '3': 1, '4': 3, '5': 11, '6': '.google.protobuf.Struct.FieldsEntry', '10': 'fields'},
],
'3': [Struct_FieldsEntry$json],
};
@$core.Deprecated('Use structDescriptor instead')
const Struct_FieldsEntry$json = {
'1': 'FieldsEntry',
'2': [
{'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
{'1': 'value', '3': 2, '4': 1, '5': 11, '6': '.google.protobuf.Value', '10': 'value'},
],
'7': {'7': true},
};
/// Descriptor for `Struct`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List structDescriptor = $convert.base64Decode(
'CgZTdHJ1Y3QSOwoGZmllbGRzGAEgAygLMiMuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdC5GaWVsZH'
'NFbnRyeVIGZmllbGRzGlEKC0ZpZWxkc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EiwKBXZhbHVl'
'GAIgASgLMhYuZ29vZ2xlLnByb3RvYnVmLlZhbHVlUgV2YWx1ZToCOAE=');
@$core.Deprecated('Use valueDescriptor instead')
const Value$json = {
'1': 'Value',
'2': [
{'1': 'null_value', '3': 1, '4': 1, '5': 14, '6': '.google.protobuf.NullValue', '9': 0, '10': 'nullValue'},
{'1': 'number_value', '3': 2, '4': 1, '5': 1, '9': 0, '10': 'numberValue'},
{'1': 'string_value', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'stringValue'},
{'1': 'bool_value', '3': 4, '4': 1, '5': 8, '9': 0, '10': 'boolValue'},
{'1': 'struct_value', '3': 5, '4': 1, '5': 11, '6': '.google.protobuf.Struct', '9': 0, '10': 'structValue'},
{'1': 'list_value', '3': 6, '4': 1, '5': 11, '6': '.google.protobuf.ListValue', '9': 0, '10': 'listValue'},
],
'8': [
{'1': 'kind'},
],
};
/// Descriptor for `Value`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List valueDescriptor = $convert.base64Decode(
'CgVWYWx1ZRI7CgpudWxsX3ZhbHVlGAEgASgOMhouZ29vZ2xlLnByb3RvYnVmLk51bGxWYWx1ZU'
'gAUgludWxsVmFsdWUSIwoMbnVtYmVyX3ZhbHVlGAIgASgBSABSC251bWJlclZhbHVlEiMKDHN0'
'cmluZ192YWx1ZRgDIAEoCUgAUgtzdHJpbmdWYWx1ZRIfCgpib29sX3ZhbHVlGAQgASgISABSCW'
'Jvb2xWYWx1ZRI8CgxzdHJ1Y3RfdmFsdWUYBSABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0'
'SABSC3N0cnVjdFZhbHVlEjsKCmxpc3RfdmFsdWUYBiABKAsyGi5nb29nbGUucHJvdG9idWYuTG'
'lzdFZhbHVlSABSCWxpc3RWYWx1ZUIGCgRraW5k');
@$core.Deprecated('Use listValueDescriptor instead')
const ListValue$json = {
'1': 'ListValue',
'2': [
{'1': 'values', '3': 1, '4': 3, '5': 11, '6': '.google.protobuf.Value', '10': 'values'},
],
};
/// Descriptor for `ListValue`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List listValueDescriptor = $convert.base64Decode(
'CglMaXN0VmFsdWUSLgoGdmFsdWVzGAEgAygLMhYuZ29vZ2xlLnByb3RvYnVmLlZhbHVlUgZ2YW'
'x1ZXM=');

View file

@ -0,0 +1,14 @@
//
// Generated code. Do not modify.
// source: google/protobuf/struct.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'struct.pb.dart';

View file

@ -14,6 +14,7 @@ import 'dart:core' as $core;
import 'package:fixnum/fixnum.dart' as $fixnum;
import 'package:protobuf/protobuf.dart' as $pb;
import 'google/protobuf/struct.pb.dart' as $1;
import 'google/protobuf/timestamp.pb.dart' as $0;
import 'stillbox.pbenum.dart';
@ -21,6 +22,7 @@ export 'stillbox.pbenum.dart';
enum Message_ToClientMessage {
call,
tgInfo,
notification,
popup,
error,
@ -30,6 +32,7 @@ enum Message_ToClientMessage {
class Message extends $pb.GeneratedMessage {
factory Message({
Call? call,
TalkgroupInfo? tgInfo,
Notification? notification,
UserPopup? popup,
Error? error,
@ -38,6 +41,9 @@ class Message extends $pb.GeneratedMessage {
if (call != null) {
$result.call = call;
}
if (tgInfo != null) {
$result.tgInfo = tgInfo;
}
if (notification != null) {
$result.notification = notification;
}
@ -55,17 +61,19 @@ class Message extends $pb.GeneratedMessage {
static const $core.Map<$core.int, Message_ToClientMessage> _Message_ToClientMessageByTag = {
1 : Message_ToClientMessage.call,
2 : Message_ToClientMessage.notification,
3 : Message_ToClientMessage.popup,
4 : Message_ToClientMessage.error,
2 : Message_ToClientMessage.tgInfo,
3 : Message_ToClientMessage.notification,
4 : Message_ToClientMessage.popup,
5 : Message_ToClientMessage.error,
0 : Message_ToClientMessage.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Message', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create)
..oo(0, [1, 2, 3, 4])
..oo(0, [1, 2, 3, 4, 5])
..aOM<Call>(1, _omitFieldNames ? '' : 'call', subBuilder: Call.create)
..aOM<Notification>(2, _omitFieldNames ? '' : 'notification', subBuilder: Notification.create)
..aOM<UserPopup>(3, _omitFieldNames ? '' : 'popup', subBuilder: UserPopup.create)
..aOM<Error>(4, _omitFieldNames ? '' : 'error', subBuilder: Error.create)
..aOM<TalkgroupInfo>(2, _omitFieldNames ? '' : 'tgInfo', protoName: 'tgInfo', subBuilder: TalkgroupInfo.create)
..aOM<Notification>(3, _omitFieldNames ? '' : 'notification', subBuilder: Notification.create)
..aOM<UserPopup>(4, _omitFieldNames ? '' : 'popup', subBuilder: UserPopup.create)
..aOM<Error>(5, _omitFieldNames ? '' : 'error', subBuilder: Error.create)
..hasRequiredFields = false
;
@ -105,37 +113,48 @@ class Message extends $pb.GeneratedMessage {
Call ensureCall() => $_ensure(0);
@$pb.TagNumber(2)
Notification get notification => $_getN(1);
TalkgroupInfo get tgInfo => $_getN(1);
@$pb.TagNumber(2)
set notification(Notification v) { setField(2, v); }
set tgInfo(TalkgroupInfo v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasNotification() => $_has(1);
$core.bool hasTgInfo() => $_has(1);
@$pb.TagNumber(2)
void clearNotification() => clearField(2);
void clearTgInfo() => clearField(2);
@$pb.TagNumber(2)
Notification ensureNotification() => $_ensure(1);
TalkgroupInfo ensureTgInfo() => $_ensure(1);
@$pb.TagNumber(3)
UserPopup get popup => $_getN(2);
Notification get notification => $_getN(2);
@$pb.TagNumber(3)
set popup(UserPopup v) { setField(3, v); }
set notification(Notification v) { setField(3, v); }
@$pb.TagNumber(3)
$core.bool hasPopup() => $_has(2);
$core.bool hasNotification() => $_has(2);
@$pb.TagNumber(3)
void clearPopup() => clearField(3);
void clearNotification() => clearField(3);
@$pb.TagNumber(3)
UserPopup ensurePopup() => $_ensure(2);
Notification ensureNotification() => $_ensure(2);
@$pb.TagNumber(4)
Error get error => $_getN(3);
UserPopup get popup => $_getN(3);
@$pb.TagNumber(4)
set error(Error v) { setField(4, v); }
set popup(UserPopup v) { setField(4, v); }
@$pb.TagNumber(4)
$core.bool hasError() => $_has(3);
$core.bool hasPopup() => $_has(3);
@$pb.TagNumber(4)
void clearError() => clearField(4);
void clearPopup() => clearField(4);
@$pb.TagNumber(4)
Error ensureError() => $_ensure(3);
UserPopup ensurePopup() => $_ensure(3);
@$pb.TagNumber(5)
Error get error => $_getN(4);
@$pb.TagNumber(5)
set error(Error v) { setField(5, v); }
@$pb.TagNumber(5)
$core.bool hasError() => $_has(4);
@$pb.TagNumber(5)
void clearError() => clearField(5);
@$pb.TagNumber(5)
Error ensureError() => $_ensure(4);
}
class Call extends $pb.GeneratedMessage {
@ -379,11 +398,15 @@ class UserPopup extends $pb.GeneratedMessage {
class Error extends $pb.GeneratedMessage {
factory Error({
$core.String? error,
Command? command,
}) {
final $result = create();
if (error != null) {
$result.error = error;
}
if (command != null) {
$result.command = command;
}
return $result;
}
Error._() : super();
@ -392,6 +415,7 @@ class Error extends $pb.GeneratedMessage {
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Error', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create)
..aOS(1, _omitFieldNames ? '' : 'error')
..aOM<Command>(2, _omitFieldNames ? '' : 'command', subBuilder: Command.create)
..hasRequiredFields = false
;
@ -424,6 +448,17 @@ class Error extends $pb.GeneratedMessage {
$core.bool hasError() => $_has(0);
@$pb.TagNumber(1)
void clearError() => clearField(1);
@$pb.TagNumber(2)
Command get command => $_getN(1);
@$pb.TagNumber(2)
set command(Command v) { setField(2, v); }
@$pb.TagNumber(2)
$core.bool hasCommand() => $_has(1);
@$pb.TagNumber(2)
void clearCommand() => clearField(2);
@$pb.TagNumber(2)
Command ensureCommand() => $_ensure(1);
}
class Notification extends $pb.GeneratedMessage {
@ -509,6 +544,7 @@ class Notification extends $pb.GeneratedMessage {
enum Command_Command {
liveCommand,
searchCommand,
tgCommand,
notSet
}
@ -516,6 +552,7 @@ class Command extends $pb.GeneratedMessage {
factory Command({
Live? liveCommand,
Search? searchCommand,
Talkgroup? tgCommand,
}) {
final $result = create();
if (liveCommand != null) {
@ -524,6 +561,9 @@ class Command extends $pb.GeneratedMessage {
if (searchCommand != null) {
$result.searchCommand = searchCommand;
}
if (tgCommand != null) {
$result.tgCommand = tgCommand;
}
return $result;
}
Command._() : super();
@ -533,12 +573,14 @@ class Command extends $pb.GeneratedMessage {
static const $core.Map<$core.int, Command_Command> _Command_CommandByTag = {
1 : Command_Command.liveCommand,
2 : Command_Command.searchCommand,
3 : Command_Command.tgCommand,
0 : Command_Command.notSet
};
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Command', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create)
..oo(0, [1, 2])
..oo(0, [1, 2, 3])
..aOM<Live>(1, _omitFieldNames ? '' : 'liveCommand', protoName: 'liveCommand', subBuilder: Live.create)
..aOM<Search>(2, _omitFieldNames ? '' : 'searchCommand', protoName: 'searchCommand', subBuilder: Search.create)
..aOM<Talkgroup>(3, _omitFieldNames ? '' : 'tgCommand', protoName: 'tgCommand', subBuilder: Talkgroup.create)
..hasRequiredFields = false
;
@ -587,6 +629,149 @@ class Command extends $pb.GeneratedMessage {
void clearSearchCommand() => clearField(2);
@$pb.TagNumber(2)
Search ensureSearchCommand() => $_ensure(1);
@$pb.TagNumber(3)
Talkgroup get tgCommand => $_getN(2);
@$pb.TagNumber(3)
set tgCommand(Talkgroup v) { setField(3, v); }
@$pb.TagNumber(3)
$core.bool hasTgCommand() => $_has(2);
@$pb.TagNumber(3)
void clearTgCommand() => clearField(3);
@$pb.TagNumber(3)
Talkgroup ensureTgCommand() => $_ensure(2);
}
class TalkgroupInfo extends $pb.GeneratedMessage {
factory TalkgroupInfo({
Talkgroup? tg,
$core.String? name,
$core.String? group,
$core.int? frequency,
$core.Iterable<$core.String>? tags,
$1.Struct? metadata,
$core.bool? learned,
}) {
final $result = create();
if (tg != null) {
$result.tg = tg;
}
if (name != null) {
$result.name = name;
}
if (group != null) {
$result.group = group;
}
if (frequency != null) {
$result.frequency = frequency;
}
if (tags != null) {
$result.tags.addAll(tags);
}
if (metadata != null) {
$result.metadata = metadata;
}
if (learned != null) {
$result.learned = learned;
}
return $result;
}
TalkgroupInfo._() : super();
factory TalkgroupInfo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
factory TalkgroupInfo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'TalkgroupInfo', package: const $pb.PackageName(_omitMessageNames ? '' : 'stillbox'), createEmptyInstance: create)
..aOM<Talkgroup>(1, _omitFieldNames ? '' : 'tg', subBuilder: Talkgroup.create)
..aOS(2, _omitFieldNames ? '' : 'name')
..aOS(3, _omitFieldNames ? '' : 'group')
..a<$core.int>(4, _omitFieldNames ? '' : 'frequency', $pb.PbFieldType.O3)
..pPS(5, _omitFieldNames ? '' : 'tags')
..aOM<$1.Struct>(6, _omitFieldNames ? '' : 'metadata', subBuilder: $1.Struct.create)
..aOB(7, _omitFieldNames ? '' : 'learned')
..hasRequiredFields = false
;
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
'Will be removed in next major version')
TalkgroupInfo clone() => TalkgroupInfo()..mergeFromMessage(this);
@$core.Deprecated(
'Using this can add significant overhead to your binary. '
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
'Will be removed in next major version')
TalkgroupInfo copyWith(void Function(TalkgroupInfo) updates) => super.copyWith((message) => updates(message as TalkgroupInfo)) as TalkgroupInfo;
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static TalkgroupInfo create() => TalkgroupInfo._();
TalkgroupInfo createEmptyInstance() => create();
static $pb.PbList<TalkgroupInfo> createRepeated() => $pb.PbList<TalkgroupInfo>();
@$core.pragma('dart2js:noInline')
static TalkgroupInfo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TalkgroupInfo>(create);
static TalkgroupInfo? _defaultInstance;
@$pb.TagNumber(1)
Talkgroup get tg => $_getN(0);
@$pb.TagNumber(1)
set tg(Talkgroup v) { setField(1, v); }
@$pb.TagNumber(1)
$core.bool hasTg() => $_has(0);
@$pb.TagNumber(1)
void clearTg() => clearField(1);
@$pb.TagNumber(1)
Talkgroup ensureTg() => $_ensure(0);
@$pb.TagNumber(2)
$core.String get name => $_getSZ(1);
@$pb.TagNumber(2)
set name($core.String v) { $_setString(1, v); }
@$pb.TagNumber(2)
$core.bool hasName() => $_has(1);
@$pb.TagNumber(2)
void clearName() => clearField(2);
@$pb.TagNumber(3)
$core.String get group => $_getSZ(2);
@$pb.TagNumber(3)
set group($core.String v) { $_setString(2, v); }
@$pb.TagNumber(3)
$core.bool hasGroup() => $_has(2);
@$pb.TagNumber(3)
void clearGroup() => clearField(3);
@$pb.TagNumber(4)
$core.int get frequency => $_getIZ(3);
@$pb.TagNumber(4)
set frequency($core.int v) { $_setSignedInt32(3, v); }
@$pb.TagNumber(4)
$core.bool hasFrequency() => $_has(3);
@$pb.TagNumber(4)
void clearFrequency() => clearField(4);
@$pb.TagNumber(5)
$core.List<$core.String> get tags => $_getList(4);
@$pb.TagNumber(6)
$1.Struct get metadata => $_getN(5);
@$pb.TagNumber(6)
set metadata($1.Struct v) { setField(6, v); }
@$pb.TagNumber(6)
$core.bool hasMetadata() => $_has(5);
@$pb.TagNumber(6)
void clearMetadata() => clearField(6);
@$pb.TagNumber(6)
$1.Struct ensureMetadata() => $_ensure(5);
@$pb.TagNumber(7)
$core.bool get learned => $_getBF(6);
@$pb.TagNumber(7)
set learned($core.bool v) { $_setBool(6, v); }
@$pb.TagNumber(7)
$core.bool hasLearned() => $_has(6);
@$pb.TagNumber(7)
void clearLearned() => clearField(7);
}
class Live extends $pb.GeneratedMessage {

View file

@ -33,9 +33,10 @@ const Message$json = {
'1': 'Message',
'2': [
{'1': 'call', '3': 1, '4': 1, '5': 11, '6': '.stillbox.Call', '9': 0, '10': 'call'},
{'1': 'notification', '3': 2, '4': 1, '5': 11, '6': '.stillbox.Notification', '9': 0, '10': 'notification'},
{'1': 'popup', '3': 3, '4': 1, '5': 11, '6': '.stillbox.UserPopup', '9': 0, '10': 'popup'},
{'1': 'error', '3': 4, '4': 1, '5': 11, '6': '.stillbox.Error', '9': 0, '10': 'error'},
{'1': 'tgInfo', '3': 2, '4': 1, '5': 11, '6': '.stillbox.TalkgroupInfo', '9': 0, '10': 'tgInfo'},
{'1': 'notification', '3': 3, '4': 1, '5': 11, '6': '.stillbox.Notification', '9': 0, '10': 'notification'},
{'1': 'popup', '3': 4, '4': 1, '5': 11, '6': '.stillbox.UserPopup', '9': 0, '10': 'popup'},
{'1': 'error', '3': 5, '4': 1, '5': 11, '6': '.stillbox.Error', '9': 0, '10': 'error'},
],
'8': [
{'1': 'toClient_message'},
@ -44,10 +45,11 @@ const Message$json = {
/// Descriptor for `Message`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List messageDescriptor = $convert.base64Decode(
'CgdNZXNzYWdlEiQKBGNhbGwYASABKAsyDi5zdGlsbGJveC5DYWxsSABSBGNhbGwSPAoMbm90aW'
'ZpY2F0aW9uGAIgASgLMhYuc3RpbGxib3guTm90aWZpY2F0aW9uSABSDG5vdGlmaWNhdGlvbhIr'
'CgVwb3B1cBgDIAEoCzITLnN0aWxsYm94LlVzZXJQb3B1cEgAUgVwb3B1cBInCgVlcnJvchgEIA'
'EoCzIPLnN0aWxsYm94LkVycm9ySABSBWVycm9yQhIKEHRvQ2xpZW50X21lc3NhZ2U=');
'CgdNZXNzYWdlEiQKBGNhbGwYASABKAsyDi5zdGlsbGJveC5DYWxsSABSBGNhbGwSMQoGdGdJbm'
'ZvGAIgASgLMhcuc3RpbGxib3guVGFsa2dyb3VwSW5mb0gAUgZ0Z0luZm8SPAoMbm90aWZpY2F0'
'aW9uGAMgASgLMhYuc3RpbGxib3guTm90aWZpY2F0aW9uSABSDG5vdGlmaWNhdGlvbhIrCgVwb3'
'B1cBgEIAEoCzITLnN0aWxsYm94LlVzZXJQb3B1cEgAUgVwb3B1cBInCgVlcnJvchgFIAEoCzIP'
'LnN0aWxsYm94LkVycm9ySABSBWVycm9yQhIKEHRvQ2xpZW50X21lc3NhZ2U=');
@$core.Deprecated('Use callDescriptor instead')
const Call$json = {
@ -98,12 +100,14 @@ const Error$json = {
'1': 'Error',
'2': [
{'1': 'error', '3': 1, '4': 1, '5': 9, '10': 'error'},
{'1': 'command', '3': 2, '4': 1, '5': 11, '6': '.stillbox.Command', '10': 'command'},
],
};
/// Descriptor for `Error`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List errorDescriptor = $convert.base64Decode(
'CgVFcnJvchIUCgVlcnJvchgBIAEoCVIFZXJyb3I=');
'CgVFcnJvchIUCgVlcnJvchgBIAEoCVIFZXJyb3ISKwoHY29tbWFuZBgCIAEoCzIRLnN0aWxsYm'
'94LkNvbW1hbmRSB2NvbW1hbmQ=');
@$core.Deprecated('Use notificationDescriptor instead')
const Notification$json = {
@ -127,6 +131,7 @@ const Command$json = {
'2': [
{'1': 'liveCommand', '3': 1, '4': 1, '5': 11, '6': '.stillbox.Live', '9': 0, '10': 'liveCommand'},
{'1': 'searchCommand', '3': 2, '4': 1, '5': 11, '6': '.stillbox.Search', '9': 0, '10': 'searchCommand'},
{'1': 'tgCommand', '3': 3, '4': 1, '5': 11, '6': '.stillbox.Talkgroup', '9': 0, '10': 'tgCommand'},
],
'8': [
{'1': 'command'},
@ -137,7 +142,37 @@ const Command$json = {
final $typed_data.Uint8List commandDescriptor = $convert.base64Decode(
'CgdDb21tYW5kEjIKC2xpdmVDb21tYW5kGAEgASgLMg4uc3RpbGxib3guTGl2ZUgAUgtsaXZlQ2'
'9tbWFuZBI4Cg1zZWFyY2hDb21tYW5kGAIgASgLMhAuc3RpbGxib3guU2VhcmNoSABSDXNlYXJj'
'aENvbW1hbmRCCQoHY29tbWFuZA==');
'aENvbW1hbmQSMwoJdGdDb21tYW5kGAMgASgLMhMuc3RpbGxib3guVGFsa2dyb3VwSABSCXRnQ2'
'9tbWFuZEIJCgdjb21tYW5k');
@$core.Deprecated('Use talkgroupInfoDescriptor instead')
const TalkgroupInfo$json = {
'1': 'TalkgroupInfo',
'2': [
{'1': 'tg', '3': 1, '4': 1, '5': 11, '6': '.stillbox.Talkgroup', '10': 'tg'},
{'1': 'name', '3': 2, '4': 1, '5': 9, '9': 0, '10': 'name', '17': true},
{'1': 'group', '3': 3, '4': 1, '5': 9, '9': 1, '10': 'group', '17': true},
{'1': 'frequency', '3': 4, '4': 1, '5': 5, '9': 2, '10': 'frequency', '17': true},
{'1': 'tags', '3': 5, '4': 3, '5': 9, '10': 'tags'},
{'1': 'metadata', '3': 6, '4': 1, '5': 11, '6': '.google.protobuf.Struct', '9': 3, '10': 'metadata', '17': true},
{'1': 'learned', '3': 7, '4': 1, '5': 8, '10': 'learned'},
],
'8': [
{'1': '_name'},
{'1': '_group'},
{'1': '_frequency'},
{'1': '_metadata'},
],
};
/// Descriptor for `TalkgroupInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List talkgroupInfoDescriptor = $convert.base64Decode(
'Cg1UYWxrZ3JvdXBJbmZvEiMKAnRnGAEgASgLMhMuc3RpbGxib3guVGFsa2dyb3VwUgJ0ZxIXCg'
'RuYW1lGAIgASgJSABSBG5hbWWIAQESGQoFZ3JvdXAYAyABKAlIAVIFZ3JvdXCIAQESIQoJZnJl'
'cXVlbmN5GAQgASgFSAJSCWZyZXF1ZW5jeYgBARISCgR0YWdzGAUgAygJUgR0YWdzEjgKCG1ldG'
'FkYXRhGAYgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdEgDUghtZXRhZGF0YYgBARIYCgds'
'ZWFybmVkGAcgASgIUgdsZWFybmVkQgcKBV9uYW1lQggKBl9ncm91cEIMCgpfZnJlcXVlbmN5Qg'
'sKCV9tZXRhZGF0YQ==');
@$core.Deprecated('Use liveDescriptor instead')
const Live$json = {

View file

@ -1,4 +1,7 @@
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import '../controller/stillbox.dart';
import '../pb/stillbox.pb.dart';
class LCD extends StatefulWidget {
const LCD({super.key});
@ -8,16 +11,6 @@ class LCD extends StatefulWidget {
}
class _LCDState extends State<LCD> {
String? _currentTG;
int? _currentTGID;
void _setListening(String tg, int tgid) {
setState(() {
_currentTG = tg;
_currentTGID = tgid;
});
}
@override
Widget build(BuildContext context) {
return Container(
@ -36,7 +29,27 @@ class _LCDState extends State<LCD> {
width: double.infinity,
child: AspectRatio(
aspectRatio: 16 / 9,
child: Container(),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: DefaultTextStyle(
style: const TextStyle(
color: Colors.black,
),
child: Consumer<Stillbox>(builder: (context, sb, child) {
if (sb.currentCall != null) {
return FutureBuilder(
future: sb.currentCall?.tg,
builder: (BuildContext context,
AsyncSnapshot<TalkgroupInfo> tgi) {
return Text(
'${tgi.data?.name ?? sb.currentCall!.call.talkgroup}${tgi.data?.learned ?? false ? ' 📓' : ''}');
});
}
return const Text('');
}),
),
),
));
}
}

View file

@ -3,13 +3,15 @@ package stillbox;
option go_package = "./pb";
import "google/protobuf/timestamp.proto";
import "google/protobuf/struct.proto";
message Message {
oneof toClient_message {
Call call = 1;
Notification notification = 2;
UserPopup popup = 3;
Error error = 4;
TalkgroupInfo tgInfo = 2;
Notification notification = 3;
UserPopup popup = 4;
Error error = 5;
}
}
@ -34,6 +36,7 @@ message UserPopup {
message Error {
string error = 1;
Command command = 2;
}
message Notification {
@ -46,9 +49,20 @@ message Command {
oneof command {
Live liveCommand = 1;
Search searchCommand = 2;
Talkgroup tgCommand = 3;
}
}
message TalkgroupInfo {
Talkgroup tg = 1;
optional string name = 2;
optional string group = 3;
optional int32 frequency = 4;
repeated string tags = 5;
optional google.protobuf.Struct metadata = 6;
bool learned = 7;
}
enum LiveState {
LS_STOPPED = 0;
LS_LIVE = 1;