Compare commits
44 Commits
beta/0.8.1
...
beta/0.8.4
Author | SHA1 | Date | |
---|---|---|---|
6afbd37d71 | |||
0e8869878f | |||
7c2cfe3215 | |||
c376c0e952 | |||
da5f663396 | |||
0e92418a33 | |||
2eef7cfe5e | |||
de4e0bfb3a | |||
8bf2d31e72 | |||
2125c46143 | |||
5402eb84df | |||
ad5aa0898f | |||
040d40b614 | |||
8e58f22c56 | |||
c91695fbe5 | |||
c43741da49 | |||
f2563a0397 | |||
fba4017819 | |||
5f23e108a1 | |||
68d14bd13d | |||
022622522f | |||
89513ca4e5 | |||
a934ee2335 | |||
49aeea634f | |||
e18b9ebe14 | |||
08ee3f3d80 | |||
62d07bf8b9 | |||
ab398cbdc3 | |||
007d12719c | |||
524d195800 | |||
405de64249 | |||
f53554702e | |||
379e1a4a7e | |||
d6f7096055 | |||
37c721e4f6 | |||
d94235ef6d | |||
eb4184713f | |||
a0a0cb4612 | |||
f448a20784 | |||
36eff26862 | |||
5b2a1163b9 | |||
4432124e8c | |||
b8ba3c59e9 | |||
c40a496b6b |
2
.gitignore
vendored
2
.gitignore
vendored
@ -18,5 +18,5 @@ flutter_export_environment.sh
|
|||||||
.flutter-plugins-dependencies
|
.flutter-plugins-dependencies
|
||||||
|
|
||||||
key.properties
|
key.properties
|
||||||
premium_features_manager.class.dart
|
.secrets.dart
|
||||||
pubspec.lock
|
pubspec.lock
|
||||||
|
@ -3,7 +3,7 @@ image:
|
|||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- before: |
|
- before: |
|
||||||
export PATH=$FLUTTER_HOME/bin:$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH
|
export PATH=$FLUTTER_HOME/bin:$FLUTTER_HOME/bin/cache/dart-sdk/bin:$ANDROID_HOME/bin:$ANDROID_HOME/platform-tools:$PATH
|
||||||
mkdir -p /home/gitpod/.android
|
mkdir -p /home/gitpod/.android
|
||||||
touch /home/gitpod/.android/repositories.cfg
|
touch /home/gitpod/.android/repositories.cfg
|
||||||
init: |
|
init: |
|
||||||
|
@ -6,7 +6,7 @@ Visit [ha-client.app](http://ha-client.app/) for more info.
|
|||||||
|
|
||||||
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient)
|
Download the app from [Google Play](https://play.google.com/apps/testing/com.keyboardcrumbs.haclient)
|
||||||
|
|
||||||
Discuss it on [Spectrum.chat](https://spectrum.chat/ha-client) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android)
|
Discuss it on [Discord](https://discord.gg/nd6FZQ) or at [Home Assistant community](https://community.home-assistant.io/c/mobile-apps/ha-client-android)
|
||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
|
[](https://gitpod.io/#https://github.com/estevez-dev/ha_client)
|
||||||
|
|
||||||
|
61
assets/html/cameraLiveView.html
Normal file
61
assets/html/cameraLiveView.html
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
widows: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
var messageChannel = '{{message_channel}}';
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<video id="screen" width="100%" controls></video>
|
||||||
|
<script>
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
var video = document.getElementById('screen');
|
||||||
|
var hls = new Hls();
|
||||||
|
hls.on(Hls.Events.ERROR, function (event, data) {
|
||||||
|
if (data.fatal) {
|
||||||
|
switch(data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
// try to recover network error
|
||||||
|
console.log("fatal network error encountered, try to recover");
|
||||||
|
hls.startLoad();
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
console.log("fatal media error encountered, try to recover");
|
||||||
|
hls.recoverMediaError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// cannot recover
|
||||||
|
hls.destroy();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// bind them together
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MEDIA_ATTACHED, function () {
|
||||||
|
console.log("video and hls.js are now bound together !");
|
||||||
|
hls.loadSource("{{stream_url}}");
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
|
||||||
|
console.log("manifest loaded, found " + data.levels.length + " quality level");
|
||||||
|
video.play();
|
||||||
|
video.onloadedmetadata = function() {
|
||||||
|
window[messageChannel].postMessage(document.body.clientWidth / video.offsetHeight);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -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,
|
||||||
@ -198,9 +192,6 @@ class CardWidget extends StatelessWidget {
|
|||||||
body.add(CardHeader(
|
body.add(CardHeader(
|
||||||
name: card.name ?? "",
|
name: card.name ?? "",
|
||||||
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.grey
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
trailing: Row(
|
trailing: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -18,7 +18,7 @@ class CardHeader extends StatelessWidget {
|
|||||||
title: Text("$name",
|
title: Text("$name",
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
style: Theme.of(context).textTheme.headline),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = new Container(width: 0.0, height: 0.0);
|
result = new Container(width: 0.0, height: 0.0);
|
||||||
|
@ -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) {
|
||||||
@ -48,8 +49,7 @@ class EntityButtonCardBody extends StatelessWidget {
|
|||||||
textOverflow: TextOverflow.ellipsis,
|
textOverflow: TextOverflow.ellipsis,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
wordsWrap: true,
|
wordsWrap: true,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center
|
||||||
fontSize: Sizes.nameFontSize,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container(width: 0, height: 0);
|
return Container(width: 0, height: 0);
|
||||||
|
@ -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;
|
||||||
@ -9,146 +9,169 @@ 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
|
@override
|
||||||
_GaugeCardBodyState createState() => _GaugeCardBodyState();
|
Widget build(BuildContext context) {
|
||||||
}
|
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
|
|
||||||
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
|
||||||
|
|
||||||
List<charts.Series> seriesList;
|
|
||||||
|
|
||||||
List<charts.Series<GaugeSegment, String>> _createData(double value) {
|
|
||||||
double fixedValue;
|
double fixedValue;
|
||||||
if (value > widget.max) {
|
double value = entityWrapper.entity.doubleState;
|
||||||
fixedValue = widget.max.toDouble();
|
if (value > max) {
|
||||||
} else if (value < widget.min) {
|
fixedValue = max.toDouble();
|
||||||
fixedValue = widget.min.toDouble();
|
} else if (value < min) {
|
||||||
|
fixedValue = min.toDouble();
|
||||||
} else {
|
} else {
|
||||||
fixedValue = value;
|
fixedValue = value;
|
||||||
}
|
}
|
||||||
double toShow = ((fixedValue - widget.min) / (widget.max - widget.min)) * 100;
|
|
||||||
Color mainColor;
|
List<GaugeRange> ranges;
|
||||||
if (widget.severity != null) {
|
Color currentColor;
|
||||||
if (widget.severity["red"] is int && fixedValue >= widget.severity["red"]) {
|
if (severity != null && severity["green"] is int && severity["red"] is int && severity["yellow"] is int) {
|
||||||
mainColor = Colors.red;
|
List<RangeContainer> rangesList = <RangeContainer>[
|
||||||
} else if (widget.severity["yellow"] is int && fixedValue >= widget.severity["yellow"]) {
|
RangeContainer(severity["green"], HAClientTheme().getGreenGaugeColor()),
|
||||||
mainColor = Colors.amber;
|
RangeContainer(severity["red"], HAClientTheme().getRedGaugeColor()),
|
||||||
|
RangeContainer(severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
||||||
|
];
|
||||||
|
rangesList.sort((current, next) {
|
||||||
|
if (current.startFrom > next.startFrom) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (current.startFrom < next.startFrom) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
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 {
|
} else {
|
||||||
mainColor = Colors.green;
|
currentColor = rangesList[2].color;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
mainColor = Colors.green;
|
ranges = [
|
||||||
|
GaugeRange(
|
||||||
|
startValue: rangesList[0].startFrom.toDouble(),
|
||||||
|
endValue: rangesList[1].startFrom.toDouble(),
|
||||||
|
color: rangesList[0].color.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3
|
||||||
|
),
|
||||||
|
GaugeRange(
|
||||||
|
startValue: rangesList[1].startFrom.toDouble(),
|
||||||
|
endValue: rangesList[2].startFrom.toDouble(),
|
||||||
|
color: rangesList[1].color.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3
|
||||||
|
),
|
||||||
|
GaugeRange(
|
||||||
|
startValue: rangesList[2].startFrom.toDouble(),
|
||||||
|
endValue: max.toDouble(),
|
||||||
|
color: rangesList[2].color.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3
|
||||||
|
)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if (ranges == null) {
|
||||||
|
currentColor = Theme.of(context).primaryColorDark;
|
||||||
|
ranges = <GaugeRange>[
|
||||||
|
GaugeRange(
|
||||||
|
startValue: min.toDouble(),
|
||||||
|
endValue: max.toDouble(),
|
||||||
|
color: Theme.of(context).primaryColorDark.withOpacity(0.1),
|
||||||
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
|
endWidth: 0.3,
|
||||||
|
startWidth: 0.3,
|
||||||
|
)
|
||||||
|
];
|
||||||
}
|
}
|
||||||
final data = [
|
|
||||||
GaugeSegment('Main', toShow, mainColor),
|
|
||||||
GaugeSegment('Rest', 100 - toShow, Colors.black45),
|
|
||||||
];
|
|
||||||
|
|
||||||
return [
|
|
||||||
charts.Series<GaugeSegment, String>(
|
|
||||||
id: 'Segments',
|
|
||||||
domainFn: (GaugeSegment segment, _) => segment.segment,
|
|
||||||
measureFn: (GaugeSegment segment, _) => segment.value,
|
|
||||||
colorFn: (GaugeSegment segment, _) => segment.color,
|
|
||||||
// Set a label accessor to control the text of the arc label.
|
|
||||||
labelAccessorFn: (GaugeSegment segment, _) =>
|
|
||||||
segment.segment == 'Main' ? '${segment.value}' : null,
|
|
||||||
data: data,
|
|
||||||
)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => entityWrapper.handleTap(),
|
onTap: () => entityWrapper.handleTap(),
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
onLongPress: () => entityWrapper.handleHold(),
|
||||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1.5,
|
aspectRatio: 2,
|
||||||
child: Stack(
|
child: LayoutBuilder(
|
||||||
fit: StackFit.expand,
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
overflow: Overflow.clip,
|
double fontSizeFactor;
|
||||||
children: [
|
if (constraints.maxWidth > 300.0) {
|
||||||
LayoutBuilder(
|
fontSizeFactor = 1.6;
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
} else if (constraints.maxWidth > 150.0) {
|
||||||
double verticalOffset;
|
fontSizeFactor = 1;
|
||||||
if(constraints.maxWidth > 150.0) {
|
} else if (constraints.maxWidth > 100.0) {
|
||||||
verticalOffset = 0.2;
|
fontSizeFactor = 0.6;
|
||||||
} else if (constraints.maxWidth > 100.0) {
|
} else {
|
||||||
verticalOffset = 0.3;
|
fontSizeFactor = 0.4;
|
||||||
} else {
|
}
|
||||||
verticalOffset = 0.3;
|
return SfRadialGauge(
|
||||||
}
|
axes: <RadialAxis>[
|
||||||
return FractionallySizedBox(
|
RadialAxis(
|
||||||
heightFactor: 2,
|
maximum: max.toDouble(),
|
||||||
widthFactor: 1,
|
minimum: min.toDouble(),
|
||||||
alignment: FractionalOffset(0,verticalOffset),
|
showLabels: false,
|
||||||
child: charts.PieChart(
|
useRangeColorForAxis: true,
|
||||||
_createData(entityWrapper.entity.doubleState),
|
showTicks: false,
|
||||||
animate: false,
|
canScaleToFit: true,
|
||||||
defaultRenderer: charts.ArcRendererConfig(
|
ranges: ranges,
|
||||||
arcRatio: 0.4,
|
axisLineStyle: AxisLineStyle(
|
||||||
startAngle: pi,
|
thickness: 0.3,
|
||||||
arcLength: pi,
|
thicknessUnit: GaugeSizeUnit.factor,
|
||||||
),
|
color: Colors.transparent
|
||||||
|
),
|
||||||
|
annotations: <GaugeAnnotation>[
|
||||||
|
GaugeAnnotation(
|
||||||
|
angle: -90,
|
||||||
|
positionFactor: 1.3,
|
||||||
|
//verticalAlignment: GaugeAlignment.far,
|
||||||
|
widget: EntityName(
|
||||||
|
textStyle: Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
fontSize: Theme.of(context).textTheme.body1.fontSize * fontSizeFactor
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
),
|
||||||
),
|
GaugeAnnotation(
|
||||||
Align(
|
angle: 180,
|
||||||
alignment: Alignment.bottomCenter,
|
positionFactor: 0,
|
||||||
child: LayoutBuilder(
|
verticalAlignment: GaugeAlignment.center,
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
widget: SimpleEntityState(
|
||||||
double fontSize = constraints.maxHeight / 7;
|
expanded: false,
|
||||||
return Padding(
|
maxLines: 1,
|
||||||
padding: EdgeInsets.only(bottom: 2*fontSize),
|
textAlign: TextAlign.center,
|
||||||
child: SimpleEntityState(
|
textStyle: Theme.of(context).textTheme.title.copyWith(
|
||||||
//textAlign: TextAlign.center,
|
fontSize: Theme.of(context).textTheme.title.fontSize * fontSizeFactor,
|
||||||
expanded: false,
|
),
|
||||||
maxLines: 1,
|
),
|
||||||
bold: true,
|
)
|
||||||
textAlign: TextAlign.center,
|
],
|
||||||
padding: EdgeInsets.all(0.0),
|
startAngle: 180,
|
||||||
fontSize: fontSize,
|
endAngle: 0,
|
||||||
//padding: EdgeInsets.only(top: Sizes.rowPadding),
|
pointers: <GaugePointer>[
|
||||||
),
|
RangePointer(
|
||||||
);
|
value: fixedValue,
|
||||||
}
|
sizeUnit: GaugeSizeUnit.factor,
|
||||||
),
|
width: 0.3,
|
||||||
),
|
color: currentColor,
|
||||||
Align(
|
enableAnimation: true,
|
||||||
alignment: Alignment.bottomCenter,
|
animationType: AnimationType.bounceOut,
|
||||||
child: LayoutBuilder(
|
)
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
]
|
||||||
double fontSize = constraints.maxHeight / 7;
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: fontSize),
|
|
||||||
child: EntityName(
|
|
||||||
fontSize: fontSize,
|
|
||||||
maxLines: 1,
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
textOverflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
]
|
],
|
||||||
)
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class GaugeSegment {
|
class RangeContainer {
|
||||||
final String segment;
|
final int startFrom;
|
||||||
final double value;
|
Color color;
|
||||||
final charts.Color color;
|
|
||||||
|
|
||||||
GaugeSegment(this.segment, this.value, Color color)
|
RangeContainer(this.startFrom, this.color);
|
||||||
: this.color = charts.Color(
|
|
||||||
r: color.red, g: color.green, b: color.blue, a: color.alpha);
|
|
||||||
}
|
}
|
@ -6,7 +6,6 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
|||||||
final bool showState;
|
final bool showState;
|
||||||
final bool nameInTheBottom;
|
final bool nameInTheBottom;
|
||||||
final double iconSize;
|
final double iconSize;
|
||||||
final double nameFontSize;
|
|
||||||
final bool wordsWrapInName;
|
final bool wordsWrapInName;
|
||||||
|
|
||||||
GlanceCardEntityContainer({
|
GlanceCardEntityContainer({
|
||||||
@ -15,7 +14,6 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
|||||||
@required this.showState,
|
@required this.showState,
|
||||||
this.nameInTheBottom: false,
|
this.nameInTheBottom: false,
|
||||||
this.iconSize: Sizes.iconSize,
|
this.iconSize: Sizes.iconSize,
|
||||||
this.nameFontSize: Sizes.smallFontSize,
|
|
||||||
this.wordsWrapInName: false
|
this.wordsWrapInName: false
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -31,7 +29,7 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
|||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
if (!nameInTheBottom) {
|
if (!nameInTheBottom) {
|
||||||
if (showName) {
|
if (showName) {
|
||||||
result.add(_buildName());
|
result.add(_buildName(context));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (showState) {
|
if (showState) {
|
||||||
@ -49,7 +47,7 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
|||||||
result.add(_buildState());
|
result.add(_buildState());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.add(_buildName());
|
result.add(_buildName(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
@ -65,13 +63,13 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildName() {
|
Widget _buildName(BuildContext context) {
|
||||||
return EntityName(
|
return EntityName(
|
||||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||||
textOverflow: TextOverflow.ellipsis,
|
textOverflow: TextOverflow.ellipsis,
|
||||||
wordsWrap: wordsWrapInName,
|
wordsWrap: wordsWrapInName,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
fontSize: nameFontSize,
|
textStyle: Theme.of(context).textTheme.body1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,58 +34,5 @@ class _LightCardBodyState extends State<LightCardBody> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return InkWell(
|
|
||||||
onTap: () => entityWrapper.handleTap(),
|
|
||||||
onLongPress: () => entityWrapper.handleHold(),
|
|
||||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
|
||||||
child: AspectRatio(
|
|
||||||
aspectRatio: 1.5,
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
overflow: Overflow.clip,
|
|
||||||
children: [
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
double fontSize = constraints.maxHeight / 7;
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: 2*fontSize),
|
|
||||||
child: SimpleEntityState(
|
|
||||||
//textAlign: TextAlign.center,
|
|
||||||
expanded: false,
|
|
||||||
maxLines: 1,
|
|
||||||
bold: true,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
fontSize: fontSize,
|
|
||||||
//padding: EdgeInsets.only(top: Sizes.rowPadding),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Align(
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (BuildContext context, BoxConstraints constraints) {
|
|
||||||
double fontSize = constraints.maxHeight / 7;
|
|
||||||
return Padding(
|
|
||||||
padding: EdgeInsets.only(bottom: fontSize),
|
|
||||||
child: EntityName(
|
|
||||||
fontSize: fontSize,
|
|
||||||
maxLines: 1,
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
textOverflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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";
|
||||||
@ -126,10 +127,10 @@ class Sizes {
|
|||||||
static const extendedWidgetHeight = 50.0;
|
static const extendedWidgetHeight = 50.0;
|
||||||
static const iconSize = 28.0;
|
static const iconSize = 28.0;
|
||||||
static const largeIconSize = 46.0;
|
static const largeIconSize = 46.0;
|
||||||
static const stateFontSize = 15.0;
|
//static const stateFontSize = 15.0;
|
||||||
static const nameFontSize = 15.0;
|
//static const nameFontSize = 15.0;
|
||||||
static const smallFontSize = 14.0;
|
//static const smallFontSize = 14.0;
|
||||||
static const largeFontSize = 24.0;
|
//static const largeFontSize = 24.0;
|
||||||
static const inputWidth = 160.0;
|
static const inputWidth = 160.0;
|
||||||
static const rowPadding = 10.0;
|
static const rowPadding = 10.0;
|
||||||
static const doubleRowPadding = rowPadding*2;
|
static const doubleRowPadding = rowPadding*2;
|
||||||
|
@ -248,7 +248,9 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
|||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
"TRIGGER",
|
"TRIGGER",
|
||||||
style: TextStyle(color: Colors.redAccent)
|
style: Theme.of(context).textTheme.subhead.copyWith(
|
||||||
|
color: Theme.of(context).errorColor
|
||||||
|
)
|
||||||
),
|
),
|
||||||
onPressed: () => _askToTrigger(entity),
|
onPressed: () => _askToTrigger(entity),
|
||||||
)
|
)
|
||||||
|
@ -7,8 +7,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
double iconSize = 26.0;
|
double iconSize = 26.0;
|
||||||
Widget badgeIcon;
|
Widget badgeIcon;
|
||||||
String onBadgeTextValue;
|
String onBadgeTextValue;
|
||||||
Color iconColor = EntityColor.badgeColors[entityModel.entityWrapper.entity.domain] ??
|
Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain);
|
||||||
EntityColor.badgeColors["default"];
|
|
||||||
switch (entityModel.entityWrapper.entity.domain) {
|
switch (entityModel.entityWrapper.entity.domain) {
|
||||||
case "sun":
|
case "sun":
|
||||||
{
|
{
|
||||||
@ -30,7 +29,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
badgeIcon = EntityIcon(
|
badgeIcon = EntityIcon(
|
||||||
padding: EdgeInsets.all(0.0),
|
padding: EdgeInsets.all(0.0),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
color: Colors.black
|
color: Theme.of(context).textTheme.body1.color
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -40,7 +39,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
badgeIcon = EntityIcon(
|
badgeIcon = EntityIcon(
|
||||||
padding: EdgeInsets.all(0.0),
|
padding: EdgeInsets.all(0.0),
|
||||||
size: iconSize,
|
size: iconSize,
|
||||||
color: Colors.black
|
color: Theme.of(context).textTheme.body1.color
|
||||||
);
|
);
|
||||||
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
||||||
break;
|
break;
|
||||||
@ -64,7 +63,9 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
overflow: TextOverflow.fade,
|
overflow: TextOverflow.fade,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: stateFontSize),
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
fontSize: stateFontSize
|
||||||
|
)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@ -77,7 +78,9 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
onBadgeText = Container(
|
onBadgeText = Container(
|
||||||
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
||||||
child: Text("$onBadgeTextValue",
|
child: Text("$onBadgeTextValue",
|
||||||
style: TextStyle(fontSize: 12.0, color: Colors.white),
|
style: Theme.of(context).textTheme.overline.copyWith(
|
||||||
|
color: HAClientTheme().getOnBadgeTextColor()
|
||||||
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
overflow: TextOverflow.fade),
|
overflow: TextOverflow.fade),
|
||||||
@ -98,7 +101,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
decoration: new BoxDecoration(
|
decoration: new BoxDecoration(
|
||||||
// Circle shape
|
// Circle shape
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: Colors.white,
|
color: Theme.of(context).cardColor,
|
||||||
// The border you want
|
// The border you want
|
||||||
border: new Border.all(
|
border: new Border.all(
|
||||||
width: 2.0,
|
width: 2.0,
|
||||||
@ -131,7 +134,7 @@ class BadgeWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
"${entityModel.entityWrapper.displayName}",
|
"${entityModel.entityWrapper.displayName}",
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 12.0),
|
style: Theme.of(context).textTheme.caption,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: 3,
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
@ -14,8 +14,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
|
|
||||||
CameraEntity _entity;
|
CameraEntity _entity;
|
||||||
String _streamUrl = "";
|
String _streamUrl = "";
|
||||||
VideoPlayerController _videoPlayerController;
|
|
||||||
Timer _monitorTimer;
|
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
double _aspectRatio = 1.33;
|
double _aspectRatio = 1.33;
|
||||||
String _webViewHtml;
|
String _webViewHtml;
|
||||||
@ -41,64 +39,45 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
if (_entity.supportStream) {
|
if (_entity.supportStream) {
|
||||||
HomeAssistant().getCameraStream(_entity.entityId)
|
HomeAssistant().getCameraStream(_entity.entityId)
|
||||||
.then((data) {
|
.then((data) {
|
||||||
if (_videoPlayerController != null) {
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
||||||
_videoPlayerController.dispose().then((_) => createPlayer(data));
|
rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
|
||||||
} else {
|
_webViewHtml = Uri.dataFromString(
|
||||||
createPlayer(data);
|
file.replaceFirst('{{stream_url}}', '${ConnectionManager().httpWebHost}${data["url"]}').replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
||||||
}
|
mimeType: 'text/html',
|
||||||
|
encoding: Encoding.getByName('utf-8')
|
||||||
|
).toString();
|
||||||
|
_loading.complete();
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.catchError((e) {
|
.catchError((e) {
|
||||||
_loading.completeError(e);
|
if (e == 'start_stream_failed') {
|
||||||
Logger.e("[Camera Player] $e");
|
Logger.e("[Camera Player] Home Assistant failed starting stream. Forcing MJPEG: $e");
|
||||||
|
_loadMJPEG().then((_) {
|
||||||
|
_loading.complete();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_loading.completeError(e);
|
||||||
|
Logger.e("[Camera Player] Error loading stream: $e");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_streamUrl = '${ConnectionManager().httpWebHost}/api/camera_proxy_stream/${_entity
|
_loadMJPEG().then((_) {
|
||||||
.entityId}?token=${_entity.attributes['access_token']}';
|
|
||||||
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
|
||||||
rootBundle.loadString('assets/html/cameraView.html').then((file) {
|
|
||||||
_webViewHtml = Uri.dataFromString(
|
|
||||||
file.replaceFirst('{{stream_url}}', _streamUrl).replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
|
||||||
mimeType: 'text/html',
|
|
||||||
encoding: Encoding.getByName('utf-8')
|
|
||||||
).toString();
|
|
||||||
_loading.complete();
|
_loading.complete();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return _loading.future;
|
return _loading.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
void createPlayer(data) {
|
Future _loadMJPEG() async {
|
||||||
_videoPlayerController = VideoPlayerController.network("${ConnectionManager().httpWebHost}${data["url"]}");
|
_streamUrl = '${ConnectionManager().httpWebHost}/api/camera_proxy_stream/${_entity
|
||||||
_videoPlayerController.initialize().then((_) {
|
.entityId}?token=${_entity.attributes['access_token']}';
|
||||||
setState((){
|
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
||||||
_aspectRatio = _videoPlayerController.value.aspectRatio;
|
var file = await rootBundle.loadString('assets/html/cameraView.html');
|
||||||
});
|
_webViewHtml = Uri.dataFromString(
|
||||||
_loading.complete();
|
file.replaceFirst('{{stream_url}}', _streamUrl).replaceFirst('{{message_channel}}', _jsMessageChannelName),
|
||||||
autoPlay();
|
mimeType: 'text/html',
|
||||||
startMonitor();
|
encoding: Encoding.getByName('utf-8')
|
||||||
}).catchError((e) {
|
).toString();
|
||||||
_loading.completeError(e);
|
|
||||||
Logger.e("[Camera Player] Error player init. Retrying");
|
|
||||||
_loadResources();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void autoPlay() {
|
|
||||||
if (!_videoPlayerController.value.isPlaying) {
|
|
||||||
_videoPlayerController.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void startMonitor() {
|
|
||||||
_monitorTimer?.cancel();
|
|
||||||
_monitorTimer = Timer.periodic(Duration(milliseconds: 500), (timer) {
|
|
||||||
if (_videoPlayerController.value.hasError) {
|
|
||||||
timer.cancel();
|
|
||||||
setState(() {
|
|
||||||
_isLoaded = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildScreen() {
|
Widget _buildScreen() {
|
||||||
@ -109,16 +88,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else if (_entity.supportStream) {
|
|
||||||
if (_videoPlayerController.value.initialized) {
|
|
||||||
screenWidget = VideoPlayer(_videoPlayerController);
|
|
||||||
} else {
|
|
||||||
screenWidget = Center(
|
|
||||||
child: EntityPicture(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
screenWidget = WebView(
|
screenWidget = WebView(
|
||||||
initialUrl: _webViewHtml,
|
initialUrl: _webViewHtml,
|
||||||
@ -130,6 +99,7 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
JavascriptChannel(
|
JavascriptChannel(
|
||||||
name: _jsMessageChannelName,
|
name: _jsMessageChannelName,
|
||||||
onMessageReceived: ((message) {
|
onMessageReceived: ((message) {
|
||||||
|
Logger.d('[Camera Player] Message from page: $message');
|
||||||
setState((){
|
setState((){
|
||||||
_aspectRatio = double.tryParse(message.message) ?? 1.33;
|
_aspectRatio = double.tryParse(message.message) ?? 1.33;
|
||||||
});
|
});
|
||||||
@ -145,28 +115,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildControls() {
|
Widget _buildControls() {
|
||||||
Widget playControl;
|
|
||||||
if (_entity.supportStream) {
|
|
||||||
playControl = Center(
|
|
||||||
child: IconButton(
|
|
||||||
icon: Icon((_videoPlayerController != null && _videoPlayerController.value.isPlaying) ? Icons.pause_circle_outline : Icons.play_circle_outline),
|
|
||||||
iconSize: 60,
|
|
||||||
color: Colors.amberAccent,
|
|
||||||
onPressed: (_videoPlayerController == null || _videoPlayerController.value.hasError || !_isLoaded) ? null :
|
|
||||||
() {
|
|
||||||
setState(() {
|
|
||||||
if (_videoPlayerController != null && _videoPlayerController.value.isPlaying) {
|
|
||||||
_videoPlayerController.pause();
|
|
||||||
} else {
|
|
||||||
_videoPlayerController.play();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
playControl = Container();
|
|
||||||
}
|
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
@ -175,7 +123,7 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.refresh),
|
icon: Icon(Icons.refresh),
|
||||||
iconSize: 40,
|
iconSize: 40,
|
||||||
color: Colors.amberAccent,
|
color: Theme.of(context).accentColor,
|
||||||
onPressed: _isLoaded ? () {
|
onPressed: _isLoaded ? () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
@ -183,14 +131,13 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
} : null,
|
} : null,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: playControl,
|
child: Container(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.fullscreen),
|
icon: Icon(Icons.fullscreen),
|
||||||
iconSize: 40,
|
iconSize: 40,
|
||||||
color: Colors.amberAccent,
|
color: Theme.of(context).accentColor,
|
||||||
onPressed: _isLoaded ? () {
|
onPressed: _isLoaded ? () {
|
||||||
_videoPlayerController?.pause();
|
|
||||||
eventBus.fire(ShowEntityPageEvent());
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@ -239,8 +186,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_monitorTimer?.cancel();
|
|
||||||
_videoPlayerController?.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -204,20 +204,20 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
//_buildOnOffControl(entity),
|
//_buildOnOffControl(entity),
|
||||||
_buildTemperatureControls(entity),
|
_buildTemperatureControls(entity, context),
|
||||||
_buildTargetTemperatureControls(entity),
|
_buildTargetTemperatureControls(entity, context),
|
||||||
_buildHumidityControls(entity),
|
_buildHumidityControls(entity, context),
|
||||||
_buildOperationControl(entity),
|
_buildOperationControl(entity, context),
|
||||||
_buildFanControl(entity),
|
_buildFanControl(entity, context),
|
||||||
_buildSwingControl(entity),
|
_buildSwingControl(entity, context),
|
||||||
_buildPresetModeControl(entity),
|
_buildPresetModeControl(entity, context),
|
||||||
_buildAuxHeatControl(entity)
|
_buildAuxHeatControl(entity, context)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPresetModeControl(ClimateEntity entity) {
|
Widget _buildPresetModeControl(ClimateEntity entity, BuildContext context) {
|
||||||
if (entity.supportPresetMode) {
|
if (entity.supportPresetMode) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
options: entity.presetModes,
|
options: entity.presetModes,
|
||||||
@ -242,7 +242,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
Widget _buildAuxHeatControl(ClimateEntity entity) {
|
Widget _buildAuxHeatControl(ClimateEntity entity, BuildContext context) {
|
||||||
if (entity.supportAuxHeat ) {
|
if (entity.supportAuxHeat ) {
|
||||||
return ModeSwitchWidget(
|
return ModeSwitchWidget(
|
||||||
caption: "Aux heat",
|
caption: "Aux heat",
|
||||||
@ -254,7 +254,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildOperationControl(ClimateEntity entity) {
|
Widget _buildOperationControl(ClimateEntity entity, BuildContext context) {
|
||||||
if (entity.hvacModes != null) {
|
if (entity.hvacModes != null) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
onChange: (mode) => _setHVACMode(entity, mode),
|
onChange: (mode) => _setHVACMode(entity, mode),
|
||||||
@ -267,7 +267,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFanControl(ClimateEntity entity) {
|
Widget _buildFanControl(ClimateEntity entity, BuildContext context) {
|
||||||
if (entity.supportFanMode) {
|
if (entity.supportFanMode) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
options: entity.fanModes,
|
options: entity.fanModes,
|
||||||
@ -280,7 +280,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSwingControl(ClimateEntity entity) {
|
Widget _buildSwingControl(ClimateEntity entity, BuildContext context) {
|
||||||
if (entity.supportSwingMode) {
|
if (entity.supportSwingMode) {
|
||||||
return ModeSelectorWidget(
|
return ModeSelectorWidget(
|
||||||
onChange: (mode) => _setSwingMode(entity, mode),
|
onChange: (mode) => _setSwingMode(entity, mode),
|
||||||
@ -293,17 +293,15 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTemperatureControls(ClimateEntity entity) {
|
Widget _buildTemperatureControls(ClimateEntity entity, BuildContext context) {
|
||||||
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
if ((entity.supportTargetTemperature) && (entity.temperature != null)) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("Target temperature", style: TextStyle(
|
Text("Target temperature", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTemperature,
|
value: _tmpTemperature,
|
||||||
fontColor: _temperaturePending ? Colors.red : Colors.black,
|
active: _temperaturePending,
|
||||||
onDec: () => _temperatureDown(entity),
|
onDec: () => _temperatureDown(entity),
|
||||||
onInc: () => _temperatureUp(entity),
|
onInc: () => _temperatureUp(entity),
|
||||||
)
|
)
|
||||||
@ -314,13 +312,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTargetTemperatureControls(ClimateEntity entity) {
|
Widget _buildTargetTemperatureControls(ClimateEntity entity, BuildContext context) {
|
||||||
List<Widget> controls = [];
|
List<Widget> controls = [];
|
||||||
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) {
|
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) {
|
||||||
controls.addAll(<Widget>[
|
controls.addAll(<Widget>[
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTargetLow,
|
value: _tmpTargetLow,
|
||||||
fontColor: _temperaturePending ? Colors.red : Colors.black,
|
active: _temperaturePending,
|
||||||
onDec: () => _targetLowDown(entity),
|
onDec: () => _targetLowDown(entity),
|
||||||
onInc: () => _targetLowUp(entity),
|
onInc: () => _targetLowUp(entity),
|
||||||
),
|
),
|
||||||
@ -333,7 +331,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
controls.add(
|
controls.add(
|
||||||
TemperatureControlWidget(
|
TemperatureControlWidget(
|
||||||
value: _tmpTargetHigh,
|
value: _tmpTargetHigh,
|
||||||
fontColor: _temperaturePending ? Colors.red : Colors.black,
|
active: _temperaturePending,
|
||||||
onDec: () => _targetHighDown(entity),
|
onDec: () => _targetHighDown(entity),
|
||||||
onInc: () => _targetHighUp(entity),
|
onInc: () => _targetHighUp(entity),
|
||||||
)
|
)
|
||||||
@ -343,9 +341,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("Target temperature range", style: TextStyle(
|
Text("Target temperature range", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
Row(
|
Row(
|
||||||
children: controls,
|
children: controls,
|
||||||
)
|
)
|
||||||
@ -356,13 +352,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHumidityControls(ClimateEntity entity) {
|
Widget _buildHumidityControls(ClimateEntity entity, BuildContext context) {
|
||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
if (entity.supportTargetHumidity) {
|
if (entity.supportTargetHumidity) {
|
||||||
result.addAll(<Widget>[
|
result.addAll(<Widget>[
|
||||||
Text(
|
Text(
|
||||||
"$_tmpTargetHumidity%",
|
"$_tmpTargetHumidity%",
|
||||||
style: TextStyle(fontSize: Sizes.largeFontSize),
|
style: Theme.of(context).textTheme.display1,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Slider(
|
child: Slider(
|
||||||
@ -387,9 +383,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
child: Text("Target humidity", style: TextStyle(
|
child: Text("Target humidity", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
@ -33,23 +33,16 @@ class ClimateStateWidget extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("$displayState",
|
Text("$displayState",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: new TextStyle(
|
style: Theme.of(context).textTheme.body2),
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
)),
|
|
||||||
Text(" $targetTemp",
|
Text(" $targetTemp",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: new TextStyle(
|
style: Theme.of(context).textTheme.body1)
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
))
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
entity.currentTemperature != null ?
|
entity.currentTemperature != null ?
|
||||||
Text("Currently: ${entity.currentTemperature}",
|
Text("Currently: ${entity.currentTemperature}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: new TextStyle(
|
style: Theme.of(context).textTheme.subtitle
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
color: Colors.black45)
|
|
||||||
) :
|
) :
|
||||||
Container(height: 0.0,)
|
Container(height: 0.0,)
|
||||||
],
|
],
|
||||||
|
@ -5,8 +5,6 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
final String caption;
|
final String caption;
|
||||||
final List options;
|
final List options;
|
||||||
final String value;
|
final String value;
|
||||||
final double captionFontSize;
|
|
||||||
final double valueFontSize;
|
|
||||||
final onChange;
|
final onChange;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
@ -16,8 +14,6 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
@required this.options,
|
@required this.options,
|
||||||
this.value,
|
this.value,
|
||||||
@required this.onChange,
|
@required this.onChange,
|
||||||
this.captionFontSize,
|
|
||||||
this.valueFontSize,
|
|
||||||
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -28,9 +24,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("$caption", style: TextStyle(
|
Text("$caption", style: Theme.of(context).textTheme.body1),
|
||||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -40,10 +34,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
|||||||
value: value,
|
value: value,
|
||||||
iconSize: 30.0,
|
iconSize: 30.0,
|
||||||
isExpanded: true,
|
isExpanded: true,
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.title,
|
||||||
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
|
||||||
color: Colors.black,
|
|
||||||
),
|
|
||||||
hint: Text("Select ${caption.toLowerCase()}"),
|
hint: Text("Select ${caption.toLowerCase()}"),
|
||||||
items: options.map((value) {
|
items: options.map((value) {
|
||||||
return new DropdownMenuItem<String>(
|
return new DropdownMenuItem<String>(
|
||||||
|
@ -4,7 +4,6 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
|
|
||||||
final String caption;
|
final String caption;
|
||||||
final onChange;
|
final onChange;
|
||||||
final double captionFontSize;
|
|
||||||
final bool value;
|
final bool value;
|
||||||
final bool expanded;
|
final bool expanded;
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
@ -13,7 +12,6 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
Key key,
|
Key key,
|
||||||
@required this.caption,
|
@required this.caption,
|
||||||
@required this.onChange,
|
@required this.onChange,
|
||||||
this.captionFontSize,
|
|
||||||
this.value,
|
this.value,
|
||||||
this.expanded: true,
|
this.expanded: true,
|
||||||
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
||||||
@ -25,7 +23,7 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
padding: this.padding,
|
padding: this.padding,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildCaption(),
|
_buildCaption(context),
|
||||||
Switch(
|
Switch(
|
||||||
onChanged: (value) => onChange(value),
|
onChanged: (value) => onChange(value),
|
||||||
value: value ?? false,
|
value: value ?? false,
|
||||||
@ -35,12 +33,10 @@ class ModeSwitchWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCaption() {
|
Widget _buildCaption(BuildContext context) {
|
||||||
Widget captionWidget = Text(
|
Widget captionWidget = Text(
|
||||||
"$caption",
|
"$caption",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1,
|
||||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
|
@ -2,8 +2,7 @@ part of '../../../main.dart';
|
|||||||
|
|
||||||
class TemperatureControlWidget extends StatelessWidget {
|
class TemperatureControlWidget extends StatelessWidget {
|
||||||
final double value;
|
final double value;
|
||||||
final double fontSize;
|
final bool active;
|
||||||
final Color fontColor;
|
|
||||||
final onInc;
|
final onInc;
|
||||||
final onDec;
|
final onDec;
|
||||||
|
|
||||||
@ -12,8 +11,9 @@ class TemperatureControlWidget extends StatelessWidget {
|
|||||||
@required this.value,
|
@required this.value,
|
||||||
@required this.onInc,
|
@required this.onInc,
|
||||||
@required this.onDec,
|
@required this.onDec,
|
||||||
this.fontSize,
|
//this.fontSize,
|
||||||
this.fontColor})
|
this.active: false
|
||||||
|
})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -23,10 +23,7 @@ class TemperatureControlWidget extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
"$value",
|
"$value",
|
||||||
style: TextStyle(
|
style: active ? Theme.of(context).textTheme.display2 : Theme.of(context).textTheme.display1,
|
||||||
fontSize: fontSize ?? 24.0,
|
|
||||||
color: fontColor ?? Colors.black
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Column(
|
Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
@ -64,9 +64,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
child: Text("Position", style: TextStyle(
|
child: Text("Position"),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
Slider(
|
Slider(
|
||||||
value: _tmpPosition,
|
value: _tmpPosition,
|
||||||
@ -118,9 +116,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
|||||||
controls.insert(0, Padding(
|
controls.insert(0, Padding(
|
||||||
padding: EdgeInsets.fromLTRB(
|
padding: EdgeInsets.fromLTRB(
|
||||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||||
child: Text("Tilt position", style: TextStyle(
|
child: Text("Tilt position"),
|
||||||
fontSize: Sizes.stateFontSize
|
|
||||||
)),
|
|
||||||
));
|
));
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
@ -9,10 +9,8 @@ class DateTimeStateWidget extends StatelessWidget {
|
|||||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
child: Text("${entity.formattedState}",
|
child: Text("${entity.formattedState}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right
|
||||||
style: new TextStyle(
|
),
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
)),
|
|
||||||
onTap: () => _handleStateTap(context, entity),
|
onTap: () => _handleStateTap(context, entity),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,19 @@ class DefaultEntityContainer extends StatelessWidget {
|
|||||||
return MissedEntityWidget();
|
return MissedEntityWidget();
|
||||||
}
|
}
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
||||||
return Divider(
|
return Divider();
|
||||||
color: Colors.black45,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Divider(
|
Divider(),
|
||||||
color: Colors.black45,
|
|
||||||
),
|
|
||||||
Text(
|
Text(
|
||||||
"${entityModel.entityWrapper.entity.displayName}",
|
"${entityModel.entityWrapper.entity.displayName}",
|
||||||
style: TextStyle(color: Colors.blue),
|
style: HAClientTheme().getLinkTextStyle(context).copyWith(
|
||||||
|
decoration: TextDecoration.none
|
||||||
|
)
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class EntityColor {
|
|
||||||
|
|
||||||
static const defaultStateColor = Color.fromRGBO(68, 115, 158, 1.0);
|
|
||||||
|
|
||||||
static const badgeColors = {
|
|
||||||
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
|
||||||
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
|
||||||
};
|
|
||||||
|
|
||||||
static const _stateColors = {
|
|
||||||
EntityState.on: Colors.amber,
|
|
||||||
"auto": Colors.amber,
|
|
||||||
EntityState.active: Colors.amber,
|
|
||||||
EntityState.playing: Colors.amber,
|
|
||||||
EntityState.paused: Colors.amber,
|
|
||||||
"above_horizon": Colors.amber,
|
|
||||||
EntityState.home: Colors.amber,
|
|
||||||
EntityState.open: Colors.amber,
|
|
||||||
EntityState.cleaning: Colors.amber,
|
|
||||||
EntityState.returning: Colors.amber,
|
|
||||||
EntityState.off: defaultStateColor,
|
|
||||||
EntityState.closed: defaultStateColor,
|
|
||||||
"below_horizon": defaultStateColor,
|
|
||||||
"default": defaultStateColor,
|
|
||||||
EntityState.idle: defaultStateColor,
|
|
||||||
"heat": Colors.redAccent,
|
|
||||||
"cool": Colors.lightBlue,
|
|
||||||
EntityState.unavailable: Colors.black26,
|
|
||||||
EntityState.unknown: Colors.black26,
|
|
||||||
EntityState.alarm_disarmed: Colors.green,
|
|
||||||
EntityState.alarm_armed_away: Colors.redAccent,
|
|
||||||
EntityState.alarm_armed_custom_bypass: Colors.redAccent,
|
|
||||||
EntityState.alarm_armed_home: Colors.redAccent,
|
|
||||||
EntityState.alarm_armed_night: Colors.redAccent,
|
|
||||||
EntityState.alarm_triggered: Colors.redAccent,
|
|
||||||
EntityState.alarm_arming: Colors.amber,
|
|
||||||
EntityState.alarm_disarming: Colors.amber,
|
|
||||||
EntityState.alarm_pending: Colors.amber,
|
|
||||||
};
|
|
||||||
|
|
||||||
static Color stateColor(String state) {
|
|
||||||
return _stateColors[state] ?? _stateColors["default"];
|
|
||||||
}
|
|
||||||
|
|
||||||
static charts.Color chartHistoryStateColor(String state, int id) {
|
|
||||||
Color c = _stateColors[state];
|
|
||||||
if (c != null) {
|
|
||||||
return charts.Color(
|
|
||||||
r: c.red,
|
|
||||||
g: c.green,
|
|
||||||
b: c.blue,
|
|
||||||
a: c.alpha
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
double r = id.toDouble() % 10;
|
|
||||||
return charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Color historyStateColor(String state, int id) {
|
|
||||||
Color c = _stateColors[state];
|
|
||||||
if (c != null) {
|
|
||||||
return c;
|
|
||||||
} else {
|
|
||||||
if (id > -1) {
|
|
||||||
double r = id.toDouble() % 10;
|
|
||||||
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
|
||||||
return Color.fromARGB(c1.a, c1.r, c1.g, c1.b);
|
|
||||||
} else {
|
|
||||||
return _stateColors[EntityState.on];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -67,7 +67,7 @@ class EntityIcon extends StatelessWidget {
|
|||||||
padding: padding,
|
padding: padding,
|
||||||
child: buildIcon(
|
child: buildIcon(
|
||||||
entityWrapper,
|
entityWrapper,
|
||||||
color ?? EntityColor.stateColor(entityWrapper.entity.state)
|
color ?? HAClientTheme().getColorByEntityState(entityWrapper.entity.state, context)
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,18 +5,24 @@ class EntityName extends StatelessWidget {
|
|||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final TextOverflow textOverflow;
|
final TextOverflow textOverflow;
|
||||||
final bool wordsWrap;
|
final bool wordsWrap;
|
||||||
final double fontSize;
|
|
||||||
final TextAlign textAlign;
|
final TextAlign textAlign;
|
||||||
final int maxLines;
|
final int maxLines;
|
||||||
|
final TextStyle textStyle;
|
||||||
|
|
||||||
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.wordsWrap: true, this.fontSize: Sizes.nameFontSize, this.textAlign: TextAlign.left}) : super(key: key);
|
const EntityName({Key key, this.maxLines, this.padding: const EdgeInsets.only(right: 10.0), this.textOverflow: TextOverflow.ellipsis, this.textStyle, this.wordsWrap: true, this.textAlign: TextAlign.left}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||||
TextStyle textStyle = TextStyle(fontSize: fontSize);
|
TextStyle tStyle;
|
||||||
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
if (textStyle == null) {
|
||||||
textStyle = textStyle.apply(color: Colors.blue, decoration: TextDecoration.underline);
|
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||||
|
tStyle = HAClientTheme().getLinkTextStyle(context);
|
||||||
|
} else {
|
||||||
|
tStyle = Theme.of(context).textTheme.body1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tStyle = textStyle;
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
@ -25,7 +31,7 @@ class EntityName extends StatelessWidget {
|
|||||||
overflow: textOverflow,
|
overflow: textOverflow,
|
||||||
softWrap: wordsWrap,
|
softWrap: wordsWrap,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
style: textStyle,
|
style: tStyle,
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ class EntityPageLayout extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
showClose ?
|
showClose ?
|
||||||
Container(
|
Container(
|
||||||
color: Colors.blue[300],
|
color: Theme.of(context).primaryColor,
|
||||||
height: 40,
|
height: 40,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -25,18 +25,14 @@ class EntityPageLayout extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(left: 8),
|
padding: EdgeInsets.only(left: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
entity.displayName,
|
entity.displayName,
|
||||||
style: TextStyle(
|
style: Theme.of(context).primaryTextTheme.headline
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 22
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
icon: Icon(Icons.close),
|
icon: Icon(Icons.close),
|
||||||
color: Colors.white,
|
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||||
iconSize: 36.0,
|
iconSize: 36.0,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
eventBus.fire(ShowEntityPageEvent());
|
eventBus.fire(ShowEntityPageEvent());
|
||||||
|
@ -22,7 +22,7 @@ class EntityPicture extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildIcon(EntityWrapper data) {
|
Widget buildIcon(EntityWrapper data, BuildContext context) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ class EntityPicture extends StatelessWidget {
|
|||||||
child: Icon(
|
child: Icon(
|
||||||
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||||
size: Sizes.largeIconSize,
|
size: Sizes.largeIconSize,
|
||||||
color: EntityColor.defaultStateColor,
|
color: HAClientTheme().getOffStateColor(context),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@ -63,7 +63,8 @@ class EntityPicture extends StatelessWidget {
|
|||||||
return Padding(
|
return Padding(
|
||||||
padding: padding,
|
padding: padding,
|
||||||
child: buildIcon(
|
child: buildIcon(
|
||||||
entityWrapper
|
entityWrapper,
|
||||||
|
context
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
final String serviceName;
|
final String serviceName;
|
||||||
final String entityId;
|
final String entityId;
|
||||||
final String text;
|
final String text;
|
||||||
final double fontSize;
|
|
||||||
|
|
||||||
FlatServiceButton({
|
FlatServiceButton({
|
||||||
Key key,
|
Key key,
|
||||||
@ -14,7 +13,6 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
@required this.serviceName,
|
@required this.serviceName,
|
||||||
@required this.entityId,
|
@required this.entityId,
|
||||||
@required this.text,
|
@required this.text,
|
||||||
this.fontSize: Sizes.stateFontSize
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
void _setNewState() {
|
void _setNewState() {
|
||||||
@ -24,7 +22,7 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: fontSize*2.5,
|
height: Theme.of(context).textTheme.subhead.fontSize*2.5,
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
onPressed: (() {
|
onPressed: (() {
|
||||||
_setNewState();
|
_setNewState();
|
||||||
@ -32,8 +30,7 @@ class FlatServiceButton extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context),
|
||||||
new TextStyle(fontSize: fontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -183,7 +183,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
}
|
}
|
||||||
return UniversalSlider(
|
return UniversalSlider(
|
||||||
title: "Color temperature",
|
title: "Color temperature",
|
||||||
leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),),
|
leading: Text("Cold", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.lightBlue)),
|
||||||
value: val,
|
value: val,
|
||||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||||
max: entity.maxMireds,
|
max: entity.maxMireds,
|
||||||
@ -194,7 +194,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
_tmpColorTemp = value.round();
|
_tmpColorTemp = value.round();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
closing: Text("Warm", style: TextStyle(color: Colors.amberAccent),),
|
closing: Text("Warm", style: Theme.of(context).textTheme.body1.copyWith(color: Colors.amberAccent),),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Container(width: 0.0, height: 0.0);
|
return Container(width: 0.0, height: 0.0);
|
||||||
@ -224,7 +224,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
FlatButton(
|
FlatButton(
|
||||||
color: savedColor?.toColor() ?? Colors.transparent,
|
color: savedColor?.toColor() ?? Theme.of(context).backgroundColor,
|
||||||
child: Text('Paste color'),
|
child: Text('Paste color'),
|
||||||
onPressed: savedColor == null ? null : () {
|
onPressed: savedColor == null ? null : () {
|
||||||
_setColor(entity, savedColor);
|
_setColor(entity, savedColor);
|
||||||
|
@ -28,8 +28,7 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
onPressed: () => _unlock(entity),
|
onPressed: () => _unlock(entity),
|
||||||
child: Text("UNLOCK",
|
child: Text("UNLOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context)
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -39,8 +38,7 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
onPressed: () => _lock(entity),
|
onPressed: () => _lock(entity),
|
||||||
child: Text("LOCK",
|
child: Text("LOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context),
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -56,8 +54,7 @@ class LockStateWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
entity.isLocked ? "UNLOCK" : "LOCK",
|
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style:
|
style: HAClientTheme().getActionTextStyle(context),
|
||||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -33,7 +33,7 @@ class _MediaPlayerProgressBarState extends State<MediaPlayerProgressBar> {
|
|||||||
return LinearProgressIndicator(
|
return LinearProgressIndicator(
|
||||||
value: progress,
|
value: progress,
|
||||||
backgroundColor: Colors.black45,
|
backgroundColor: Colors.black45,
|
||||||
valueColor: AlwaysStoppedAnimation<Color>(EntityColor.stateColor(EntityState.on)),
|
valueColor: AlwaysStoppedAnimation<Color>(HAClientTheme().getOnStateColor(context)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,12 +13,6 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
double _currentPosition = 0;
|
double _currentPosition = 0;
|
||||||
int _savedPosition = 0;
|
int _savedPosition = 0;
|
||||||
|
|
||||||
final TextStyle _seekTextStyle = TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
color: Colors.blue,
|
|
||||||
fontWeight: FontWeight.bold
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -53,8 +47,7 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
buttons.add(
|
buttons.add(
|
||||||
RaisedButton(
|
RaisedButton(
|
||||||
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
|
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
|
||||||
color: Colors.orange,
|
color: Theme.of(context).accentColor,
|
||||||
focusColor: Colors.white,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ConnectionManager().callService(
|
ConnectionManager().callService(
|
||||||
domain: "media_player",
|
domain: "media_player",
|
||||||
@ -79,7 +72,13 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("00:00"),
|
Text("00:00"),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text("${Duration(seconds: _currentPosition.toInt()).toString().split(".")[0]}",textAlign: TextAlign.center, style: _seekTextStyle),
|
child: Text(
|
||||||
|
"${Duration(seconds: _currentPosition.toInt()).toString().split(".")[0]}",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.title.copyWith(
|
||||||
|
color: Colors.blue
|
||||||
|
)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text("${Duration(seconds: entity.durationSeconds).toString().split(".")[0]}")
|
Text("${Duration(seconds: entity.durationSeconds).toString().split(".")[0]}")
|
||||||
],
|
],
|
||||||
@ -87,8 +86,7 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
|||||||
Container(height: 10,),
|
Container(height: 10,),
|
||||||
Slider(
|
Slider(
|
||||||
min: 0,
|
min: 0,
|
||||||
activeColor: Colors.amber,
|
activeColor: Theme.of(context).accentColor,
|
||||||
inactiveColor: Colors.black26,
|
|
||||||
max: entity.durationSeconds.toDouble(),
|
max: entity.durationSeconds.toDouble(),
|
||||||
value: _currentPosition,
|
value: _currentPosition,
|
||||||
onChangeStart: (val) {
|
onChangeStart: (val) {
|
||||||
|
@ -12,14 +12,14 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
Stack(
|
Stack(
|
||||||
alignment: AlignmentDirectional.topEnd,
|
alignment: AlignmentDirectional.topEnd,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildImage(entity),
|
_buildImage(entity, context),
|
||||||
Positioned(
|
Positioned(
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
left: 0.0,
|
left: 0.0,
|
||||||
right: 0.0,
|
right: 0.0,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.black45,
|
color: Colors.black45,
|
||||||
child: _buildState(entity),
|
child: _buildState(entity, context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
@ -35,12 +35,9 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildState(MediaPlayerEntity entity) {
|
Widget _buildState(MediaPlayerEntity entity, BuildContext context) {
|
||||||
TextStyle style = TextStyle(
|
TextStyle style = Theme.of(context).textTheme.body1.copyWith(
|
||||||
fontSize: 14.0,
|
color: Colors.white
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
height: 1.2
|
|
||||||
);
|
);
|
||||||
List<Widget> states = [];
|
List<Widget> states = [];
|
||||||
states.add(Text("${entity.displayName}", style: style));
|
states.add(Text("${entity.displayName}", style: style));
|
||||||
@ -71,7 +68,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildImage(MediaPlayerEntity entity) {
|
Widget _buildImage(MediaPlayerEntity entity, BuildContext context) {
|
||||||
String state = entity.state;
|
String state = entity.state;
|
||||||
if (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
if (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||||
return Container(
|
return Container(
|
||||||
@ -97,7 +94,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
|||||||
Icon(
|
Icon(
|
||||||
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
||||||
size: 150.0,
|
size: 150.0,
|
||||||
color: EntityColor.stateColor("$state"),
|
color: HAClientTheme().getColorByEntityState("$state", context),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -7,10 +7,10 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
final EdgeInsetsGeometry padding;
|
final EdgeInsetsGeometry padding;
|
||||||
final int maxLines;
|
final int maxLines;
|
||||||
final String customValue;
|
final String customValue;
|
||||||
final double fontSize;
|
final TextStyle textStyle;
|
||||||
final bool bold;
|
//final bool bold;
|
||||||
|
|
||||||
const SimpleEntityState({Key key,this.bold: false, this.maxLines: 10, this.fontSize: Sizes.stateFontSize, this.expanded: true, this.textAlign: TextAlign.right, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0), this.customValue}) : super(key: key);
|
const SimpleEntityState({Key key,/*this.bold: false,*/ this.maxLines: 10, this.expanded: true, this.textAlign: TextAlign.right, this.textStyle, this.padding: const EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0), this.customValue}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -22,16 +22,19 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
state = customValue;
|
state = customValue;
|
||||||
}
|
}
|
||||||
TextStyle textStyle = TextStyle(
|
TextStyle tStyle;
|
||||||
fontSize: this.fontSize,
|
if (textStyle != null) {
|
||||||
fontWeight: FontWeight.normal
|
tStyle = textStyle;
|
||||||
);
|
} else if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
||||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
tStyle = Theme.of(context).textTheme.subhead.copyWith(
|
||||||
textStyle = textStyle.apply(color: Colors.blue);
|
color: Colors.blue
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tStyle = Theme.of(context).textTheme.body1;
|
||||||
}
|
}
|
||||||
if (this.bold) {
|
/*if (this.bold) {
|
||||||
textStyle = textStyle.apply(fontWeightDelta: 100);
|
textStyle = textStyle.apply(fontWeightDelta: 100);
|
||||||
}
|
}*/
|
||||||
while (state.contains(" ")){
|
while (state.contains(" ")){
|
||||||
state = state.replaceAll(" ", " ");
|
state = state.replaceAll(" ", " ");
|
||||||
}
|
}
|
||||||
@ -43,7 +46,7 @@ class SimpleEntityState extends StatelessWidget {
|
|||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: textStyle
|
style: tStyle
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
if (expanded) {
|
if (expanded) {
|
||||||
|
@ -62,8 +62,7 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
"$_newValue",
|
"$_newValue",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.display1.copyWith(
|
||||||
fontSize: Sizes.largeFontSize,
|
|
||||||
color: Colors.blue
|
color: Colors.blue
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -40,10 +40,7 @@ class UniversalSlider extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(height: Sizes.rowPadding,),
|
Container(height: Sizes.rowPadding,),
|
||||||
Text(
|
Text("$title"),
|
||||||
"$title",
|
|
||||||
style: TextStyle(fontSize: Sizes.stateFontSize),
|
|
||||||
),
|
|
||||||
Container(height: Sizes.rowPadding,),
|
Container(height: Sizes.rowPadding,),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -10,7 +10,7 @@ class VacuumControls extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildStatusAndBattery(entity),
|
_buildStatusAndBattery(entity, context),
|
||||||
_buildCommands(entity),
|
_buildCommands(entity),
|
||||||
_buildFanSpeed(entity),
|
_buildFanSpeed(entity),
|
||||||
_buildAdditionalInfo(entity)
|
_buildAdditionalInfo(entity)
|
||||||
@ -19,12 +19,12 @@ class VacuumControls extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStatusAndBattery(VacuumEntity entity) {
|
Widget _buildStatusAndBattery(VacuumEntity entity, BuildContext context) {
|
||||||
List<Widget> result = [];
|
List<Widget> result = [];
|
||||||
if (entity.supportStatus) {
|
if (entity.supportStatus) {
|
||||||
result.addAll(
|
result.addAll(
|
||||||
<Widget>[
|
<Widget>[
|
||||||
Text("Status:", style: TextStyle(fontSize: Sizes.stateFontSize),),
|
Text("Status:"),
|
||||||
Container(width: 6,),
|
Container(width: 6,),
|
||||||
Expanded(
|
Expanded(
|
||||||
//flex: 1,
|
//flex: 1,
|
||||||
@ -33,10 +33,7 @@ class VacuumControls extends StatelessWidget {
|
|||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body2,
|
||||||
fontSize: Sizes.stateFontSize,
|
|
||||||
fontWeight: FontWeight.bold
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -48,7 +45,7 @@ class VacuumControls extends StatelessWidget {
|
|||||||
result.addAll(<Widget>[
|
result.addAll(<Widget>[
|
||||||
Icon(MaterialDesignIcons.getIconDataFromIconName(iconName)),
|
Icon(MaterialDesignIcons.getIconDataFromIconName(iconName)),
|
||||||
Container(width: 6,),
|
Container(width: 6,),
|
||||||
Text("$batteryLevel %", style: TextStyle(fontSize: Sizes.stateFontSize))
|
Text("$batteryLevel %")
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -172,7 +169,7 @@ class VacuumControls extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("Vacuum cleaner commands:", style: TextStyle(fontSize: Sizes.stateFontSize)),
|
Text("Vacuum cleaner commands:"),
|
||||||
Container(height: Sizes.rowPadding,),
|
Container(height: Sizes.rowPadding,),
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
@ -27,10 +27,7 @@ class VacuumStateButton extends StatelessWidget {
|
|||||||
text: "RETURN TO DOCK"
|
text: "RETURN TO DOCK"
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = Text(entity.state.toUpperCase(), style: TextStyle(
|
result = Text(entity.state.toUpperCase(), style: Theme.of(context).textTheme.subhead);
|
||||||
fontSize: 16,
|
|
||||||
color: Colors.grey
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(right: 15),
|
padding: EdgeInsets.only(right: 15),
|
||||||
|
@ -331,7 +331,7 @@ class HomeAssistant {
|
|||||||
void _createUI() {
|
void _createUI() {
|
||||||
Logger.d("Creating Lovelace UI");
|
Logger.d("Creating Lovelace UI");
|
||||||
ui = HomeAssistantUI(rawLovelaceConfig: _rawLovelaceData);
|
ui = HomeAssistantUI(rawLovelaceConfig: _rawLovelaceData);
|
||||||
if (isServiceExist('zha_map')) {
|
/*if (isServiceExist('zha_map')) {
|
||||||
panels.add(
|
panels.add(
|
||||||
Panel(
|
Panel(
|
||||||
id: 'haclient_zha',
|
id: 'haclient_zha',
|
||||||
@ -341,7 +341,7 @@ class HomeAssistant {
|
|||||||
icon: 'mdi:zigbee'
|
icon: 'mdi:zigbee'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -10,7 +8,6 @@ import 'package:web_socket_channel/io.dart';
|
|||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart' as urlLauncher;
|
import 'package:url_launcher/url_launcher.dart' as urlLauncher;
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:date_format/date_format.dart';
|
import 'package:date_format/date_format.dart';
|
||||||
@ -33,9 +30,11 @@ import 'package:battery/battery.dart';
|
|||||||
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview;
|
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview;
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:syncfusion_flutter_core/core.dart';
|
||||||
|
import 'package:syncfusion_flutter_gauges/gauges.dart';
|
||||||
|
|
||||||
import 'utils/logger.dart';
|
import 'utils/logger.dart';
|
||||||
|
import '.secrets.dart';
|
||||||
|
|
||||||
part 'const.dart';
|
part 'const.dart';
|
||||||
part 'utils/launcher.dart';
|
part 'utils/launcher.dart';
|
||||||
@ -76,7 +75,6 @@ part 'entities/universal_slider.widget.dart';
|
|||||||
part 'entities/flat_service_button.widget.dart';
|
part 'entities/flat_service_button.widget.dart';
|
||||||
part 'entities/light/widgets/light_color_picker.dart';
|
part 'entities/light/widgets/light_color_picker.dart';
|
||||||
part 'entities/camera/widgets/camera_stream_view.dart';
|
part 'entities/camera/widgets/camera_stream_view.dart';
|
||||||
part 'entities/entity_colors.class.dart';
|
|
||||||
part 'plugins/history_chart/entity_history.dart';
|
part 'plugins/history_chart/entity_history.dart';
|
||||||
part 'plugins/history_chart/simple_state_history_chart.dart';
|
part 'plugins/history_chart/simple_state_history_chart.dart';
|
||||||
part 'plugins/history_chart/numeric_state_history_chart.dart';
|
part 'plugins/history_chart/numeric_state_history_chart.dart';
|
||||||
@ -104,14 +102,16 @@ part 'entities/alarm_control_panel/widgets/alarm_control_panel_controls.widget.d
|
|||||||
part 'entities/vacuum/vacuum_entity.class.dart';
|
part 'entities/vacuum/vacuum_entity.class.dart';
|
||||||
part 'entities/vacuum/widgets/vacuum_controls.dart';
|
part 'entities/vacuum/widgets/vacuum_controls.dart';
|
||||||
part 'entities/vacuum/widgets/vacuum_state_button.dart';
|
part 'entities/vacuum/widgets/vacuum_state_button.dart';
|
||||||
part 'pages/settings.page.dart';
|
part 'pages/settings/connection_settings.part.dart';
|
||||||
part 'pages/purchase.page.dart';
|
part 'pages/purchase.page.dart';
|
||||||
part 'pages/widgets/product_purchase.widget.dart';
|
part 'pages/widgets/product_purchase.widget.dart';
|
||||||
part 'pages/widgets/page_loading_indicator.dart';
|
part 'pages/widgets/page_loading_indicator.dart';
|
||||||
part 'pages/widgets/page_loading_error.dart';
|
part 'pages/widgets/page_loading_error.dart';
|
||||||
part 'pages/panel.page.dart';
|
part 'pages/panel.page.dart';
|
||||||
part 'pages/main/main.page.dart';
|
part 'pages/main/main.page.dart';
|
||||||
part 'pages/integration_settings.page.dart';
|
part 'pages/settings/integration_settings.part.dart';
|
||||||
|
part 'pages/settings/app_settings.page.dart';
|
||||||
|
part 'pages/settings/lookandfeel_settings.part.dart';
|
||||||
part 'pages/zha_page.dart';
|
part 'pages/zha_page.dart';
|
||||||
part 'home_assistant.class.dart';
|
part 'home_assistant.class.dart';
|
||||||
part 'pages/log.page.dart';
|
part 'pages/log.page.dart';
|
||||||
@ -124,6 +124,7 @@ part 'managers/mobile_app_integration_manager.class.dart';
|
|||||||
part 'managers/connection_manager.class.dart';
|
part 'managers/connection_manager.class.dart';
|
||||||
part 'managers/device_info_manager.class.dart';
|
part 'managers/device_info_manager.class.dart';
|
||||||
part 'managers/startup_user_messages_manager.class.dart';
|
part 'managers/startup_user_messages_manager.class.dart';
|
||||||
|
part 'managers/theme_manager.dart';
|
||||||
part 'ui.dart';
|
part 'ui.dart';
|
||||||
part 'view.class.dart';
|
part 'view.class.dart';
|
||||||
part 'cards/card.class.dart';
|
part 'cards/card.class.dart';
|
||||||
@ -148,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.0";
|
const appVersionNumber = "0.8.4";
|
||||||
const appVersionAdd = "";
|
const appVersionAdd = "";
|
||||||
const appVersion = "$appVersionNumber$appVersionAdd";
|
const appVersion = "$appVersionNumber$appVersionAdd";
|
||||||
|
|
||||||
@ -164,17 +165,24 @@ Future<void> _reportError(dynamic error, dynamic stackTrace) async {
|
|||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
Crashlytics.instance.enableInDevMode = false;
|
Crashlytics.instance.enableInDevMode = false;
|
||||||
|
SyncfusionLicense.registerLicense(secrets['syncfusion_license_key']);
|
||||||
|
|
||||||
FlutterError.onError = (FlutterErrorDetails details) {
|
FlutterError.onError = (FlutterErrorDetails details) {
|
||||||
Logger.e(" Caut Flutter runtime error: ${details.exception}");
|
Logger.e("Caut Flutter runtime error: ${details.exception}");
|
||||||
if (Logger.isInDebugMode) {
|
if (Logger.isInDebugMode) {
|
||||||
FlutterError.dumpErrorToConsole(details);
|
FlutterError.dumpErrorToConsole(details);
|
||||||
}
|
}
|
||||||
Crashlytics.instance.recordFlutterError(details);
|
Crashlytics.instance.recordFlutterError(details);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
AppTheme theme = AppTheme.values[prefs.getInt('app-theme') ?? AppTheme.defaultTheme.index];
|
||||||
|
|
||||||
runZoned(() {
|
runZoned(() {
|
||||||
runApp(new HAClientApp());
|
runApp(new HAClientApp(
|
||||||
|
theme: theme,
|
||||||
|
));
|
||||||
}, onError: (error, stack) {
|
}, onError: (error, stack) {
|
||||||
_reportError(error, stack);
|
_reportError(error, stack);
|
||||||
});
|
});
|
||||||
@ -182,22 +190,34 @@ void main() async {
|
|||||||
|
|
||||||
class HAClientApp extends StatefulWidget {
|
class HAClientApp extends StatefulWidget {
|
||||||
|
|
||||||
|
final AppTheme theme;
|
||||||
|
|
||||||
|
const HAClientApp({Key key, this.theme: AppTheme.defaultTheme}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HAClientAppState createState() => new _HAClientAppState();
|
_HAClientAppState createState() => new _HAClientAppState();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HAClientAppState extends State<HAClientApp> {
|
class _HAClientAppState extends State<HAClientApp> {
|
||||||
StreamSubscription<List<PurchaseDetails>> _subscription;
|
StreamSubscription<List<PurchaseDetails>> _purchaseUpdateSubscription;
|
||||||
|
StreamSubscription _themeChangeSubscription;
|
||||||
|
AppTheme _currentTheme = AppTheme.defaultTheme;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
InAppPurchaseConnection.enablePendingPurchases();
|
InAppPurchaseConnection.enablePendingPurchases();
|
||||||
final Stream purchaseUpdates =
|
final Stream purchaseUpdates =
|
||||||
InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
InAppPurchaseConnection.instance.purchaseUpdatedStream;
|
||||||
_subscription = purchaseUpdates.listen((purchases) {
|
_purchaseUpdateSubscription = purchaseUpdates.listen((purchases) {
|
||||||
_handlePurchaseUpdates(purchases);
|
_handlePurchaseUpdates(purchases);
|
||||||
});
|
});
|
||||||
|
_currentTheme = widget.theme;
|
||||||
|
_themeChangeSubscription = eventBus.on<ChangeThemeEvent>().listen((event){
|
||||||
|
setState(() {
|
||||||
|
_currentTheme = event.theme;
|
||||||
|
});
|
||||||
|
});
|
||||||
workManager.Workmanager.initialize(
|
workManager.Workmanager.initialize(
|
||||||
updateDeviceLocationIsolate,
|
updateDeviceLocationIsolate,
|
||||||
isInDebugMode: false
|
isInDebugMode: false
|
||||||
@ -226,14 +246,15 @@ class _HAClientAppState extends State<HAClientApp> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return new MaterialApp(
|
return new MaterialApp(
|
||||||
title: appName,
|
title: appName,
|
||||||
theme: new ThemeData(
|
theme: HAClientTheme().getThemeData(_currentTheme),
|
||||||
primarySwatch: Colors.blue,
|
darkTheme: HAClientTheme().darkTheme,
|
||||||
),
|
debugShowCheckedModeBanner: false,
|
||||||
initialRoute: "/",
|
initialRoute: "/",
|
||||||
routes: {
|
routes: {
|
||||||
"/": (context) => MainPage(title: 'HA Client'),
|
"/": (context) => MainPage(title: 'HA Client'),
|
||||||
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
|
"/app-settings": (context) => AppSettingsPage(),
|
||||||
"/integration-settings": (context) => IntegrationSettingsPage(title: "Integration settings"),
|
"/connection-settings": (context) => AppSettingsPage(showSection: AppSettingsSection.connectionSettings),
|
||||||
|
"/integration-settings": (context) => AppSettingsPage(showSection: AppSettingsSection.integrationSettings),
|
||||||
"/putchase": (context) => PurchasePage(title: "Support app development"),
|
"/putchase": (context) => PurchasePage(title: "Support app development"),
|
||||||
"/play-media": (context) => PlayMediaPage(
|
"/play-media": (context) => PlayMediaPage(
|
||||||
mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",
|
mediaUrl: "${ModalRoute.of(context).settings.arguments != null ? (ModalRoute.of(context).settings.arguments as Map)['url'] : ''}",
|
||||||
@ -262,7 +283,9 @@ class _HAClientAppState extends State<HAClientApp> {
|
|||||||
title: new Text("Login with HA"),
|
title: new Text("Login with HA"),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text("Manual", style: TextStyle(color: Colors.white)),
|
child: Text("Manual", style: Theme.of(context).textTheme.button.copyWith(
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
)),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
eventBus.fire(ShowPageEvent(path: "/connection-settings", goBackFirst: true));
|
eventBus.fire(ShowPageEvent(path: "/connection-settings", goBackFirst: true));
|
||||||
},
|
},
|
||||||
@ -276,7 +299,8 @@ class _HAClientAppState extends State<HAClientApp> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_subscription.cancel();
|
_purchaseUpdateSubscription.cancel();
|
||||||
|
_themeChangeSubscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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";
|
||||||
@ -223,16 +225,6 @@ class ConnectionManager {
|
|||||||
onLovelaceUpdatedCallback();
|
onLovelaceUpdatedCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ((data["event"] != null) && (data["event"]["event_type"] == "state_changed")) {
|
|
||||||
Logger.d("[Received] <== ${data['type']}.${data["event"]["event_type"]}: ${data["event"]["data"]["entity_id"]}");
|
|
||||||
onStateChangeCallback(data["event"]["data"]);
|
|
||||||
} else if (data["event"] != null) {
|
|
||||||
Logger.w("Unhandled event type: ${data["event"]["event_type"]}");
|
|
||||||
} else {
|
|
||||||
Logger.e("Event is null: $data");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Logger.d("[Received unhandled] <== ${data.toString()}");
|
Logger.d("[Received unhandled] <== ${data.toString()}");
|
||||||
}
|
}
|
||||||
|
@ -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: () {
|
||||||
|
@ -14,7 +14,7 @@ class StartupUserMessagesManager {
|
|||||||
bool _supportAppDevelopmentMessageShown;
|
bool _supportAppDevelopmentMessageShown;
|
||||||
bool _whatsNewMessageShown;
|
bool _whatsNewMessageShown;
|
||||||
static final _supportAppDevelopmentMessageKey = "user-message-shown-support-development_3";
|
static final _supportAppDevelopmentMessageKey = "user-message-shown-support-development_3";
|
||||||
static final _whatsNewMessageKey = "user-message-shown-whats-new-884";
|
static final _whatsNewMessageKey = "user-message-shown-whats-new-888";
|
||||||
|
|
||||||
void checkMessagesToShow() async {
|
void checkMessagesToShow() async {
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
272
lib/managers/theme_manager.dart
Normal file
272
lib/managers/theme_manager.dart
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
part of '../main.dart';
|
||||||
|
|
||||||
|
enum AppTheme {darkTheme, defaultTheme, haTheme}
|
||||||
|
|
||||||
|
class HAClientTheme {
|
||||||
|
|
||||||
|
static const TextTheme textTheme = TextTheme(
|
||||||
|
display1: TextStyle(fontSize: 34, fontWeight: FontWeight.normal),
|
||||||
|
display2: TextStyle(fontSize: 34, fontWeight: FontWeight.normal),
|
||||||
|
headline: TextStyle(fontSize: 24, fontWeight: FontWeight.normal),
|
||||||
|
title: TextStyle(fontSize: 20, fontWeight: FontWeight.w700),
|
||||||
|
subhead: TextStyle(fontSize: 16, fontWeight: FontWeight.normal),
|
||||||
|
body1: TextStyle(fontSize: 15, fontWeight: FontWeight.normal),
|
||||||
|
body2: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
|
||||||
|
subtitle: TextStyle(fontSize: 15, fontWeight: FontWeight.w500),
|
||||||
|
caption: TextStyle(fontSize: 12, fontWeight: FontWeight.normal),
|
||||||
|
overline: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
fontWeight: FontWeight.normal,
|
||||||
|
letterSpacing: 1,
|
||||||
|
),
|
||||||
|
button: TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
|
);
|
||||||
|
|
||||||
|
static const offEntityStates = [
|
||||||
|
EntityState.off,
|
||||||
|
EntityState.closed,
|
||||||
|
"below_horizon",
|
||||||
|
"default",
|
||||||
|
EntityState.idle,
|
||||||
|
EntityState.alarm_disarmed,
|
||||||
|
];
|
||||||
|
|
||||||
|
static const onEntityStates = [
|
||||||
|
EntityState.on,
|
||||||
|
"auto",
|
||||||
|
EntityState.active,
|
||||||
|
EntityState.playing,
|
||||||
|
EntityState.paused,
|
||||||
|
"above_horizon",
|
||||||
|
EntityState.home,
|
||||||
|
EntityState.open,
|
||||||
|
EntityState.cleaning,
|
||||||
|
EntityState.returning,
|
||||||
|
"cool",
|
||||||
|
EntityState.alarm_arming,
|
||||||
|
EntityState.alarm_disarming,
|
||||||
|
EntityState.alarm_pending,
|
||||||
|
];
|
||||||
|
|
||||||
|
static const disabledEntityStates = [
|
||||||
|
EntityState.unavailable,
|
||||||
|
EntityState.unknown,
|
||||||
|
];
|
||||||
|
|
||||||
|
static const alarmEntityStates = [
|
||||||
|
EntityState.alarm_armed_away,
|
||||||
|
EntityState.alarm_armed_custom_bypass,
|
||||||
|
EntityState.alarm_armed_home,
|
||||||
|
EntityState.alarm_armed_night,
|
||||||
|
EntityState.alarm_triggered,
|
||||||
|
"heat",
|
||||||
|
];
|
||||||
|
|
||||||
|
static const defaultStateColor = Color.fromRGBO(68, 115, 158, 1.0);
|
||||||
|
|
||||||
|
static const badgeColors = {
|
||||||
|
"default": Color.fromRGBO(223, 76, 30, 1.0),
|
||||||
|
"binary_sensor": Color.fromRGBO(3, 155, 229, 1.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
static final HAClientTheme _instance = HAClientTheme
|
||||||
|
._internal();
|
||||||
|
|
||||||
|
factory HAClientTheme() {
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
HAClientTheme._internal();
|
||||||
|
|
||||||
|
ThemeData getThemeData(AppTheme theme) {
|
||||||
|
switch (theme) {
|
||||||
|
case AppTheme.darkTheme:
|
||||||
|
return darkTheme;
|
||||||
|
break;
|
||||||
|
case AppTheme.defaultTheme:
|
||||||
|
return defaultTheme;
|
||||||
|
break;
|
||||||
|
case AppTheme.haTheme:
|
||||||
|
return homeAssistantTheme;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return defaultTheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final ThemeData defaultTheme = ThemeData.from(
|
||||||
|
colorScheme: ColorScheme(
|
||||||
|
primary: Color.fromRGBO(112, 154, 193, 1),
|
||||||
|
primaryVariant: Color.fromRGBO(68, 115, 158, 1),
|
||||||
|
secondary: Color.fromRGBO(253, 216, 53, 1),
|
||||||
|
secondaryVariant: Color.fromRGBO(222, 181, 2, 1),
|
||||||
|
background: Color.fromRGBO(250, 250, 250, 1),
|
||||||
|
surface: Colors.white,
|
||||||
|
error: Colors.red,
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
onSecondary: Colors.black87,
|
||||||
|
onBackground: Colors.black87,
|
||||||
|
onSurface: Colors.black87,
|
||||||
|
onError: Colors.white,
|
||||||
|
brightness: Brightness.light
|
||||||
|
),
|
||||||
|
textTheme: ThemeData.light().textTheme.copyWith(
|
||||||
|
display1: textTheme.display1.copyWith(color: Colors.black54),
|
||||||
|
display2: textTheme.display2.copyWith(color: Colors.redAccent),
|
||||||
|
headline: textTheme.headline.copyWith(color: Colors.black87),
|
||||||
|
title: textTheme.title.copyWith(color: Colors.black87),
|
||||||
|
subhead: textTheme.subhead.copyWith(color: Colors.black54),
|
||||||
|
body1: textTheme.body1.copyWith(color: Colors.black87),
|
||||||
|
body2: textTheme.body2.copyWith(color: Colors.black87),
|
||||||
|
subtitle: textTheme.subtitle.copyWith(color: Colors.black45),
|
||||||
|
caption: textTheme.caption.copyWith(color: Colors.black45),
|
||||||
|
overline: textTheme.overline.copyWith(color: Colors.black26),
|
||||||
|
button: textTheme.button.copyWith(color: Colors.white),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final ThemeData homeAssistantTheme = ThemeData.from(
|
||||||
|
colorScheme: ColorScheme(
|
||||||
|
primary: Color.fromRGBO(2, 165, 238, 1),
|
||||||
|
primaryVariant: Color.fromRGBO(68, 115, 158, 1),
|
||||||
|
secondary: Color.fromRGBO(253, 216, 53, 1),
|
||||||
|
secondaryVariant: Color.fromRGBO(222, 181, 2, 1),
|
||||||
|
background: Color.fromRGBO(250, 250, 250, 1),
|
||||||
|
surface: Colors.white,
|
||||||
|
error: Colors.red,
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
onSecondary: Colors.black87,
|
||||||
|
onBackground: Colors.black87,
|
||||||
|
onSurface: Colors.black87,
|
||||||
|
onError: Colors.white,
|
||||||
|
brightness: Brightness.light
|
||||||
|
),
|
||||||
|
textTheme: ThemeData.light().textTheme.copyWith(
|
||||||
|
display1: textTheme.display1.copyWith(color: Colors.black54),
|
||||||
|
display2: textTheme.display2.copyWith(color: Colors.redAccent),
|
||||||
|
headline: textTheme.headline.copyWith(color: Colors.black87),
|
||||||
|
title: textTheme.title.copyWith(color: Colors.black87),
|
||||||
|
subhead: textTheme.subhead.copyWith(color: Colors.black54),
|
||||||
|
body1: textTheme.body1.copyWith(color: Colors.black87),
|
||||||
|
body2: textTheme.body2.copyWith(color: Colors.black87),
|
||||||
|
subtitle: textTheme.subtitle.copyWith(color: Colors.black45),
|
||||||
|
caption: textTheme.caption.copyWith(color: Colors.black45),
|
||||||
|
overline: textTheme.overline.copyWith(color: Colors.black26),
|
||||||
|
button: textTheme.button.copyWith(color: Colors.white),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
final ThemeData darkTheme = ThemeData.from(
|
||||||
|
colorScheme: ColorScheme(
|
||||||
|
primary: Color.fromRGBO(112, 154, 193, 1),
|
||||||
|
primaryVariant: Color.fromRGBO(68, 115, 158, 1),
|
||||||
|
secondary: Color.fromRGBO(253, 216, 53, 1),
|
||||||
|
secondaryVariant: Color.fromRGBO(222, 181, 2, 1),
|
||||||
|
background: Color.fromRGBO(47, 49, 54, 1),
|
||||||
|
surface: Color.fromRGBO(54, 57, 63, 1),
|
||||||
|
error: Color.fromRGBO(183, 109, 109, 1),
|
||||||
|
onPrimary: Colors.white,
|
||||||
|
onSecondary: Colors.black87,
|
||||||
|
onBackground: Color.fromRGBO(220, 221, 222, 1),
|
||||||
|
onSurface: Colors.white,
|
||||||
|
onError: Colors.white,
|
||||||
|
brightness: Brightness.dark
|
||||||
|
),
|
||||||
|
textTheme: textTheme
|
||||||
|
);
|
||||||
|
|
||||||
|
Color getOnStateColor(BuildContext context) {
|
||||||
|
return Theme.of(context).colorScheme.secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getOffStateColor(BuildContext context) {
|
||||||
|
return Theme.of(context).colorScheme.primaryVariant;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getDisabledStateColor(BuildContext context) {
|
||||||
|
return Theme.of(context).disabledColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getAlertStateColor(BuildContext context) {
|
||||||
|
return Theme.of(context).colorScheme.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getColorByEntityState(String state, BuildContext context) {
|
||||||
|
if (onEntityStates.contains(state)) {
|
||||||
|
return getOnStateColor(context);
|
||||||
|
} else if (disabledEntityStates.contains(state)) {
|
||||||
|
return getDisabledStateColor(context);
|
||||||
|
} else if (alarmEntityStates.contains(state)) {
|
||||||
|
return getAlertStateColor(context);
|
||||||
|
} else {
|
||||||
|
return getOffStateColor(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getGreenGaugeColor() {
|
||||||
|
return Colors.green;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getYellowGaugeColor() {
|
||||||
|
return Colors.yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getRedGaugeColor() {
|
||||||
|
return Colors.red;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getLinkTextStyle(BuildContext context) {
|
||||||
|
ThemeData theme = Theme.of(context);
|
||||||
|
return theme.textTheme.body1.copyWith(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextStyle getActionTextStyle(BuildContext context) {
|
||||||
|
ThemeData theme = Theme.of(context);
|
||||||
|
return theme.textTheme.subhead.copyWith(
|
||||||
|
color: Colors.blue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getBadgeColor(String entityDomain) {
|
||||||
|
return badgeColors[entityDomain] ??
|
||||||
|
badgeColors["default"];
|
||||||
|
}
|
||||||
|
|
||||||
|
Color getOnBadgeTextColor() {
|
||||||
|
return Colors.white;
|
||||||
|
}
|
||||||
|
|
||||||
|
charts.Color chartHistoryStateColor(String state, int id, BuildContext context) {
|
||||||
|
Color c = getColorByEntityState(state, context);
|
||||||
|
if (c != null) {
|
||||||
|
return charts.Color(
|
||||||
|
r: c.red,
|
||||||
|
g: c.green,
|
||||||
|
b: c.blue,
|
||||||
|
a: c.alpha
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
double r = id.toDouble() % 10;
|
||||||
|
return charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Color historyStateColor(String state, int id, BuildContext context) {
|
||||||
|
Color c = getColorByEntityState(state, context);
|
||||||
|
if (c != null) {
|
||||||
|
return c;
|
||||||
|
} else {
|
||||||
|
if (id > -1) {
|
||||||
|
double r = id.toDouble() % 10;
|
||||||
|
charts.Color c1 = charts.MaterialPalette.getOrderedPalettes(10)[r.round()].shadeDefault;
|
||||||
|
return Color.fromARGB(c1.a, c1.r, c1.g, c1.b);
|
||||||
|
} else {
|
||||||
|
return getOnStateColor(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,200 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class IntegrationSettingsPage extends StatefulWidget {
|
|
||||||
IntegrationSettingsPage({Key key, this.title}) : super(key: key);
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_IntegrationSettingsPageState createState() => new _IntegrationSettingsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
|
||||||
|
|
||||||
int _locationInterval = LocationManager().defaultUpdateIntervalMinutes;
|
|
||||||
bool _locationTrackingEnabled = false;
|
|
||||||
bool _wait = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadSettings();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadSettings() async {
|
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
||||||
await prefs.reload();
|
|
||||||
SharedPreferences.getInstance().then((prefs) {
|
|
||||||
setState(() {
|
|
||||||
_locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
|
|
||||||
_locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes;
|
|
||||||
if (_locationInterval % 5 != 0) {
|
|
||||||
_locationInterval = 5 * (_locationInterval ~/ 5);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void incLocationInterval() {
|
|
||||||
if (_locationInterval < 720) {
|
|
||||||
setState(() {
|
|
||||||
_locationInterval = _locationInterval + 5;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void decLocationInterval() {
|
|
||||||
if (_locationInterval > 5) {
|
|
||||||
setState(() {
|
|
||||||
_locationInterval = _locationInterval - 5;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
restart() {
|
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
|
||||||
title: "Are you sure you want to restart Home Assistant?",
|
|
||||||
body: "This will restart your Home Assistant server.",
|
|
||||||
positiveText: "Sure. Make it so",
|
|
||||||
negativeText: "What?? No!",
|
|
||||||
onPositive: () {
|
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "restart");
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
|
||||||
title: "Are you sure you want to STOP Home Assistant?",
|
|
||||||
body: "This will STOP your Home Assistant server. It means that your web interface as well as HA Client will not work untill you'll find a way to start your server using ssh or something.",
|
|
||||||
positiveText: "Sure. Make it so",
|
|
||||||
negativeText: "What?? No!",
|
|
||||||
onPositive: () {
|
|
||||||
ConnectionManager().callService(domain: "homeassistant", service: "stop");
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateRegistration() {
|
|
||||||
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
resetRegistration() {
|
|
||||||
eventBus.fire(ShowPopupDialogEvent(
|
|
||||||
title: "Waaaait",
|
|
||||||
body: "If you don't whant to have duplicate integrations and entities in your HA for your current device, first you need to remove MobileApp integration from Integration settings in HA and restart server.",
|
|
||||||
positiveText: "Done it already",
|
|
||||||
negativeText: "Ok, I will",
|
|
||||||
onPositive: () {
|
|
||||||
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true, forceRegister: true);
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
_switchLocationTrackingState(bool state) async {
|
|
||||||
if (state) {
|
|
||||||
await LocationManager().updateDeviceLocation();
|
|
||||||
}
|
|
||||||
await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
|
|
||||||
setState(() {
|
|
||||||
_wait = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return new Scaffold(
|
|
||||||
appBar: new AppBar(
|
|
||||||
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
|
||||||
Navigator.pop(context);
|
|
||||||
}),
|
|
||||||
title: new Text(widget.title),
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
padding: const EdgeInsets.all(20.0),
|
|
||||||
children: <Widget>[
|
|
||||||
Text("Location tracking", style: TextStyle(fontSize: Sizes.largeFontSize-2)),
|
|
||||||
Container(height: Sizes.rowPadding,),
|
|
||||||
InkWell(
|
|
||||||
onTap: () => Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.app/docs#location-tracking"),
|
|
||||||
child: Text(
|
|
||||||
"Please read documentation!",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.blue,
|
|
||||||
fontSize: 16,
|
|
||||||
decoration: TextDecoration.underline
|
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: Sizes.rowPadding,),
|
|
||||||
Row(
|
|
||||||
children: <Widget>[
|
|
||||||
Text("Enable device location tracking"),
|
|
||||||
Switch(
|
|
||||||
value: _locationTrackingEnabled,
|
|
||||||
onChanged: _wait ? null : (value) {
|
|
||||||
setState(() {
|
|
||||||
_locationTrackingEnabled = value;
|
|
||||||
_wait = true;
|
|
||||||
});
|
|
||||||
_switchLocationTrackingState(value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Container(height: Sizes.rowPadding,),
|
|
||||||
Text("Location update interval in minutes:"),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
//Expanded(child: Container(),),
|
|
||||||
FlatButton(
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
child: Text("-", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
|
||||||
onPressed: () => decLocationInterval(),
|
|
||||||
),
|
|
||||||
Text("$_locationInterval", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
|
||||||
FlatButton(
|
|
||||||
padding: EdgeInsets.all(0.0),
|
|
||||||
child: Text("+", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
|
||||||
onPressed: () => incLocationInterval(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Divider(),
|
|
||||||
Text("Integration status", style: TextStyle(fontSize: Sizes.largeFontSize-2)),
|
|
||||||
Container(height: Sizes.rowPadding,),
|
|
||||||
Text("${HomeAssistant().userName}'s ${DeviceInfoManager().model}, ${DeviceInfoManager().osName} ${DeviceInfoManager().osVersion}"),
|
|
||||||
Container(height: 6.0,),
|
|
||||||
Text("Here you can manually check if HA Client integration with your Home Assistant works fine. As mobileApp integration in Home Assistant is still in development, this is not 100% correct check."),
|
|
||||||
//Divider(),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: <Widget>[
|
|
||||||
RaisedButton(
|
|
||||||
color: Colors.blue,
|
|
||||||
onPressed: () => updateRegistration(),
|
|
||||||
child: Text("Check integration", style: TextStyle(color: Colors.white))
|
|
||||||
),
|
|
||||||
Container(width: 10.0,),
|
|
||||||
RaisedButton(
|
|
||||||
color: Colors.redAccent,
|
|
||||||
onPressed: () => resetRegistration(),
|
|
||||||
child: Text("Reset integration", style: TextStyle(color: Colors.white))
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
|
|
||||||
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,
|
||||||
@ -347,11 +343,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
accountName: Text(HomeAssistant().userName),
|
accountName: Text(HomeAssistant().userName),
|
||||||
accountEmail: Text(HomeAssistant().locationName ?? ""),
|
accountEmail: Text(HomeAssistant().locationName ?? ""),
|
||||||
currentAccountPicture: CircleAvatar(
|
currentAccountPicture: CircleAvatar(
|
||||||
|
backgroundColor: Theme.of(context).backgroundColor,
|
||||||
child: Text(
|
child: Text(
|
||||||
HomeAssistant().userAvatarText,
|
HomeAssistant().userAvatarText,
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.display1
|
||||||
fontSize: 32.0
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -360,41 +355,19 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
HomeAssistant().panels.forEach((Panel panel) {
|
HomeAssistant().panels.forEach((Panel panel) {
|
||||||
if (!panel.isHidden) {
|
if (!panel.isHidden) {
|
||||||
menuItems.add(
|
menuItems.add(
|
||||||
new ListTile(
|
panel.getMenuItemWidget(context)
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName(panel.icon)),
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: <Widget>[
|
|
||||||
Text("${panel.title}"),
|
|
||||||
Container(width: 4.0,),
|
|
||||||
panel.isWebView ? Text("webview", style: TextStyle(fontSize: 8.0, color: Colors.black45),) : Container(width: 1.0,)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
panel.handleOpen(context);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
menuItems.addAll([
|
menuItems.addAll([
|
||||||
Divider(),
|
Divider(),
|
||||||
ListTile(
|
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:server-network")),
|
|
||||||
title: Text("Connection settings"),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pushNamed('/connection-settings');
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:cellphone-settings-variant")),
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:cellphone-settings-variant")),
|
||||||
title: Text("Integration settings"),
|
title: Text("App settings"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pushNamed('/integration-settings');
|
Navigator.of(context).pushNamed('/app-settings');
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
@ -435,11 +408,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:forum")),
|
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
|
||||||
title: Text("Contacts/Discussion"),
|
title: Text("Contacts/Discussion"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Launcher.launchURL("https://spectrum.chat/ha-client");
|
Launcher.launchURL("https://discord.gg/nd6FZQ");
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
new ListTile(
|
new ListTile(
|
||||||
@ -458,9 +431,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"ha-client.app",
|
"ha-client.app",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
decoration: TextDecoration.underline
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -474,9 +447,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"Terms and Conditions",
|
"Terms and Conditions",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
decoration: TextDecoration.underline
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -490,9 +463,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"Privacy Policy",
|
"Privacy Policy",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
decoration: TextDecoration.underline
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -519,13 +492,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
bool _showBottomBar = false;
|
bool _showBottomBar = false;
|
||||||
String _bottomBarText;
|
String _bottomBarText;
|
||||||
bool _bottomBarProgress;
|
bool _bottomBarProgress;
|
||||||
Color _bottomBarColor;
|
bool _bottomBarErrorColor;
|
||||||
Timer _bottomBarTimer;
|
Timer _bottomBarTimer;
|
||||||
|
|
||||||
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
||||||
_bottomBarTimer?.cancel();
|
_bottomBarTimer?.cancel();
|
||||||
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
||||||
_bottomBarColor = Colors.grey.shade50;
|
_bottomBarErrorColor = false;
|
||||||
setState(() {
|
setState(() {
|
||||||
_bottomBarText = message;
|
_bottomBarText = message;
|
||||||
_bottomBarProgress = progress;
|
_bottomBarProgress = progress;
|
||||||
@ -539,11 +512,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showErrorBottomBar(HAError error) {
|
void _showErrorBottomBar(HAError error) {
|
||||||
TextStyle textStyle = TextStyle(
|
TextStyle textStyle = Theme.of(context).textTheme.button.copyWith(
|
||||||
color: Colors.blue,
|
decoration: TextDecoration.underline
|
||||||
fontSize: Sizes.nameFontSize
|
|
||||||
);
|
);
|
||||||
_bottomBarColor = Colors.red.shade100;
|
_bottomBarErrorColor = true;
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
error.actions.forEach((HAErrorAction action) {
|
error.actions.forEach((HAErrorAction action) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
@ -615,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 = [];
|
||||||
@ -649,9 +620,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
activePlayers.map((entity) => PopupMenuItem<String>(
|
activePlayers.map((entity) => PopupMenuItem<String>(
|
||||||
child: Text(
|
child: Text(
|
||||||
"${entity.displayName}",
|
"${entity.displayName}",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
color: EntityColor.stateColor(entity.state)
|
color: HAClientTheme().getColorByEntityState(entity.state, context)
|
||||||
),
|
)
|
||||||
),
|
),
|
||||||
value: "${entity.entityId}",
|
value: "${entity.entityId}",
|
||||||
)).toList()
|
)).toList()
|
||||||
@ -680,7 +651,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text("$playersCount", style: TextStyle(fontSize: 12)),
|
child: Text(
|
||||||
|
"$playersCount",
|
||||||
|
style: Theme.of(context).textTheme.caption.copyWith(
|
||||||
|
color: Colors.white
|
||||||
|
)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -698,7 +674,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
FlatButton(
|
FlatButton(
|
||||||
child: Text("Login with Home Assistant", style: TextStyle(fontSize: 16.0, color: Colors.white)),
|
child: Text("Login with Home Assistant", style: Theme.of(context).textTheme.button),
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
onPressed: () => _fullLoad(),
|
onPressed: () => _fullLoad(),
|
||||||
)
|
)
|
||||||
@ -716,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(
|
||||||
@ -793,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,
|
||||||
@ -803,7 +758,6 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
body: mainScrollBody,
|
body: mainScrollBody,
|
||||||
controller: _mainScrollController,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,16 +787,16 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
|||||||
bottomBarChildren.add(
|
bottomBarChildren.add(
|
||||||
CollectionScaleTransition(
|
CollectionScaleTransition(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
|
Icon(Icons.stop, size: 10.0, color: HAClientTheme().getOnStateColor(context),),
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
|
Icon(Icons.stop, size: 10.0, color: HAClientTheme().getDisabledStateColor(context),),
|
||||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
|
Icon(Icons.stop, size: 10.0, color: HAClientTheme().getOffStateColor(context),),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (bottomBarChildren.isNotEmpty) {
|
if (bottomBarChildren.isNotEmpty) {
|
||||||
bottomBar = Container(
|
bottomBar = Container(
|
||||||
color: _bottomBarColor,
|
color: _bottomBarErrorColor ? Theme.of(context).errorColor : Theme.of(context).primaryColorLight,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@ -868,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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +135,9 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
|
|||||||
if (_validationMessage.isNotEmpty) {
|
if (_validationMessage.isNotEmpty) {
|
||||||
children.add(Text(
|
children.add(Text(
|
||||||
"$_validationMessage",
|
"$_validationMessage",
|
||||||
style: TextStyle(color: Colors.red)
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
color: Theme.of(context).errorColor
|
||||||
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
children.addAll(<Widget>[
|
children.addAll(<Widget>[
|
||||||
@ -193,9 +195,9 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
|
|||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
"How?",
|
"How?",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
decoration: TextDecoration.underline
|
decoration: TextDecoration.underline
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -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
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,234 +0,0 @@
|
|||||||
part of '../main.dart';
|
|
||||||
|
|
||||||
class ConnectionSettingsPage extends StatefulWidget {
|
|
||||||
ConnectionSettingsPage({Key key, this.title}) : super(key: key);
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
_ConnectionSettingsPageState createState() => new _ConnectionSettingsPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
|
||||||
String _hassioDomain = "";
|
|
||||||
String _newHassioDomain = "";
|
|
||||||
String _hassioPort = "";
|
|
||||||
String _newHassioPort = "";
|
|
||||||
String _socketProtocol = "wss";
|
|
||||||
String _newSocketProtocol = "wss";
|
|
||||||
String _longLivedToken = "";
|
|
||||||
String _newLongLivedToken = "";
|
|
||||||
bool _useLovelace = true;
|
|
||||||
bool _newUseLovelace = true;
|
|
||||||
|
|
||||||
String oauthUrl;
|
|
||||||
bool useOAuth = false;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_loadSettings();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_loadSettings() async {
|
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
||||||
final storage = new FlutterSecureStorage();
|
|
||||||
|
|
||||||
try {
|
|
||||||
useOAuth = prefs.getBool("oauth-used") ?? true;
|
|
||||||
} catch (e) {
|
|
||||||
useOAuth = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!useOAuth) {
|
|
||||||
try {
|
|
||||||
_longLivedToken = _newLongLivedToken =
|
|
||||||
await storage.read(key: "hacl_llt");
|
|
||||||
} catch (e) {
|
|
||||||
_longLivedToken = _newLongLivedToken = "";
|
|
||||||
await storage.delete(key: "hacl_llt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_hassioDomain = _newHassioDomain = prefs.getString("hassio-domain")?? "";
|
|
||||||
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
|
|
||||||
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
|
|
||||||
try {
|
|
||||||
_useLovelace = _newUseLovelace = prefs.getBool("use-lovelace") ?? true;
|
|
||||||
} catch (e) {
|
|
||||||
_useLovelace = _newUseLovelace = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _checkConfigChanged() {
|
|
||||||
return (
|
|
||||||
(_newHassioPort != _hassioPort) ||
|
|
||||||
(_newHassioDomain != _hassioDomain) ||
|
|
||||||
(_newSocketProtocol != _socketProtocol) ||
|
|
||||||
(_newUseLovelace != _useLovelace) ||
|
|
||||||
(_newLongLivedToken != _longLivedToken));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_saveSettings() async {
|
|
||||||
_newHassioDomain = _newHassioDomain.trim();
|
|
||||||
if (_newHassioDomain.startsWith("http") && _newHassioDomain.indexOf("//") > 0) {
|
|
||||||
_newHassioDomain.startsWith("https") ? _newSocketProtocol = "wss" : _newSocketProtocol = "ws";
|
|
||||||
_newHassioDomain = _newHassioDomain.split("//")[1];
|
|
||||||
}
|
|
||||||
_newHassioDomain = _newHassioDomain.split("/")[0];
|
|
||||||
if (_newHassioDomain.contains(":")) {
|
|
||||||
List<String> domainAndPort = _newHassioDomain.split(":");
|
|
||||||
_newHassioDomain = domainAndPort[0];
|
|
||||||
_newHassioPort = domainAndPort[1];
|
|
||||||
}
|
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
||||||
final storage = new FlutterSecureStorage();
|
|
||||||
if (_newLongLivedToken.isNotEmpty) {
|
|
||||||
_newLongLivedToken = _newLongLivedToken.trim();
|
|
||||||
prefs.setBool("oauth-used", false);
|
|
||||||
await storage.write(key: "hacl_llt", value: _newLongLivedToken);
|
|
||||||
} else if (!useOAuth) {
|
|
||||||
await storage.delete(key: "hacl_llt");
|
|
||||||
}
|
|
||||||
prefs.setString("hassio-domain", _newHassioDomain);
|
|
||||||
if (_newHassioPort == null || _newHassioPort.isEmpty) {
|
|
||||||
_newHassioPort = _newSocketProtocol == "wss" ? "443" : "80";
|
|
||||||
} else {
|
|
||||||
_newHassioPort = _newHassioPort.trim();
|
|
||||||
}
|
|
||||||
prefs.setString("hassio-port", _newHassioPort);
|
|
||||||
prefs.setString("hassio-protocol", _newSocketProtocol);
|
|
||||||
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
|
|
||||||
prefs.setBool("use-lovelace", _newUseLovelace);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return new Scaffold(
|
|
||||||
appBar: new AppBar(
|
|
||||||
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
|
||||||
Navigator.pop(context);
|
|
||||||
}),
|
|
||||||
title: new Text(widget.title),
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
icon: Icon(Icons.check),
|
|
||||||
onPressed: (){
|
|
||||||
if (_checkConfigChanged()) {
|
|
||||||
Logger.d("Settings changed. Saving...");
|
|
||||||
_saveSettings().then((r) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
eventBus.fire(SettingsChangedEvent(true));
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
Logger.d("Settings was not changed");
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
body: ListView(
|
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
padding: const EdgeInsets.all(20.0),
|
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
"Connection settings",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black45,
|
|
||||||
fontSize: 20.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
new Row(
|
|
||||||
children: [
|
|
||||||
Text("Use ssl (HTTPS)"),
|
|
||||||
Switch(
|
|
||||||
value: (_newSocketProtocol == "wss"),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_newSocketProtocol = value ? "wss" : "ws";
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
new TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Home Assistant domain or ip address"
|
|
||||||
),
|
|
||||||
controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioDomain)),
|
|
||||||
onChanged: (value) {
|
|
||||||
_newHassioDomain = value;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
new TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Home Assistant port (default is 8123)"
|
|
||||||
),
|
|
||||||
controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioPort)),
|
|
||||||
onChanged: (value) {
|
|
||||||
_newHassioPort = value;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
new Text(
|
|
||||||
"Try ports 80 and 443 if default is not working and you don't know why.",
|
|
||||||
style: TextStyle(color: Colors.grey),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 20.0),
|
|
||||||
child: Text(
|
|
||||||
"UI",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black45,
|
|
||||||
fontSize: 20.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
new Row(
|
|
||||||
children: [
|
|
||||||
Text("Use Lovelace UI"),
|
|
||||||
Switch(
|
|
||||||
value: _newUseLovelace,
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() {
|
|
||||||
_newUseLovelace = value;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"Authentication settings",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black45,
|
|
||||||
fontSize: 20.0
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(height: 10.0,),
|
|
||||||
Text(
|
|
||||||
"You can leave this field blank to make app generate new long-lived token automatically by asking you to login to your Home Assistant. Use this field only if you still want to use manually generated long-lived token. Leave it blank if you don't understand what we are talking about.",
|
|
||||||
style: TextStyle(color: Colors.redAccent),
|
|
||||||
),
|
|
||||||
new TextField(
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Long-lived token"
|
|
||||||
),
|
|
||||||
controller: TextEditingController.fromValue(TextEditingValue(text: _newLongLivedToken)),
|
|
||||||
onChanged: (value) {
|
|
||||||
_newLongLivedToken = value;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
104
lib/pages/settings/app_settings.page.dart
Normal file
104
lib/pages/settings/app_settings.page.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
enum AppSettingsSection {menu, connectionSettings, integrationSettings, lookAndFeel}
|
||||||
|
|
||||||
|
class AppSettingsPage extends StatefulWidget {
|
||||||
|
final AppSettingsSection showSection;
|
||||||
|
|
||||||
|
AppSettingsPage({Key key, this.showSection: AppSettingsSection.menu}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AppSettingsPageState createState() => new _AppSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||||
|
|
||||||
|
var _currentSection;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_currentSection = widget.showSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenuItem(BuildContext context, IconData icon,String title, AppSettingsSection section) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text(title, style: Theme.of(context).textTheme.subhead),
|
||||||
|
leading: Icon(icon),
|
||||||
|
trailing: Icon(Icons.keyboard_arrow_right),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_currentSection = section;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildMenu(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: <Widget>[
|
||||||
|
_buildMenuItem(context, MaterialDesignIcons.getIconDataFromIconName('mdi:network'), 'Connection settings', AppSettingsSection.connectionSettings),
|
||||||
|
_buildMenuItem(context, MaterialDesignIcons.getIconDataFromIconName('mdi:cellphone-android'), 'Integration settings', AppSettingsSection.integrationSettings),
|
||||||
|
_buildMenuItem(context, MaterialDesignIcons.getIconDataFromIconName('mdi:brush'), 'Look and feel', AppSettingsSection.lookAndFeel),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget section;
|
||||||
|
String title;
|
||||||
|
switch (_currentSection) {
|
||||||
|
case AppSettingsSection.menu: {
|
||||||
|
section = _buildMenu(context);
|
||||||
|
title = 'App settings';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AppSettingsSection.connectionSettings: {
|
||||||
|
section = ConnectionSettingsPage();
|
||||||
|
title = 'App settings - Connection';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AppSettingsSection.integrationSettings: {
|
||||||
|
section = IntegrationSettingsPage();
|
||||||
|
title = 'App settings - Integration';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AppSettingsSection.lookAndFeel: {
|
||||||
|
section = LookAndFeelSettingsPage();
|
||||||
|
title = 'App settings - Look&Feel';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
title = ':(';
|
||||||
|
section = PageLoadingIndicator();
|
||||||
|
}
|
||||||
|
return WillPopScope(
|
||||||
|
child: Scaffold(
|
||||||
|
appBar: new AppBar(
|
||||||
|
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
|
||||||
|
if (_currentSection == AppSettingsSection.menu) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_currentSection = AppSettingsSection.menu;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
title: Text(title),
|
||||||
|
),
|
||||||
|
body: section
|
||||||
|
),
|
||||||
|
onWillPop: () {
|
||||||
|
if (_currentSection == AppSettingsSection.menu) {
|
||||||
|
return Future.value(true);
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
_currentSection = AppSettingsSection.menu;
|
||||||
|
});
|
||||||
|
return Future.value(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
192
lib/pages/settings/connection_settings.part.dart
Normal file
192
lib/pages/settings/connection_settings.part.dart
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class ConnectionSettingsPage extends StatefulWidget {
|
||||||
|
ConnectionSettingsPage({Key key, this.title}) : super(key: key);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_ConnectionSettingsPageState createState() => new _ConnectionSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||||
|
String _hassioDomain = "";
|
||||||
|
String _newHassioDomain = "";
|
||||||
|
String _hassioPort = "";
|
||||||
|
String _newHassioPort = "";
|
||||||
|
String _socketProtocol = "wss";
|
||||||
|
String _newSocketProtocol = "wss";
|
||||||
|
String _longLivedToken = "";
|
||||||
|
String _newLongLivedToken = "";
|
||||||
|
|
||||||
|
String oauthUrl;
|
||||||
|
bool useOAuth = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadSettings();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadSettings() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final storage = new FlutterSecureStorage();
|
||||||
|
|
||||||
|
try {
|
||||||
|
useOAuth = prefs.getBool("oauth-used") ?? true;
|
||||||
|
} catch (e) {
|
||||||
|
useOAuth = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!useOAuth) {
|
||||||
|
try {
|
||||||
|
_longLivedToken = _newLongLivedToken =
|
||||||
|
await storage.read(key: "hacl_llt");
|
||||||
|
} catch (e) {
|
||||||
|
_longLivedToken = _newLongLivedToken = "";
|
||||||
|
await storage.delete(key: "hacl_llt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_hassioDomain = _newHassioDomain = prefs.getString("hassio-domain")?? "";
|
||||||
|
_hassioPort = _newHassioPort = prefs.getString("hassio-port") ?? "";
|
||||||
|
_socketProtocol = _newSocketProtocol = prefs.getString("hassio-protocol") ?? 'wss';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _checkConfigChanged() {
|
||||||
|
return (
|
||||||
|
(_newHassioPort != _hassioPort) ||
|
||||||
|
(_newHassioDomain != _hassioDomain) ||
|
||||||
|
(_newSocketProtocol != _socketProtocol) ||
|
||||||
|
(_newLongLivedToken != _longLivedToken));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveSettings() async {
|
||||||
|
_newHassioDomain = _newHassioDomain.trim();
|
||||||
|
if (_newHassioDomain.startsWith("http") && _newHassioDomain.indexOf("//") > 0) {
|
||||||
|
_newHassioDomain.startsWith("https") ? _newSocketProtocol = "wss" : _newSocketProtocol = "ws";
|
||||||
|
_newHassioDomain = _newHassioDomain.split("//")[1];
|
||||||
|
}
|
||||||
|
_newHassioDomain = _newHassioDomain.split("/")[0];
|
||||||
|
if (_newHassioDomain.contains(":")) {
|
||||||
|
List<String> domainAndPort = _newHassioDomain.split(":");
|
||||||
|
_newHassioDomain = domainAndPort[0];
|
||||||
|
_newHassioPort = domainAndPort[1];
|
||||||
|
}
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
final storage = new FlutterSecureStorage();
|
||||||
|
if (_newLongLivedToken.isNotEmpty) {
|
||||||
|
_newLongLivedToken = _newLongLivedToken.trim();
|
||||||
|
prefs.setBool("oauth-used", false);
|
||||||
|
await storage.write(key: "hacl_llt", value: _newLongLivedToken);
|
||||||
|
} else if (!useOAuth) {
|
||||||
|
await storage.delete(key: "hacl_llt");
|
||||||
|
}
|
||||||
|
prefs.setString("hassio-domain", _newHassioDomain);
|
||||||
|
if (_newHassioPort == null || _newHassioPort.isEmpty) {
|
||||||
|
_newHassioPort = _newSocketProtocol == "wss" ? "443" : "80";
|
||||||
|
} else {
|
||||||
|
_newHassioPort = _newHassioPort.trim();
|
||||||
|
}
|
||||||
|
prefs.setString("hassio-port", _newHassioPort);
|
||||||
|
prefs.setString("hassio-protocol", _newSocketProtocol);
|
||||||
|
prefs.setString("hassio-res-protocol", _newSocketProtocol == "wss" ? "https" : "http");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
"Connection settings",
|
||||||
|
style: Theme.of(context).textTheme.headline,
|
||||||
|
),
|
||||||
|
new Row(
|
||||||
|
children: [
|
||||||
|
Text("Use ssl (HTTPS)"),
|
||||||
|
Switch(
|
||||||
|
value: (_newSocketProtocol == "wss"),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_newSocketProtocol = value ? "wss" : "ws";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
new TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Home Assistant domain or ip address"
|
||||||
|
),
|
||||||
|
controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioDomain)),
|
||||||
|
onChanged: (value) {
|
||||||
|
_newHassioDomain = value;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Home Assistant port (default is 8123)"
|
||||||
|
),
|
||||||
|
controller: TextEditingController.fromValue(TextEditingValue(text: _newHassioPort)),
|
||||||
|
onChanged: (value) {
|
||||||
|
_newHassioPort = value;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
new Text(
|
||||||
|
"Try ports 80 and 443 if default is not working and you don't know why.",
|
||||||
|
style: Theme.of(context).textTheme.caption,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Authentication settings",
|
||||||
|
style: Theme.of(context).textTheme.headline,
|
||||||
|
),
|
||||||
|
Container(height: 10.0,),
|
||||||
|
Text(
|
||||||
|
"You can leave this field blank to make app generate new long-lived token automatically by asking you to login to your Home Assistant. Use this field only if you still want to use manually generated long-lived token. Leave it blank if you don't understand what we are talking about.",
|
||||||
|
style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
color: Colors.redAccent
|
||||||
|
),
|
||||||
|
),
|
||||||
|
new TextField(
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Long-lived token"
|
||||||
|
),
|
||||||
|
controller: TextEditingController.fromValue(TextEditingValue(text: _newLongLivedToken)),
|
||||||
|
onChanged: (value) {
|
||||||
|
_newLongLivedToken = value;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: Sizes.rowPadding,
|
||||||
|
),
|
||||||
|
RaisedButton(
|
||||||
|
child: Text('Apply', style: Theme.of(context).textTheme.button),
|
||||||
|
color: Theme.of(context).primaryColorDark,
|
||||||
|
onPressed: () {
|
||||||
|
if (_checkConfigChanged()) {
|
||||||
|
Logger.d("Settings changed. Saving...");
|
||||||
|
_saveSettings().then((r) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
eventBus.fire(SettingsChangedEvent(true));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Logger.d("Settings was not changed");
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
194
lib/pages/settings/integration_settings.part.dart
Normal file
194
lib/pages/settings/integration_settings.part.dart
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class IntegrationSettingsPage extends StatefulWidget {
|
||||||
|
IntegrationSettingsPage({Key key, this.title}) : super(key: key);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_IntegrationSettingsPageState createState() => new _IntegrationSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
||||||
|
|
||||||
|
int _locationInterval = LocationManager().defaultUpdateIntervalMinutes;
|
||||||
|
bool _locationTrackingEnabled = false;
|
||||||
|
bool _wait = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadSettings();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadSettings() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.reload();
|
||||||
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
|
setState(() {
|
||||||
|
_locationTrackingEnabled = prefs.getBool("location-enabled") ?? false;
|
||||||
|
_locationInterval = prefs.getInt("location-interval") ?? LocationManager().defaultUpdateIntervalMinutes;
|
||||||
|
if (_locationInterval % 5 != 0) {
|
||||||
|
_locationInterval = 5 * (_locationInterval ~/ 5);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void incLocationInterval() {
|
||||||
|
if (_locationInterval < 720) {
|
||||||
|
setState(() {
|
||||||
|
_locationInterval = _locationInterval + 5;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decLocationInterval() {
|
||||||
|
if (_locationInterval > 5) {
|
||||||
|
setState(() {
|
||||||
|
_locationInterval = _locationInterval - 5;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
restart() {
|
||||||
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
|
title: "Are you sure you want to restart Home Assistant?",
|
||||||
|
body: "This will restart your Home Assistant server.",
|
||||||
|
positiveText: "Sure. Make it so",
|
||||||
|
negativeText: "What?? No!",
|
||||||
|
onPositive: () {
|
||||||
|
ConnectionManager().callService(domain: "homeassistant", service: "restart");
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
|
title: "Are you sure you want to STOP Home Assistant?",
|
||||||
|
body: "This will STOP your Home Assistant server. It means that your web interface as well as HA Client will not work untill you'll find a way to start your server using ssh or something.",
|
||||||
|
positiveText: "Sure. Make it so",
|
||||||
|
negativeText: "What?? No!",
|
||||||
|
onPositive: () {
|
||||||
|
ConnectionManager().callService(domain: "homeassistant", service: "stop");
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateRegistration() {
|
||||||
|
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetRegistration() {
|
||||||
|
eventBus.fire(ShowPopupDialogEvent(
|
||||||
|
title: "Waaaait",
|
||||||
|
body: "If you don't whant to have duplicate integrations and entities in your HA for your current device, first you need to remove MobileApp integration from Integration settings in HA and restart server.",
|
||||||
|
positiveText: "Done it already",
|
||||||
|
negativeText: "Ok, I will",
|
||||||
|
onPositive: () {
|
||||||
|
MobileAppIntegrationManager.checkAppRegistration(showOkDialog: true, forceRegister: true);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
_switchLocationTrackingState(bool state) async {
|
||||||
|
if (state) {
|
||||||
|
await LocationManager().updateDeviceLocation();
|
||||||
|
}
|
||||||
|
await LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
|
||||||
|
setState(() {
|
||||||
|
_wait = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Location tracking", style: Theme.of(context).textTheme.title),
|
||||||
|
Container(height: Sizes.rowPadding,),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => Launcher.launchURLInCustomTab(context: context, url: "http://ha-client.app/docs#location-tracking"),
|
||||||
|
child: Text(
|
||||||
|
"Please read documentation!",
|
||||||
|
style: Theme.of(context).textTheme.subhead.copyWith(
|
||||||
|
color: Colors.blue,
|
||||||
|
decoration: TextDecoration.underline
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(height: Sizes.rowPadding,),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Enable device location tracking"),
|
||||||
|
Switch(
|
||||||
|
value: _locationTrackingEnabled,
|
||||||
|
onChanged: _wait ? null : (value) {
|
||||||
|
setState(() {
|
||||||
|
_locationTrackingEnabled = value;
|
||||||
|
_wait = true;
|
||||||
|
});
|
||||||
|
_switchLocationTrackingState(value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Container(height: Sizes.rowPadding,),
|
||||||
|
Text("Location update interval in minutes:"),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
//Expanded(child: Container(),),
|
||||||
|
FlatButton(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
child: Text("-", style: Theme.of(context).textTheme.title),
|
||||||
|
onPressed: () => decLocationInterval(),
|
||||||
|
),
|
||||||
|
Text("$_locationInterval", style: Theme.of(context).textTheme.title),
|
||||||
|
FlatButton(
|
||||||
|
padding: EdgeInsets.all(0.0),
|
||||||
|
child: Text("+", style: Theme.of(context).textTheme.title),
|
||||||
|
onPressed: () => incLocationInterval(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
Text("Integration status", style: Theme.of(context).textTheme.title),
|
||||||
|
Container(height: Sizes.rowPadding,),
|
||||||
|
Text(
|
||||||
|
"${HomeAssistant().userName}'s ${DeviceInfoManager().model}, ${DeviceInfoManager().osName} ${DeviceInfoManager().osVersion}",
|
||||||
|
style: Theme.of(context).textTheme.subtitle,
|
||||||
|
),
|
||||||
|
Container(height: 6.0,),
|
||||||
|
Text("Here you can manually check if HA Client integration with your Home Assistant works fine. As mobileApp integration in Home Assistant is still in development, this is not 100% correct check."),
|
||||||
|
//Divider(),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: <Widget>[
|
||||||
|
RaisedButton(
|
||||||
|
color: Colors.blue,
|
||||||
|
onPressed: () => updateRegistration(),
|
||||||
|
child: Text("Check integration", style: Theme.of(context).textTheme.button)
|
||||||
|
),
|
||||||
|
Container(width: 10.0,),
|
||||||
|
RaisedButton(
|
||||||
|
color: Colors.redAccent,
|
||||||
|
onPressed: () => resetRegistration(),
|
||||||
|
child: Text("Reset integration", style: Theme.of(context).textTheme.button)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
LocationManager().setSettings(_locationTrackingEnabled, _locationInterval);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
79
lib/pages/settings/lookandfeel_settings.part.dart
Normal file
79
lib/pages/settings/lookandfeel_settings.part.dart
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
part of '../../main.dart';
|
||||||
|
|
||||||
|
class LookAndFeelSettingsPage extends StatefulWidget {
|
||||||
|
LookAndFeelSettingsPage({Key key, this.title}) : super(key: key);
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LookAndFeelSettingsPageState createState() => new _LookAndFeelSettingsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LookAndFeelSettingsPageState extends State<LookAndFeelSettingsPage> {
|
||||||
|
|
||||||
|
AppTheme _currentTheme;
|
||||||
|
bool _changed = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadSettings() async {
|
||||||
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.reload();
|
||||||
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
|
setState(() {
|
||||||
|
_currentTheme = AppTheme.values[prefs.getInt("app-theme") ?? AppTheme.defaultTheme];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveSettings(AppTheme theme) {
|
||||||
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
|
prefs.setInt('app-theme', theme.index);
|
||||||
|
setState(() {
|
||||||
|
_currentTheme = theme;
|
||||||
|
eventBus.fire(ChangeThemeEvent(_currentTheme));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Map appThemeName = {
|
||||||
|
AppTheme.defaultTheme: 'Default',
|
||||||
|
AppTheme.haTheme: 'Home Assistant theme',
|
||||||
|
AppTheme.darkTheme: 'Dark theme'
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
children: <Widget>[
|
||||||
|
Text("Application theme:", style: Theme.of(context).textTheme.body2),
|
||||||
|
Container(height: Sizes.rowPadding),
|
||||||
|
DropdownButton<AppTheme>(
|
||||||
|
value: _currentTheme,
|
||||||
|
iconSize: 30.0,
|
||||||
|
isExpanded: true,
|
||||||
|
style: Theme.of(context).textTheme.title,
|
||||||
|
//hint: Text("Select ${caption.toLowerCase()}"),
|
||||||
|
items: AppTheme.values.map((value) {
|
||||||
|
return new DropdownMenuItem<AppTheme>(
|
||||||
|
value: value,
|
||||||
|
child: Text('${appThemeName[value]}'),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
onChanged: (theme) => _saveSettings(theme),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -24,7 +24,7 @@ class _WhatsNewPageState extends State<WhatsNewPage> {
|
|||||||
error = "";
|
error = "";
|
||||||
});
|
});
|
||||||
http.Response response;
|
http.Response response;
|
||||||
response = await http.get("http://ha-client.app/service/whats_new_0.8.md");
|
response = await http.get("http://ha-client.app/service/whats_new_0.8.3.md");
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
setState(() {
|
setState(() {
|
||||||
data = response.body;
|
data = response.body;
|
||||||
|
@ -10,8 +10,7 @@ class LastUpdatedWidget extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'${entityModel.entityWrapper.entity.lastUpdated}',
|
'${entityModel.entityWrapper.entity.lastUpdated}',
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.caption
|
||||||
fontSize: Sizes.smallFontSize, color: Colors.black26),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class PageLoadingError extends StatelessWidget {
|
|||||||
size: 48.0
|
size: 48.0
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Text(this.errorText, style: TextStyle(color: Colors.black45))
|
Text(this.errorText, style: Theme.of(context).textTheme.subtitle)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -14,7 +14,7 @@ class PageLoadingIndicator extends StatelessWidget {
|
|||||||
padding: EdgeInsets.only(top: 40.0, bottom: 20.0),
|
padding: EdgeInsets.only(top: 40.0, bottom: 20.0),
|
||||||
child: CircularProgressIndicator()
|
child: CircularProgressIndicator()
|
||||||
),
|
),
|
||||||
Text("Loading...", style: TextStyle(color: Colors.black45))
|
Text("Loading...", style: Theme.of(context).textTheme.subtitle)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -40,10 +40,7 @@ class ProductPurchase extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
"${product.title}",
|
"${product.title}",
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.body2,
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16.0
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Container(height: Sizes.rowPadding,),
|
Container(height: Sizes.rowPadding,),
|
||||||
Text(
|
Text(
|
||||||
@ -53,7 +50,9 @@ class ProductPurchase extends StatelessWidget {
|
|||||||
softWrap: true,
|
softWrap: true,
|
||||||
),
|
),
|
||||||
Container(height: Sizes.rowPadding,),
|
Container(height: Sizes.rowPadding,),
|
||||||
Text("${product.price} $period", style: TextStyle(color: priceColor)),
|
Text("${product.price} $period", style: Theme.of(context).textTheme.body1.copyWith(
|
||||||
|
color: priceColor
|
||||||
|
)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@ -61,7 +60,7 @@ class ProductPurchase extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: RaisedButton(
|
child: RaisedButton(
|
||||||
child: Text(this.purchased ? buttonTextInactive : buttonText, style: TextStyle(color: Colors.white)),
|
child: Text(this.purchased ? buttonTextInactive : buttonText, style: Theme.of(context).textTheme.button),
|
||||||
color: Colors.blue,
|
color: Colors.blue,
|
||||||
onPressed: this.purchased ? null : () => this.onBuy(this.product),
|
onPressed: this.purchased ? null : () => this.onBuy(this.product),
|
||||||
),
|
),
|
||||||
|
@ -56,7 +56,7 @@ class Panel {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text("${this.title}"),
|
Text("${this.title}"),
|
||||||
Container(width: 4.0,),
|
Container(width: 4.0,),
|
||||||
isWebView ? Text("webview", style: TextStyle(fontSize: 8.0, color: Colors.black45),) : Container(width: 1.0,)
|
isWebView ? Text("webview", style: Theme.of(context).textTheme.overline) : Container(width: 1.0,)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -16,7 +16,7 @@ class LinkToWebConfig extends StatelessWidget {
|
|||||||
title: Text("${this.name}",
|
title: Text("${this.name}",
|
||||||
textAlign: TextAlign.left,
|
textAlign: TextAlign.left,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
style: Theme.of(context).textTheme.headline),
|
||||||
subtitle: Text("Tap to open web version"),
|
subtitle: Text("Tap to open web version"),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Launcher.launchAuthenticatedWebView(context: context, url: this.url, title: this.name);
|
Launcher.launchAuthenticatedWebView(context: context, url: this.url, title: this.name);
|
||||||
|
@ -156,7 +156,8 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
|||||||
result.add(
|
result.add(
|
||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: "value",
|
id: "value",
|
||||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor("_", historyMoment.colorId),
|
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||||
|
HAClientTheme().chartHistoryStateColor("_", historyMoment.colorId, context),
|
||||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
||||||
if (historyMoment.hiddenDot) {
|
if (historyMoment.hiddenDot) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
@ -179,7 +180,8 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
|||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: 'state',
|
id: 'state',
|
||||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
|
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 4.0,
|
||||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||||
|
HAClientTheme().chartHistoryStateColor(historyMoment.state, historyMoment.colorId, context),
|
||||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||||
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||||
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||||
|
@ -28,7 +28,7 @@ class HistoryControlWidget extends StatelessWidget {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(right: 10.0),
|
padding: EdgeInsets.only(right: 10.0),
|
||||||
child: _buildStates(),
|
child: _buildStates(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
_buildTime(),
|
_buildTime(),
|
||||||
@ -46,18 +46,16 @@ class HistoryControlWidget extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStates() {
|
Widget _buildStates(BuildContext context) {
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
for (int i = 0; i < selectedStates.length; i++) {
|
for (int i = 0; i < selectedStates.length; i++) {
|
||||||
children.add(
|
children.add(
|
||||||
Text(
|
Text(
|
||||||
"${selectedStates[i] ?? '-'}",
|
"${selectedStates[i] ?? '-'}",
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: TextStyle(
|
style: Theme.of(context).textTheme.title.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
color: HAClientTheme().historyStateColor(selectedStates[i], colorIndexes[i], context)
|
||||||
color: EntityColor.historyStateColor(selectedStates[i], colorIndexes[i]),
|
)
|
||||||
fontSize: 22.0
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,8 @@ class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChar
|
|||||||
return [
|
return [
|
||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: 'State',
|
id: 'State',
|
||||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(EntityState.on, -1),
|
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||||
|
HAClientTheme().chartHistoryStateColor(EntityState.on, -1, context),
|
||||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||||
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
|
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -107,7 +107,8 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
|||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: 'State',
|
id: 'State',
|
||||||
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 6.0 : 3.0,
|
strokeWidthPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 6.0 : 3.0,
|
||||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||||
|
HAClientTheme().chartHistoryStateColor(historyMoment.state, historyMoment.colorId, context),
|
||||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||||
data: data,
|
data: data,
|
||||||
@ -115,7 +116,8 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
|||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: 'State',
|
id: 'State',
|
||||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
||||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||||
|
HAClientTheme().chartHistoryStateColor(historyMoment.state, historyMoment.colorId, context),
|
||||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||||
data: data,
|
data: data,
|
||||||
@ -123,7 +125,8 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
|||||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||||
id: 'State',
|
id: 'State',
|
||||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
radiusPxFn: (EntityHistoryMoment historyMoment, __) => (historyMoment.id == _selectedId) ? 5.0 : 3.0,
|
||||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(historyMoment.state, historyMoment.colorId),
|
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||||
|
HAClientTheme().chartHistoryStateColor(historyMoment.state, historyMoment.colorId, context),
|
||||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||||
data: data,
|
data: data,
|
||||||
|
@ -28,6 +28,13 @@ class ReloadUIEvent {
|
|||||||
ReloadUIEvent();
|
ReloadUIEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ChangeThemeEvent {
|
||||||
|
|
||||||
|
final AppTheme theme;
|
||||||
|
|
||||||
|
ChangeThemeEvent(this.theme);
|
||||||
|
}
|
||||||
|
|
||||||
class StartAuthEvent {
|
class StartAuthEvent {
|
||||||
String oauthUrl;
|
String oauthUrl;
|
||||||
bool showButton;
|
bool showButton;
|
||||||
|
@ -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.0+886
|
version: 0.8.4+892
|
||||||
|
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
@ -32,7 +32,8 @@ dependencies:
|
|||||||
workmanager: ^0.2.2
|
workmanager: ^0.2.2
|
||||||
battery: ^0.3.1+7
|
battery: ^0.3.1+7
|
||||||
firebase_crashlytics: ^0.1.3+3
|
firebase_crashlytics: ^0.1.3+3
|
||||||
video_player: ^0.10.7
|
syncfusion_flutter_core: ^18.1.43
|
||||||
|
syncfusion_flutter_gauges: ^18.1.43
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
@ -53,6 +54,7 @@ flutter:
|
|||||||
- images/hassio-192x192.png
|
- images/hassio-192x192.png
|
||||||
- assets/js/externalAuth.js
|
- assets/js/externalAuth.js
|
||||||
- assets/html/cameraView.html
|
- assets/html/cameraView.html
|
||||||
|
- assets/html/cameraLiveView.html
|
||||||
|
|
||||||
fonts:
|
fonts:
|
||||||
- family: "Material Design Icons"
|
- family: "Material Design Icons"
|
||||||
|
11
tool/secrets.dart
Normal file
11
tool/secrets.dart
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
final config = {
|
||||||
|
'syncfusion_license_key': Platform.environment['SYNCFUSION_LICENSE_KEY'],
|
||||||
|
};
|
||||||
|
|
||||||
|
final filename = 'lib/.secrets.dart';
|
||||||
|
File(filename).writeAsString('final secrets = ${json.encode(config)};');
|
||||||
|
}
|
Reference in New Issue
Block a user