WIP #120 History widget improvements
This commit is contained in:
parent
6e038b0685
commit
e16338c3f2
@ -1,14 +1,39 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class Entity {
|
||||
static const STATE_ICONS_COLORS = {
|
||||
class EntityColors {
|
||||
static const _stateColors = {
|
||||
"on": Colors.amber,
|
||||
"auto": Colors.amber,
|
||||
"idle": Colors.amber,
|
||||
"off": Color.fromRGBO(68, 115, 158, 1.0),
|
||||
"default": Color.fromRGBO(68, 115, 158, 1.0),
|
||||
"unavailable": Colors.black12,
|
||||
"unknown": Colors.black12,
|
||||
"playing": Colors.amber
|
||||
"heat": Colors.redAccent,
|
||||
"cool": Colors.lightBlue,
|
||||
"unavailable": Colors.black26,
|
||||
"unknown": Colors.black26,
|
||||
"playing": Colors.amber,
|
||||
"above_horizon": Colors.amber,
|
||||
"home": Colors.amber,
|
||||
};
|
||||
|
||||
static Color stateColor(String state) {
|
||||
return _stateColors[state] ?? _stateColors["default"];
|
||||
}
|
||||
|
||||
static charts.Color historyStateColor(String state) {
|
||||
Color c = stateColor(state);
|
||||
return charts.Color(
|
||||
r: c.red,
|
||||
g: c.green,
|
||||
b: c.blue,
|
||||
a: c.alpha
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Entity {
|
||||
|
||||
static const badgeColors = {
|
||||
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
||||
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
||||
|
@ -1,217 +0,0 @@
|
||||
part of '../main.dart';
|
||||
|
||||
class EntityHistoryWidgetType {
|
||||
static const int simplest = 0;
|
||||
static const int valueToTime = 0;
|
||||
}
|
||||
|
||||
class EntityHistoryWidget extends StatefulWidget {
|
||||
|
||||
final int type;
|
||||
|
||||
const EntityHistoryWidget({Key key, @required this.type}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EntityHistoryWidgetState createState() {
|
||||
return new _EntityHistoryWidgetState();
|
||||
}
|
||||
}
|
||||
|
||||
class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
|
||||
List _history;
|
||||
bool _needToUpdateHistory;
|
||||
DateTime _selectionTimeStart;
|
||||
DateTime _selectionTimeEnd;
|
||||
Map<String, String> _selectionData;
|
||||
int _selectedId = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_needToUpdateHistory = true;
|
||||
}
|
||||
|
||||
void _loadHistory(HomeAssistant ha, String entityId) {
|
||||
ha.getHistory(entityId).then((history){
|
||||
setState(() {
|
||||
_history = history.isNotEmpty ? history[0] : [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
}).catchError((e) {
|
||||
TheLogger.error("Error loading $entityId history: $e");
|
||||
setState(() {
|
||||
_history = [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final HomeAssistantModel homeAssistantModel = HomeAssistantModel.of(context);
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final Entity entity = entityModel.entity;
|
||||
if (!_needToUpdateHistory) {
|
||||
_needToUpdateHistory = true;
|
||||
} else {
|
||||
_loadHistory(homeAssistantModel.homeAssistant, entity.entityId);
|
||||
}
|
||||
return _buildChart();
|
||||
}
|
||||
|
||||
Widget _buildChart() {
|
||||
List<Widget> children = [];
|
||||
if (_selectionTimeStart != null) {
|
||||
children.add(
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
"${_selectionData["State"]}",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: _selectionData["State"] == "on" ? Colors.green : Colors.red
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Text("${formatDate(_selectionTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}"),
|
||||
Text("${formatDate(_selectionTimeEnd ?? _selectionTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}"),
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
);
|
||||
} else {
|
||||
children.add(
|
||||
Container(height: 32.0,)
|
||||
);
|
||||
}
|
||||
if (_history == null) {
|
||||
children.add(
|
||||
Text("Loading history...")
|
||||
);
|
||||
} else if (_history.isEmpty) {
|
||||
children.add(
|
||||
Text("No history for last 24h")
|
||||
);
|
||||
} else {
|
||||
children.add(
|
||||
SizedBox(
|
||||
height: 70.0,
|
||||
child: charts.TimeSeriesChart(
|
||||
_createHistoryData(),
|
||||
animate: false,
|
||||
dateTimeFactory: const charts.LocalDateTimeFactory(),
|
||||
primaryMeasureAxis: charts.NumericAxisSpec(
|
||||
renderSpec: charts.NoneRenderSpec()
|
||||
),
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
listener: _onSelectionChanged,
|
||||
)
|
||||
],
|
||||
behaviors: [
|
||||
charts.PanAndZoomBehavior(),
|
||||
],
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
children.add(Divider());
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_onSelectionChanged(charts.SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
|
||||
DateTime timeStart;
|
||||
DateTime timeEnd;
|
||||
int selectedId;
|
||||
final measures = <String, String>{};
|
||||
|
||||
if ((selectedDatum.isNotEmpty) &&(selectedDatum.first.datum.endTime != null)) {
|
||||
timeStart = selectedDatum.first.datum.startTime;
|
||||
timeEnd = selectedDatum.first.datum.endTime;
|
||||
selectedId = selectedDatum.first.datum.id;
|
||||
TheLogger.debug("Selected datum length is ${selectedDatum.length}");
|
||||
selectedDatum.forEach((charts.SeriesDatum datumPair) {
|
||||
measures[datumPair.series.displayName] = datumPair.datum.state;
|
||||
});
|
||||
setState(() {
|
||||
_selectionTimeStart = timeStart;
|
||||
_selectionTimeEnd = timeEnd;
|
||||
_selectionData = measures;
|
||||
_selectedId = selectedId;
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<charts.Series<EntityStateHistoryMoment, DateTime>> _createHistoryData() {
|
||||
List<EntityStateHistoryMoment> data = [];
|
||||
DateTime now = DateTime.now();
|
||||
for (var i = 0; i < _history.length; i++) {
|
||||
var stateData = _history[i];
|
||||
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
|
||||
DateTime endTime;
|
||||
if (i < (_history.length - 1)) {
|
||||
endTime = DateTime.tryParse(_history[i+1]["last_updated"])?.toLocal();
|
||||
} else {
|
||||
endTime = now;
|
||||
}
|
||||
data.add(EntityStateHistoryMoment(stateData["state"], startTime, endTime, i));
|
||||
}
|
||||
data.add(EntityStateHistoryMoment(data.last.state, now, null, _history.length));
|
||||
return [
|
||||
new charts.Series<EntityStateHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
strokeWidthPxFn: (EntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 70.0 : 40.0,
|
||||
colorFn: ((EntityStateHistoryMoment historyMoment, __) {
|
||||
if (historyMoment.state == "on") {
|
||||
if (historyMoment.id == _selectedId) {
|
||||
return charts.MaterialPalette.green.makeShades(2)[0];
|
||||
} else {
|
||||
return charts.MaterialPalette.green.makeShades(2)[1];
|
||||
}
|
||||
} else {
|
||||
if (historyMoment.id == _selectedId) {
|
||||
return charts.MaterialPalette.red.makeShades(2)[0];
|
||||
} else {
|
||||
return charts.MaterialPalette.red.makeShades(2)[1];
|
||||
}
|
||||
}
|
||||
}),
|
||||
domainFn: (EntityStateHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityStateHistoryMoment historyMoment, _) => 0,
|
||||
data: data,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class EntityStateHistoryMoment {
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final String state;
|
||||
final int id;
|
||||
|
||||
EntityStateHistoryMoment(this.state, this.startTime, this.endTime, this.id);
|
||||
}
|
@ -11,8 +11,8 @@ class EntityIcon extends StatelessWidget {
|
||||
child: MaterialDesignIcons.createIconWidgetFromEntityData(
|
||||
entityModel.entity,
|
||||
Entity.iconSize,
|
||||
Entity.STATE_ICONS_COLORS[entityModel.entity.state] ??
|
||||
Entity.STATE_ICONS_COLORS["default"]),
|
||||
EntityColors.stateColor(entityModel.entity.state)
|
||||
),
|
||||
),
|
||||
onTap: () => entityModel.handleTap
|
||||
? eventBus.fire(new ShowEntityPageEvent(entityModel.entity))
|
||||
|
86
lib/entity_widgets/history_chart/entity_history.dart
Normal file
86
lib/entity_widgets/history_chart/entity_history.dart
Normal file
@ -0,0 +1,86 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class EntityHistoryWidgetType {
|
||||
static const int simplest = 0;
|
||||
static const int valueToTime = 1;
|
||||
}
|
||||
|
||||
class EntityHistoryWidget extends StatefulWidget {
|
||||
|
||||
final int type;
|
||||
|
||||
const EntityHistoryWidget({Key key, @required this.type}) : super(key: key);
|
||||
|
||||
@override
|
||||
_EntityHistoryWidgetState createState() {
|
||||
return new _EntityHistoryWidgetState();
|
||||
}
|
||||
}
|
||||
|
||||
class _EntityHistoryWidgetState extends State<EntityHistoryWidget> {
|
||||
|
||||
List _history;
|
||||
bool _needToUpdateHistory;
|
||||
int _selectedId = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_needToUpdateHistory = true;
|
||||
}
|
||||
|
||||
void _loadHistory(HomeAssistant ha, String entityId) {
|
||||
ha.getHistory(entityId).then((history){
|
||||
setState(() {
|
||||
_history = history.isNotEmpty ? history[0] : [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
}).catchError((e) {
|
||||
TheLogger.error("Error loading $entityId history: $e");
|
||||
setState(() {
|
||||
_history = [];
|
||||
_needToUpdateHistory = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final HomeAssistantModel homeAssistantModel = HomeAssistantModel.of(context);
|
||||
final EntityModel entityModel = EntityModel.of(context);
|
||||
final Entity entity = entityModel.entity;
|
||||
if (!_needToUpdateHistory) {
|
||||
_needToUpdateHistory = true;
|
||||
} else {
|
||||
_loadHistory(homeAssistantModel.homeAssistant, entity.entityId);
|
||||
}
|
||||
return _buildChart();
|
||||
}
|
||||
|
||||
Widget _buildChart() {
|
||||
List<Widget> children = [];
|
||||
if (_history == null) {
|
||||
children.add(
|
||||
Text("Loading history...")
|
||||
);
|
||||
} else if (_history.isEmpty) {
|
||||
children.add(
|
||||
Text("No history for last 24h")
|
||||
);
|
||||
} else {
|
||||
children.add(
|
||||
SimpleStateHistoryChartWidget(
|
||||
rawHistory: _history
|
||||
)
|
||||
);
|
||||
}
|
||||
children.add(Divider());
|
||||
return Padding(
|
||||
padding: EdgeInsets.fromLTRB(0.0, Entity.rowPadding, 0.0, Entity.rowPadding),
|
||||
child: Column(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
197
lib/entity_widgets/history_chart/simple_state_history_chart.dart
Normal file
197
lib/entity_widgets/history_chart/simple_state_history_chart.dart
Normal file
@ -0,0 +1,197 @@
|
||||
part of '../../main.dart';
|
||||
|
||||
class SimpleStateHistoryChartWidget extends StatefulWidget {
|
||||
final rawHistory;
|
||||
|
||||
const SimpleStateHistoryChartWidget({Key key, this.rawHistory}) : super(key: key);
|
||||
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return new _SimpleStateHistoryChartWidgetState();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartWidget> {
|
||||
|
||||
int _selectedId = -1;
|
||||
List<charts.Series<SimpleEntityStateHistoryMoment, DateTime>> _parsedHistory;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_parsedHistory = _parseHistory();
|
||||
DateTime selectedTimeStart;
|
||||
DateTime selectedTimeEnd;
|
||||
String selectedState;
|
||||
if ((_selectedId > -1) && (_parsedHistory != null) && (_parsedHistory.first.data.length >= (_selectedId + 1))) {
|
||||
selectedTimeStart = _parsedHistory.first.data[_selectedId].startTime;
|
||||
selectedTimeEnd = _parsedHistory.first.data[_selectedId].endTime;
|
||||
selectedState = _parsedHistory.first.data[_selectedId].state;
|
||||
}
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
HistoryControlWidget(
|
||||
selectedTimeStart: selectedTimeStart,
|
||||
selectedTimeEnd: selectedTimeEnd,
|
||||
selectedState: selectedState,
|
||||
onPrevTap: () => _selectPrev(),
|
||||
onNextTap: () => _selectNext(),
|
||||
),
|
||||
SizedBox(
|
||||
height: 70.0,
|
||||
child: charts.TimeSeriesChart(
|
||||
_parsedHistory,
|
||||
animate: false,
|
||||
dateTimeFactory: const charts.LocalDateTimeFactory(),
|
||||
primaryMeasureAxis: charts.NumericAxisSpec(
|
||||
renderSpec: charts.NoneRenderSpec()
|
||||
),
|
||||
selectionModels: [
|
||||
new charts.SelectionModelConfig(
|
||||
type: charts.SelectionModelType.info,
|
||||
listener: (model) => _onSelectionChanged(model),
|
||||
)
|
||||
],
|
||||
behaviors: [
|
||||
charts.PanAndZoomBehavior(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
List<charts.Series<SimpleEntityStateHistoryMoment, DateTime>> _parseHistory() {
|
||||
List<SimpleEntityStateHistoryMoment> data = [];
|
||||
DateTime now = DateTime.now();
|
||||
for (var i = 0; i < widget.rawHistory.length; i++) {
|
||||
var stateData = widget.rawHistory[i];
|
||||
DateTime startTime = DateTime.tryParse(stateData["last_updated"])?.toLocal();
|
||||
DateTime endTime;
|
||||
if (i < (widget.rawHistory.length - 1)) {
|
||||
endTime = DateTime.tryParse(widget.rawHistory[i+1]["last_updated"])?.toLocal();
|
||||
} else {
|
||||
endTime = now;
|
||||
}
|
||||
data.add(SimpleEntityStateHistoryMoment(stateData["state"], startTime, endTime, i));
|
||||
}
|
||||
data.add(SimpleEntityStateHistoryMoment(data.last.state, now, null, widget.rawHistory.length));
|
||||
return [
|
||||
new charts.Series<SimpleEntityStateHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
strokeWidthPxFn: (SimpleEntityStateHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 70.0 : 40.0,
|
||||
colorFn: (SimpleEntityStateHistoryMoment historyMoment, __) => EntityColors.historyStateColor(historyMoment.state),
|
||||
domainFn: (SimpleEntityStateHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (SimpleEntityStateHistoryMoment historyMoment, _) => 0,
|
||||
data: data,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
void _selectPrev() {
|
||||
if (_selectedId > 0) {
|
||||
setState(() {
|
||||
_selectedId -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _selectNext() {
|
||||
if (_selectedId < (_parsedHistory.first.data.length - 2)) {
|
||||
setState(() {
|
||||
_selectedId += 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onSelectionChanged(charts.SelectionModel model) {
|
||||
final selectedDatum = model.selectedDatum;
|
||||
|
||||
int selectedId;
|
||||
|
||||
if ((selectedDatum.isNotEmpty) &&(selectedDatum.first.datum.endTime != null)) {
|
||||
selectedId = selectedDatum.first.datum.id;
|
||||
setState(() {
|
||||
_selectedId = selectedId;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HistoryControlWidget extends StatelessWidget {
|
||||
|
||||
final Function onPrevTap;
|
||||
final Function onNextTap;
|
||||
final DateTime selectedTimeStart;
|
||||
final DateTime selectedTimeEnd;
|
||||
final String selectedState;
|
||||
|
||||
const HistoryControlWidget({Key key, this.onPrevTap, this.onNextTap, this.selectedTimeStart, this.selectedTimeEnd, this.selectedState}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (selectedTimeStart != null) {
|
||||
return
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(Icons.chevron_left),
|
||||
padding: EdgeInsets.all(0.0),
|
||||
iconSize: 40.0,
|
||||
onPressed: onPrevTap,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: Text(
|
||||
"$selectedState",
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: EntityColors.stateColor(selectedState),
|
||||
fontSize: 22.0
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("${formatDate(selectedTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,),
|
||||
Text("${formatDate(selectedTimeEnd ?? selectedTimeStart, [M, ' ', d, ', ', HH, ':', nn, ':', ss])}", textAlign: TextAlign.left,),
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.chevron_right),
|
||||
padding: EdgeInsets.all(0.0),
|
||||
iconSize: 40.0,
|
||||
onPressed: onNextTap,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
} else {
|
||||
return Container(height: 32.0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class SimpleEntityStateHistoryMoment {
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final String state;
|
||||
final int id;
|
||||
|
||||
SimpleEntityStateHistoryMoment(this.state, this.startTime, this.endTime, this.id);
|
||||
}
|
@ -477,7 +477,6 @@ class HomeAssistant {
|
||||
DateTime now = DateTime.now();
|
||||
//String endTime = formatDate(now, [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
||||
String startTime = formatDate(now.subtract(Duration(hours: 24)), [yyyy, '-', mm, '-', dd, 'T', HH, ':', nn, ':', ss, z]);
|
||||
TheLogger.debug( "$startTime");
|
||||
String url = "$homeAssistantWebHost/api/history/period/$startTime?&filter_entity_id=$entityId";
|
||||
TheLogger.debug( "$url");
|
||||
http.Response historyResponse;
|
||||
@ -494,7 +493,7 @@ class HomeAssistant {
|
||||
}
|
||||
var history = json.decode(historyResponse.body);
|
||||
if (history is List) {
|
||||
TheLogger.debug( "${history.toString()}");
|
||||
TheLogger.debug( "Got ${history.first.length} history recors");
|
||||
return history;
|
||||
} else {
|
||||
return [];
|
||||
|
@ -35,7 +35,8 @@ part 'entity_widgets/last_updated.dart';
|
||||
part 'entity_widgets/mode_swicth.dart';
|
||||
part 'entity_widgets/mode_selector.dart';
|
||||
part 'entity_widgets/entity_page_container.dart';
|
||||
part 'entity_widgets/entity_history.dart';
|
||||
part 'entity_widgets/history_chart/entity_history.dart';
|
||||
part 'entity_widgets/history_chart/simple_state_history_chart.dart';
|
||||
part 'entity_widgets/state/switch_state.dart';
|
||||
part 'entity_widgets/state/slider_state.dart';
|
||||
part 'entity_widgets/state/text_input_state.dart';
|
||||
@ -65,7 +66,7 @@ part 'ui_widgets/media_control_card.dart';
|
||||
|
||||
EventBus eventBus = new EventBus();
|
||||
const String appName = "HA Client";
|
||||
const appVersion = "0.3.3.47";
|
||||
const appVersion = "0.3.3.48";
|
||||
|
||||
String homeAssistantWebHost;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: hass_client
|
||||
description: Home Assistant Android Client
|
||||
|
||||
version: 0.3.3+47
|
||||
version: 0.3.3+48
|
||||
|
||||
environment:
|
||||
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||
|
Reference in New Issue
Block a user