Compare commits
13 Commits
beta/0.8.3
...
beta/0.8.4
Author | SHA1 | Date | |
---|---|---|---|
6afbd37d71 | |||
0e8869878f | |||
7c2cfe3215 | |||
c376c0e952 | |||
da5f663396 | |||
0e92418a33 | |||
2eef7cfe5e | |||
de4e0bfb3a | |||
8bf2d31e72 | |||
2125c46143 | |||
5402eb84df | |||
ad5aa0898f | |||
040d40b614 |
@ -59,6 +59,10 @@ class CardWidget extends StatelessWidget {
|
|||||||
return _buildEntityButtonCard(context);
|
return _buildEntityButtonCard(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case CardType.BUTTON: {
|
||||||
|
return _buildEntityButtonCard(context);
|
||||||
|
}
|
||||||
|
|
||||||
case CardType.GAUGE: {
|
case CardType.GAUGE: {
|
||||||
return _buildGaugeCard(context);
|
return _buildGaugeCard(context);
|
||||||
}
|
}
|
||||||
@ -78,20 +82,15 @@ class CardWidget extends StatelessWidget {
|
|||||||
case CardType.HORIZONTAL_STACK: {
|
case CardType.HORIZONTAL_STACK: {
|
||||||
if (card.childCards.isNotEmpty) {
|
if (card.childCards.isNotEmpty) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
card.childCards.forEach((card) {
|
children = card.childCards.map((childCard) => Flexible(
|
||||||
if (card.getEntitiesToShow().isNotEmpty || card.showEmpty) {
|
fit: FlexFit.tight,
|
||||||
children.add(
|
child: childCard.build(context),
|
||||||
Flexible(
|
)
|
||||||
fit: FlexFit.tight,
|
).toList();
|
||||||
child: card.build(context),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -100,12 +99,7 @@ class CardWidget extends StatelessWidget {
|
|||||||
|
|
||||||
case CardType.VERTICAL_STACK: {
|
case CardType.VERTICAL_STACK: {
|
||||||
if (card.childCards.isNotEmpty) {
|
if (card.childCards.isNotEmpty) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = card.childCards.map((childCard) => childCard.build(context)).toList();
|
||||||
card.childCards.forEach((card) {
|
|
||||||
children.add(
|
|
||||||
card.build(context)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
@ -25,6 +25,7 @@ class EntityButtonCardBody extends StatelessWidget {
|
|||||||
child: FractionallySizedBox(
|
child: FractionallySizedBox(
|
||||||
widthFactor: 1,
|
widthFactor: 1,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
part of '../../main.dart';
|
part of '../../main.dart';
|
||||||
|
|
||||||
class GaugeCardBody extends StatefulWidget {
|
class GaugeCardBody extends StatelessWidget {
|
||||||
|
|
||||||
final int min;
|
final int min;
|
||||||
final int max;
|
final int max;
|
||||||
@ -8,31 +8,26 @@ class GaugeCardBody extends StatefulWidget {
|
|||||||
|
|
||||||
GaugeCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
GaugeCardBody({Key key, this.min, this.max, this.severity}) : super(key: key);
|
||||||
|
|
||||||
@override
|
|
||||||
_GaugeCardBodyState createState() => _GaugeCardBodyState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
double fixedValue;
|
double fixedValue;
|
||||||
double value = entityWrapper.entity.doubleState;
|
double value = entityWrapper.entity.doubleState;
|
||||||
if (value > widget.max) {
|
if (value > max) {
|
||||||
fixedValue = widget.max.toDouble();
|
fixedValue = max.toDouble();
|
||||||
} else if (value < widget.min) {
|
} else if (value < min) {
|
||||||
fixedValue = widget.min.toDouble();
|
fixedValue = min.toDouble();
|
||||||
} else {
|
} else {
|
||||||
fixedValue = value;
|
fixedValue = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<GaugeRange> ranges;
|
List<GaugeRange> ranges;
|
||||||
if (widget.severity != null && widget.severity["green"] is int && widget.severity["red"] is int && widget.severity["yellow"] is int) {
|
Color currentColor;
|
||||||
|
if (severity != null && severity["green"] is int && severity["red"] is int && severity["yellow"] is int) {
|
||||||
List<RangeContainer> rangesList = <RangeContainer>[
|
List<RangeContainer> rangesList = <RangeContainer>[
|
||||||
RangeContainer(widget.severity["green"], HAClientTheme().getGreenGaugeColor()),
|
RangeContainer(severity["green"], HAClientTheme().getGreenGaugeColor()),
|
||||||
RangeContainer(widget.severity["red"], HAClientTheme().getRedGaugeColor()),
|
RangeContainer(severity["red"], HAClientTheme().getRedGaugeColor()),
|
||||||
RangeContainer(widget.severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
RangeContainer(severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
||||||
];
|
];
|
||||||
rangesList.sort((current, next) {
|
rangesList.sort((current, next) {
|
||||||
if (current.startFrom > next.startFrom) {
|
if (current.startFrom > next.startFrom) {
|
||||||
@ -43,11 +38,20 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (fixedValue < rangesList[1].startFrom) {
|
||||||
|
currentColor = rangesList[0].color;
|
||||||
|
} else if (fixedValue < rangesList[2].startFrom && fixedValue >= rangesList[1].startFrom) {
|
||||||
|
currentColor = rangesList[1].color;
|
||||||
|
} else {
|
||||||
|
currentColor = rangesList[2].color;
|
||||||
|
}
|
||||||
|
|
||||||
ranges = [
|
ranges = [
|
||||||
GaugeRange(
|
GaugeRange(
|
||||||
startValue: rangesList[0].startFrom.toDouble(),
|
startValue: rangesList[0].startFrom.toDouble(),
|
||||||
endValue: rangesList[1].startFrom.toDouble(),
|
endValue: rangesList[1].startFrom.toDouble(),
|
||||||
color: fixedValue < rangesList[1].startFrom ? rangesList[0].color : rangesList[0].color.withOpacity(0.1),
|
color: rangesList[0].color.withOpacity(0.1),
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
endWidth: 0.3,
|
endWidth: 0.3,
|
||||||
startWidth: 0.3
|
startWidth: 0.3
|
||||||
@ -55,15 +59,15 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|||||||
GaugeRange(
|
GaugeRange(
|
||||||
startValue: rangesList[1].startFrom.toDouble(),
|
startValue: rangesList[1].startFrom.toDouble(),
|
||||||
endValue: rangesList[2].startFrom.toDouble(),
|
endValue: rangesList[2].startFrom.toDouble(),
|
||||||
color: (fixedValue < rangesList[2].startFrom && fixedValue >= rangesList[1].startFrom) ? rangesList[1].color : rangesList[1].color.withOpacity(0.1),
|
color: rangesList[1].color.withOpacity(0.1),
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
endWidth: 0.3,
|
endWidth: 0.3,
|
||||||
startWidth: 0.3
|
startWidth: 0.3
|
||||||
),
|
),
|
||||||
GaugeRange(
|
GaugeRange(
|
||||||
startValue: rangesList[2].startFrom.toDouble(),
|
startValue: rangesList[2].startFrom.toDouble(),
|
||||||
endValue: widget.max.toDouble(),
|
endValue: max.toDouble(),
|
||||||
color: fixedValue >= rangesList[2].startFrom ? rangesList[2].color : rangesList[2].color.withOpacity(0.1),
|
color: rangesList[2].color.withOpacity(0.1),
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
endWidth: 0.3,
|
endWidth: 0.3,
|
||||||
startWidth: 0.3
|
startWidth: 0.3
|
||||||
@ -71,14 +75,15 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (ranges == null) {
|
if (ranges == null) {
|
||||||
|
currentColor = Theme.of(context).primaryColorDark;
|
||||||
ranges = <GaugeRange>[
|
ranges = <GaugeRange>[
|
||||||
GaugeRange(
|
GaugeRange(
|
||||||
startValue: widget.min.toDouble(),
|
startValue: min.toDouble(),
|
||||||
endValue: widget.max.toDouble(),
|
endValue: max.toDouble(),
|
||||||
color: Theme.of(context).primaryColorDark,
|
color: Theme.of(context).primaryColorDark.withOpacity(0.1),
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
endWidth: 0.3,
|
endWidth: 0.3,
|
||||||
startWidth: 0.3
|
startWidth: 0.3,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -104,12 +109,18 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|||||||
return SfRadialGauge(
|
return SfRadialGauge(
|
||||||
axes: <RadialAxis>[
|
axes: <RadialAxis>[
|
||||||
RadialAxis(
|
RadialAxis(
|
||||||
maximum: widget.max.toDouble(),
|
maximum: max.toDouble(),
|
||||||
minimum: widget.min.toDouble(),
|
minimum: min.toDouble(),
|
||||||
showLabels: false,
|
showLabels: false,
|
||||||
|
useRangeColorForAxis: true,
|
||||||
showTicks: false,
|
showTicks: false,
|
||||||
canScaleToFit: true,
|
canScaleToFit: true,
|
||||||
ranges: ranges,
|
ranges: ranges,
|
||||||
|
axisLineStyle: AxisLineStyle(
|
||||||
|
thickness: 0.3,
|
||||||
|
thicknessUnit: GaugeSizeUnit.factor,
|
||||||
|
color: Colors.transparent
|
||||||
|
),
|
||||||
annotations: <GaugeAnnotation>[
|
annotations: <GaugeAnnotation>[
|
||||||
GaugeAnnotation(
|
GaugeAnnotation(
|
||||||
angle: -90,
|
angle: -90,
|
||||||
@ -135,27 +146,16 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
axisLineStyle: AxisLineStyle(
|
|
||||||
thickness: 0.3,
|
|
||||||
thicknessUnit: GaugeSizeUnit.factor
|
|
||||||
),
|
|
||||||
startAngle: 180,
|
startAngle: 180,
|
||||||
endAngle: 0,
|
endAngle: 0,
|
||||||
pointers: <GaugePointer>[
|
pointers: <GaugePointer>[
|
||||||
NeedlePointer(
|
RangePointer(
|
||||||
value: fixedValue,
|
value: fixedValue,
|
||||||
lengthUnit: GaugeSizeUnit.factor,
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
needleLength: 0.9,
|
width: 0.3,
|
||||||
needleColor: Theme.of(context).accentColor,
|
color: currentColor,
|
||||||
enableAnimation: true,
|
enableAnimation: true,
|
||||||
needleStartWidth: 1,
|
|
||||||
animationType: AnimationType.bounceOut,
|
animationType: AnimationType.bounceOut,
|
||||||
needleEndWidth: 3,
|
|
||||||
knobStyle: KnobStyle(
|
|
||||||
sizeUnit: GaugeSizeUnit.factor,
|
|
||||||
color: Theme.of(context).buttonColor,
|
|
||||||
knobRadius: 0.1
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ -166,6 +166,7 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RangeContainer {
|
class RangeContainer {
|
||||||
|
@ -113,6 +113,7 @@ class CardType {
|
|||||||
static const IFRAME = "iframe";
|
static const IFRAME = "iframe";
|
||||||
static const GAUGE = "gauge";
|
static const GAUGE = "gauge";
|
||||||
static const ENTITY_BUTTON = "entity-button";
|
static const ENTITY_BUTTON = "entity-button";
|
||||||
|
static const BUTTON = "button";
|
||||||
static const CONDITIONAL = "conditional";
|
static const CONDITIONAL = "conditional";
|
||||||
static const ALARM_PANEL = "alarm-panel";
|
static const ALARM_PANEL = "alarm-panel";
|
||||||
static const MARKDOWN = "markdown";
|
static const MARKDOWN = "markdown";
|
||||||
|
@ -149,7 +149,7 @@ EventBus eventBus = new EventBus();
|
|||||||
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||||
const String appName = "HA Client";
|
const String appName = "HA Client";
|
||||||
const appVersionNumber = "0.8.3";
|
const appVersionNumber = "0.8.4";
|
||||||
const appVersionAdd = "";
|
const appVersionAdd = "";
|
||||||
const appVersion = "$appVersionNumber$appVersionAdd";
|
const appVersion = "$appVersionNumber$appVersionAdd";
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ class ConnectionManager {
|
|||||||
String oauthUrl;
|
String oauthUrl;
|
||||||
String webhookId;
|
String webhookId;
|
||||||
bool settingsLoaded = false;
|
bool settingsLoaded = false;
|
||||||
|
int appIntegrationVersion;
|
||||||
bool get isAuthenticated => _token != null;
|
bool get isAuthenticated => _token != null;
|
||||||
StreamSubscription _socketSubscription;
|
StreamSubscription _socketSubscription;
|
||||||
Duration connectTimeout = Duration(seconds: 15);
|
Duration connectTimeout = Duration(seconds: 15);
|
||||||
@ -43,6 +44,7 @@ class ConnectionManager {
|
|||||||
_domain = prefs.getString('hassio-domain');
|
_domain = prefs.getString('hassio-domain');
|
||||||
_port = prefs.getString('hassio-port');
|
_port = prefs.getString('hassio-port');
|
||||||
webhookId = prefs.getString('app-webhook-id');
|
webhookId = prefs.getString('app-webhook-id');
|
||||||
|
appIntegrationVersion = prefs.getInt('app-integration-version') ?? 0;
|
||||||
displayHostname = "$_domain:$_port";
|
displayHostname = "$_domain:$_port";
|
||||||
_webSocketAPIEndpoint =
|
_webSocketAPIEndpoint =
|
||||||
"${prefs.getString('hassio-protocol')}://$_domain:$_port/api/websocket";
|
"${prefs.getString('hassio-protocol')}://$_domain:$_port/api/websocket";
|
||||||
|
@ -2,9 +2,11 @@ part of '../main.dart';
|
|||||||
|
|
||||||
class MobileAppIntegrationManager {
|
class MobileAppIntegrationManager {
|
||||||
|
|
||||||
|
static const INTEGRATION_VERSION = 3;
|
||||||
|
|
||||||
static final _appRegistrationData = {
|
static final _appRegistrationData = {
|
||||||
"app_version": "$appVersion",
|
|
||||||
"device_name": "",
|
"device_name": "",
|
||||||
|
"app_version": "$appVersion",
|
||||||
"manufacturer": DeviceInfoManager().manufacturer,
|
"manufacturer": DeviceInfoManager().manufacturer,
|
||||||
"model": DeviceInfoManager().model,
|
"model": DeviceInfoManager().model,
|
||||||
"os_version": DeviceInfoManager().osVersion,
|
"os_version": DeviceInfoManager().osVersion,
|
||||||
@ -22,6 +24,7 @@ class MobileAppIntegrationManager {
|
|||||||
Logger.d("Mobile app was not registered yet or need to be reseted. Registering...");
|
Logger.d("Mobile app was not registered yet or need to be reseted. Registering...");
|
||||||
var registrationData = Map.from(_appRegistrationData);
|
var registrationData = Map.from(_appRegistrationData);
|
||||||
registrationData.addAll({
|
registrationData.addAll({
|
||||||
|
"device_id": "${DeviceInfoManager().unicDeviceId}",
|
||||||
"app_id": "ha_client",
|
"app_id": "ha_client",
|
||||||
"app_name": "$appName",
|
"app_name": "$appName",
|
||||||
"os_name": DeviceInfoManager().osName,
|
"os_name": DeviceInfoManager().osName,
|
||||||
@ -34,9 +37,11 @@ class MobileAppIntegrationManager {
|
|||||||
).then((response) {
|
).then((response) {
|
||||||
Logger.d("Processing registration responce...");
|
Logger.d("Processing registration responce...");
|
||||||
var responseObject = json.decode(response);
|
var responseObject = json.decode(response);
|
||||||
|
ConnectionManager().webhookId = responseObject["webhook_id"];
|
||||||
|
ConnectionManager().appIntegrationVersion = INTEGRATION_VERSION;
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
prefs.setString("app-webhook-id", responseObject["webhook_id"]);
|
prefs.setString("app-webhook-id", responseObject["webhook_id"]);
|
||||||
ConnectionManager().webhookId = responseObject["webhook_id"];
|
prefs.setInt('app-integration-version', INTEGRATION_VERSION);
|
||||||
|
|
||||||
completer.complete();
|
completer.complete();
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
@ -65,18 +70,29 @@ class MobileAppIntegrationManager {
|
|||||||
includeAuthHeader: false,
|
includeAuthHeader: false,
|
||||||
data: json.encode(updateData)
|
data: json.encode(updateData)
|
||||||
).then((response) {
|
).then((response) {
|
||||||
if (response == null || response.isEmpty) {
|
var registrationData;
|
||||||
Logger.d("No registration data in response. MobileApp integration was removed");
|
try {
|
||||||
|
registrationData = json.decode(response);
|
||||||
|
} catch (e) {
|
||||||
|
registrationData = null;
|
||||||
|
}
|
||||||
|
if (registrationData == null || registrationData.isEmpty) {
|
||||||
|
Logger.d("No registration data in response. MobileApp integration was removed or broken");
|
||||||
_askToRegisterApp();
|
_askToRegisterApp();
|
||||||
} else {
|
} else {
|
||||||
Logger.d("App registration works fine");
|
if (INTEGRATION_VERSION > ConnectionManager().appIntegrationVersion) {
|
||||||
if (showOkDialog) {
|
Logger.d('App registration needs to be updated');
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
_askToRemoveAndRegisterApp();
|
||||||
title: "All good",
|
} else {
|
||||||
body: "HA Client integration with your Home Assistant server works fine",
|
Logger.d('App registration works fine');
|
||||||
positiveText: "Nice!",
|
if (showOkDialog) {
|
||||||
negativeText: "Ok"
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
));
|
title: "All good",
|
||||||
|
body: "HA Client integration with your Home Assistant server works fine",
|
||||||
|
positiveText: "Nice!",
|
||||||
|
negativeText: "Ok"
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
completer.complete();
|
completer.complete();
|
||||||
@ -105,10 +121,22 @@ class MobileAppIntegrationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void _askToRemoveAndRegisterApp() {
|
||||||
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
|
title: "Mobile app integration needs to be updated",
|
||||||
|
body: "You need to update HA Client integration to continue using notifications and location tracking. Please remove 'Mobile App' integration for this device from your Home Assistant and restart Home Assistant. Then go back to HA Client to create app integration again.",
|
||||||
|
positiveText: "Ok",
|
||||||
|
negativeText: "Report an issue",
|
||||||
|
onNegative: () {
|
||||||
|
Launcher.launchURL("https://github.com/estevez-dev/ha_client/issues/new");
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
static void _askToRegisterApp() {
|
static void _askToRegisterApp() {
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
title: "App integration was removed",
|
title: "App integration is broken",
|
||||||
body: "Looks like app integration was removed from your Home Assistant. HA Client needs to be registered on your Home Assistant server to make it possible to use notifications and other useful stuff.",
|
body: "Looks like app integration was removed from your Home Assistant or it needs to be updated. HA Client needs to be registered on your Home Assistant server to make it possible to use notifications and location tracking. Please remove 'Mobile App' integration for this device from your Home Assistant before registering and restart Home Assistant. Then go back here.",
|
||||||
positiveText: "Register now",
|
positiveText: "Register now",
|
||||||
negativeText: "Cancel",
|
negativeText: "Cancel",
|
||||||
onPositive: () {
|
onPositive: () {
|
||||||
|
@ -12,61 +12,52 @@ class EntityViewPage extends StatefulWidget {
|
|||||||
class _EntityViewPageState extends State<EntityViewPage> {
|
class _EntityViewPageState extends State<EntityViewPage> {
|
||||||
StreamSubscription _refreshDataSubscription;
|
StreamSubscription _refreshDataSubscription;
|
||||||
StreamSubscription _stateSubscription;
|
StreamSubscription _stateSubscription;
|
||||||
Entity entity;
|
Entity _entity;
|
||||||
Entity forwardToMainPage;
|
|
||||||
bool _popScheduled = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
_stateSubscription = eventBus.on<StateChangedEvent>().listen((event) {
|
||||||
if (event.entityId == widget.entityId) {
|
if (event.entityId == widget.entityId) {
|
||||||
entity = HomeAssistant().entities.get(widget.entityId);
|
|
||||||
Logger.d("[Entity page] State change event handled: ${event.entityId}");
|
Logger.d("[Entity page] State change event handled: ${event.entityId}");
|
||||||
setState(() {});
|
setState(() {
|
||||||
|
_getEntity();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
_refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) {
|
||||||
entity = HomeAssistant().entities.get(widget.entityId);
|
Logger.d("[Entity page] Refresh data event handled");
|
||||||
setState(() {});
|
setState(() {
|
||||||
|
_getEntity();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
entity = HomeAssistant().entities.get(widget.entityId);
|
_getEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getEntity() {
|
||||||
|
_entity = HomeAssistant().entities.get(widget.entityId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget body;
|
String entityNameToDisplay = '${(_entity?.displayName ?? widget.entityId) ?? ''}';
|
||||||
if (MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
|
|
||||||
if (!_popScheduled) {
|
|
||||||
_popScheduled = true;
|
|
||||||
_popAfterBuild();
|
|
||||||
}
|
|
||||||
body = PageLoadingIndicator();
|
|
||||||
} else {
|
|
||||||
body = EntityPageLayout(entity: entity);
|
|
||||||
}
|
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
appBar: new AppBar(
|
appBar: new AppBar(
|
||||||
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}),
|
}),
|
||||||
title: new Text("${entity.displayName}"),
|
title: new Text(entityNameToDisplay),
|
||||||
),
|
),
|
||||||
body: body,
|
body: _entity == null ? PageLoadingError(
|
||||||
|
errorText: 'Entity is not available $entityNameToDisplay',
|
||||||
|
) : EntityPageLayout(entity: _entity),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_popAfterBuild() async {
|
|
||||||
forwardToMainPage = entity;
|
|
||||||
await Future.delayed(Duration(milliseconds: 300));
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose(){
|
void dispose(){
|
||||||
if (_stateSubscription != null) _stateSubscription.cancel();
|
if (_stateSubscription != null) _stateSubscription.cancel();
|
||||||
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
if (_refreshDataSubscription != null) _refreshDataSubscription.cancel();
|
||||||
eventBus.fire(ShowEntityPageEvent(entity: forwardToMainPage));
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,7 +25,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
int _previousViewCount;
|
int _previousViewCount;
|
||||||
bool _showLoginButton = false;
|
bool _showLoginButton = false;
|
||||||
bool _preventAppRefresh = false;
|
bool _preventAppRefresh = false;
|
||||||
Entity _entityToShow;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -125,9 +124,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
}
|
}
|
||||||
await HomeAssistant().fetchData(uiOnly).then((_) {
|
await HomeAssistant().fetchData(uiOnly).then((_) {
|
||||||
_hideBottomBar();
|
_hideBottomBar();
|
||||||
if (_entityToShow != null) {
|
|
||||||
_entityToShow = HomeAssistant().entities.get(_entityToShow.entityId);
|
|
||||||
}
|
|
||||||
}).catchError((e) {
|
}).catchError((e) {
|
||||||
if (e is HAError) {
|
if (e is HAError) {
|
||||||
_setErrorState(e);
|
_setErrorState(e);
|
||||||
@ -302,12 +298,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showEntityPage(String entityId) {
|
void _showEntityPage(String entityId) {
|
||||||
setState(() {
|
Navigator.push(
|
||||||
_entityToShow = HomeAssistant().entities?.get(entityId);
|
context,
|
||||||
if (_entityToShow != null) {
|
MaterialPageRoute(
|
||||||
_mainScrollController?.jumpTo(0);
|
builder: (context) => EntityViewPage(entityId: entityId),
|
||||||
}
|
)
|
||||||
});
|
);
|
||||||
/*if (_entityToShow!= null && MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
|
/*if (_entityToShow!= null && MediaQuery.of(context).size.width < Sizes.tabletMinWidth) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@ -591,7 +587,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
}
|
}
|
||||||
|
|
||||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||||
final ScrollController _mainScrollController = ScrollController();
|
|
||||||
|
|
||||||
Widget _buildScaffoldBody(bool empty) {
|
Widget _buildScaffoldBody(bool empty) {
|
||||||
List<PopupMenuItem<String>> serviceMenuItems = [];
|
List<PopupMenuItem<String>> serviceMenuItems = [];
|
||||||
@ -697,28 +692,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_entityToShow != null && MediaQuery.of(context).size.width >= Sizes.tabletMinWidth) {
|
mainScrollBody = HomeAssistant().ui.build(context, _viewsTabController);
|
||||||
mainScrollBody = Flex(
|
|
||||||
direction: Axis.horizontal,
|
|
||||||
children: <Widget>[
|
|
||||||
Expanded(
|
|
||||||
child: HomeAssistant().ui.build(context, _viewsTabController),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
width: Sizes.mainPageScreenSeparatorWidth,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
ConstrainedBox(
|
|
||||||
constraints: BoxConstraints.tightFor(width: Sizes.entityPageMaxWidth),
|
|
||||||
child: EntityPageLayout(entity: _entityToShow, showClose: true,),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
} else if (_entityToShow != null) {
|
|
||||||
mainScrollBody = EntityPageLayout(entity: _entityToShow, showClose: true,);
|
|
||||||
} else {
|
|
||||||
mainScrollBody = HomeAssistant().ui.build(context, _viewsTabController);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NestedScrollView(
|
return NestedScrollView(
|
||||||
@ -774,7 +748,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
_scaffoldKey.currentState.openDrawer();
|
_scaffoldKey.currentState.openDrawer();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
bottom: (empty || _entityToShow != null) ? null : TabBar(
|
bottom: empty ? null : TabBar(
|
||||||
controller: _viewsTabController,
|
controller: _viewsTabController,
|
||||||
tabs: buildUIViewTabs(),
|
tabs: buildUIViewTabs(),
|
||||||
isScrollable: true,
|
isScrollable: true,
|
||||||
@ -784,7 +758,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: mainScrollBody,
|
body: mainScrollBody,
|
||||||
controller: _mainScrollController,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -849,22 +822,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
body: _buildScaffoldBody(true)
|
body: _buildScaffoldBody(true)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return WillPopScope(
|
return Scaffold(
|
||||||
child: Scaffold(
|
key: _scaffoldKey,
|
||||||
key: _scaffoldKey,
|
drawer: _buildAppDrawer(),
|
||||||
drawer: _buildAppDrawer(),
|
primary: false,
|
||||||
primary: false,
|
bottomNavigationBar: bottomBar,
|
||||||
bottomNavigationBar: bottomBar,
|
body: _buildScaffoldBody(false)
|
||||||
body: _buildScaffoldBody(false)
|
|
||||||
),
|
|
||||||
onWillPop: () {
|
|
||||||
if (_entityToShow != null) {
|
|
||||||
eventBus.fire(ShowEntityPageEvent());
|
|
||||||
return Future.value(false);
|
|
||||||
} else {
|
|
||||||
return Future.value(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ class _PurchasePageState extends State<PurchasePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProducts() {
|
List<Widget> _buildProducts() {
|
||||||
List<Widget> productWidgets = [];
|
List<Widget> productWidgets = [];
|
||||||
for (ProductDetails product in _products) {
|
for (ProductDetails product in _products) {
|
||||||
productWidgets.add(
|
productWidgets.add(
|
||||||
@ -72,10 +72,7 @@ class _PurchasePageState extends State<PurchasePage> {
|
|||||||
purchased: _purchases.any((purchase) { return purchase.productID == product.id;}),)
|
purchased: _purchases.any((purchase) { return purchase.productID == product.id;}),)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ListView(
|
return productWidgets;
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
children: productWidgets
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _buyProduct(ProductDetails product) {
|
void _buyProduct(ProductDetails product) {
|
||||||
@ -87,12 +84,28 @@ class _PurchasePageState extends State<PurchasePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget body;
|
List<Widget> body;
|
||||||
if (!_loaded) {
|
if (!_loaded) {
|
||||||
body = _error.isEmpty ? PageLoadingIndicator() : PageLoadingError(errorText: _error);
|
body = [_error.isEmpty ? PageLoadingIndicator() : PageLoadingError(errorText: _error)];
|
||||||
} else {
|
} else {
|
||||||
body = _buildProducts();
|
body = _buildProducts();
|
||||||
}
|
}
|
||||||
|
body.add(
|
||||||
|
Card(
|
||||||
|
child: Container(
|
||||||
|
height: 80,
|
||||||
|
child: InkWell(
|
||||||
|
child: Image.network('https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif'),
|
||||||
|
onTap: () {
|
||||||
|
Launcher.launchURLInCustomTab(
|
||||||
|
context: context,
|
||||||
|
url: 'https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ARWGETZD2D83Q&source=url'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
return new Scaffold(
|
return new Scaffold(
|
||||||
appBar: new AppBar(
|
appBar: new AppBar(
|
||||||
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
||||||
@ -100,7 +113,10 @@ class _PurchasePageState extends State<PurchasePage> {
|
|||||||
}),
|
}),
|
||||||
title: new Text(widget.title),
|
title: new Text(widget.title),
|
||||||
),
|
),
|
||||||
body: body,
|
body: ListView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
children: body
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ class MaterialDesignIcons {
|
|||||||
"binary_sensor": "mdi:checkbox-blank-circle-outline",
|
"binary_sensor": "mdi:checkbox-blank-circle-outline",
|
||||||
"group": "mdi:google-circles-communities",
|
"group": "mdi:google-circles-communities",
|
||||||
"sensor": "mdi:eye",
|
"sensor": "mdi:eye",
|
||||||
"automation": "mdi:playlist-play",
|
"automation": "mdi:robot",
|
||||||
"script": "mdi:file-document",
|
"script": "mdi:script-text",
|
||||||
"input_boolean": "mdi:drawing",
|
"input_boolean": "mdi:toggle-switch-outline",
|
||||||
"input_datetime": "mdi:clock",
|
"input_datetime": "mdi:clock",
|
||||||
"input_number": "mdi:ray-vertex",
|
"input_number": "mdi:ray-vertex",
|
||||||
"input_select": "mdi:format-list-bulleted",
|
"input_select": "mdi:format-list-bulleted",
|
||||||
|
@ -48,7 +48,7 @@ class HAView {
|
|||||||
type: rawCardInfo['type'] ?? CardType.ENTITIES,
|
type: rawCardInfo['type'] ?? CardType.ENTITIES,
|
||||||
columnsCount: rawCardInfo['columns'] ?? 4,
|
columnsCount: rawCardInfo['columns'] ?? 4,
|
||||||
showName: (rawCardInfo['show_name'] ?? rawCard['show_name']) ?? true,
|
showName: (rawCardInfo['show_name'] ?? rawCard['show_name']) ?? true,
|
||||||
showHeaderToggle: (rawCardInfo['show_header_toggle'] ?? rawCard['show_header_toggle']) ?? true,
|
showHeaderToggle: (rawCardInfo['show_header_toggle'] ?? rawCard['show_header_toggle']) ?? false,
|
||||||
showState: (rawCardInfo['show_state'] ?? rawCard['show_state']) ?? true,
|
showState: (rawCardInfo['show_state'] ?? rawCard['show_state']) ?? true,
|
||||||
showEmpty: (rawCardInfo['show_empty'] ?? rawCard['show_empty']) ?? true,
|
showEmpty: (rawCardInfo['show_empty'] ?? rawCard['show_empty']) ?? true,
|
||||||
stateFilter: (rawCard['state_filter'] ?? rawCardInfo['state_filter']) ?? [],
|
stateFilter: (rawCard['state_filter'] ?? rawCardInfo['state_filter']) ?? [],
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: hass_client
|
name: hass_client
|
||||||
description: Home Assistant Android Client
|
description: Home Assistant Android Client
|
||||||
|
|
||||||
version: 0.8.3+890
|
version: 0.8.4+892
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
Reference in New Issue
Block a user