Compare commits
23 Commits
beta/0.8.1
...
beta/0.8.2
Author | SHA1 | Date | |
---|---|---|---|
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
|
||||
|
||||
key.properties
|
||||
premium_features_manager.class.dart
|
||||
.secrets.dart
|
||||
pubspec.lock
|
||||
|
@ -3,7 +3,7 @@ image:
|
||||
|
||||
tasks:
|
||||
- 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
|
||||
touch /home/gitpod/.android/repositories.cfg
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
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>
|
@ -198,9 +198,6 @@ class CardWidget extends StatelessWidget {
|
||||
body.add(CardHeader(
|
||||
name: card.name ?? "",
|
||||
subtitle: Text("${card.linkedEntityWrapper.entity.displayState}",
|
||||
style: TextStyle(
|
||||
color: Colors.grey
|
||||
),
|
||||
),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -18,7 +18,7 @@ class CardHeader extends StatelessWidget {
|
||||
title: Text("$name",
|
||||
textAlign: TextAlign.left,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
|
||||
style: Theme.of(context).textTheme.headline),
|
||||
);
|
||||
} else {
|
||||
result = new Container(width: 0.0, height: 0.0);
|
||||
|
@ -48,8 +48,7 @@ class EntityButtonCardBody extends StatelessWidget {
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
maxLines: 3,
|
||||
wordsWrap: true,
|
||||
textAlign: TextAlign.center,
|
||||
fontSize: Sizes.nameFontSize,
|
||||
textAlign: TextAlign.center
|
||||
);
|
||||
}
|
||||
return Container(width: 0, height: 0);
|
||||
|
@ -14,10 +14,11 @@ class GaugeCardBody extends StatefulWidget {
|
||||
|
||||
class _GaugeCardBodyState extends State<GaugeCardBody> {
|
||||
|
||||
List<charts.Series> seriesList;
|
||||
|
||||
List<charts.Series<GaugeSegment, String>> _createData(double value) {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
double fixedValue;
|
||||
double value = entityWrapper.entity.doubleState;
|
||||
if (value > widget.max) {
|
||||
fixedValue = widget.max.toDouble();
|
||||
} else if (value < widget.min) {
|
||||
@ -25,130 +26,151 @@ class _GaugeCardBodyState extends State<GaugeCardBody> {
|
||||
} else {
|
||||
fixedValue = value;
|
||||
}
|
||||
double toShow = ((fixedValue - widget.min) / (widget.max - widget.min)) * 100;
|
||||
Color mainColor;
|
||||
if (widget.severity != null) {
|
||||
if (widget.severity["red"] is int && fixedValue >= widget.severity["red"]) {
|
||||
mainColor = Colors.red;
|
||||
} else if (widget.severity["yellow"] is int && fixedValue >= widget.severity["yellow"]) {
|
||||
mainColor = Colors.amber;
|
||||
} else {
|
||||
mainColor = Colors.green;
|
||||
}
|
||||
} else {
|
||||
mainColor = Colors.green;
|
||||
|
||||
List<GaugeRange> ranges;
|
||||
if (widget.severity != null && widget.severity["green"] is int && widget.severity["red"] is int && widget.severity["yellow"] is int) {
|
||||
List<RangeContainer> rangesList = <RangeContainer>[
|
||||
RangeContainer(widget.severity["green"], HAClientTheme().getGreenGaugeColor()),
|
||||
RangeContainer(widget.severity["red"], HAClientTheme().getRedGaugeColor()),
|
||||
RangeContainer(widget.severity["yellow"], HAClientTheme().getYellowGaugeColor())
|
||||
];
|
||||
rangesList.sort((current, next) {
|
||||
if (current.startFrom > next.startFrom) {
|
||||
return 1;
|
||||
}
|
||||
if (current.startFrom < next.startFrom) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
ranges = [
|
||||
GaugeRange(
|
||||
startValue: rangesList[0].startFrom.toDouble(),
|
||||
endValue: rangesList[1].startFrom.toDouble(),
|
||||
color: fixedValue < rangesList[1].startFrom ? rangesList[0].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: (fixedValue < rangesList[2].startFrom && fixedValue >= rangesList[1].startFrom) ? rangesList[1].color : rangesList[1].color.withOpacity(0.1),
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
endWidth: 0.3,
|
||||
startWidth: 0.3
|
||||
),
|
||||
GaugeRange(
|
||||
startValue: rangesList[2].startFrom.toDouble(),
|
||||
endValue: widget.max.toDouble(),
|
||||
color: fixedValue >= rangesList[2].startFrom ? rangesList[2].color : rangesList[2].color.withOpacity(0.1),
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
endWidth: 0.3,
|
||||
startWidth: 0.3
|
||||
)
|
||||
];
|
||||
}
|
||||
if (ranges == null) {
|
||||
ranges = <GaugeRange>[
|
||||
GaugeRange(
|
||||
startValue: widget.min.toDouble(),
|
||||
endValue: widget.max.toDouble(),
|
||||
color: Theme.of(context).primaryColorDark,
|
||||
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(
|
||||
onTap: () => entityWrapper.handleTap(),
|
||||
onLongPress: () => entityWrapper.handleHold(),
|
||||
onDoubleTap: () => entityWrapper.handleDoubleTap(),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
overflow: Overflow.clip,
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
double verticalOffset;
|
||||
if(constraints.maxWidth > 150.0) {
|
||||
verticalOffset = 0.2;
|
||||
} else if (constraints.maxWidth > 100.0) {
|
||||
verticalOffset = 0.3;
|
||||
} else {
|
||||
verticalOffset = 0.3;
|
||||
}
|
||||
return FractionallySizedBox(
|
||||
heightFactor: 2,
|
||||
widthFactor: 1,
|
||||
alignment: FractionalOffset(0,verticalOffset),
|
||||
child: charts.PieChart(
|
||||
_createData(entityWrapper.entity.doubleState),
|
||||
animate: false,
|
||||
defaultRenderer: charts.ArcRendererConfig(
|
||||
arcRatio: 0.4,
|
||||
startAngle: pi,
|
||||
arcLength: pi,
|
||||
),
|
||||
aspectRatio: 2,
|
||||
child: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
double fontSizeFactor;
|
||||
if (constraints.maxWidth > 300.0) {
|
||||
fontSizeFactor = 1.6;
|
||||
} else if (constraints.maxWidth > 150.0) {
|
||||
fontSizeFactor = 1;
|
||||
} else if (constraints.maxWidth > 100.0) {
|
||||
fontSizeFactor = 0.6;
|
||||
} else {
|
||||
fontSizeFactor = 0.4;
|
||||
}
|
||||
return SfRadialGauge(
|
||||
axes: <RadialAxis>[
|
||||
RadialAxis(
|
||||
maximum: widget.max.toDouble(),
|
||||
minimum: widget.min.toDouble(),
|
||||
showLabels: false,
|
||||
showTicks: false,
|
||||
canScaleToFit: true,
|
||||
ranges: ranges,
|
||||
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
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
GaugeAnnotation(
|
||||
angle: 180,
|
||||
positionFactor: 0,
|
||||
verticalAlignment: GaugeAlignment.center,
|
||||
widget: SimpleEntityState(
|
||||
expanded: false,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
textStyle: Theme.of(context).textTheme.title.copyWith(
|
||||
fontSize: Theme.of(context).textTheme.title.fontSize * fontSizeFactor,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
axisLineStyle: AxisLineStyle(
|
||||
thickness: 0.3,
|
||||
thicknessUnit: GaugeSizeUnit.factor
|
||||
),
|
||||
startAngle: 180,
|
||||
endAngle: 0,
|
||||
pointers: <GaugePointer>[
|
||||
NeedlePointer(
|
||||
value: fixedValue,
|
||||
lengthUnit: GaugeSizeUnit.factor,
|
||||
needleLength: 0.9,
|
||||
needleColor: Theme.of(context).accentColor,
|
||||
enableAnimation: true,
|
||||
needleStartWidth: 1,
|
||||
animationType: AnimationType.bounceOut,
|
||||
needleEndWidth: 3,
|
||||
knobStyle: KnobStyle(
|
||||
sizeUnit: GaugeSizeUnit.factor,
|
||||
color: Theme.of(context).buttonColor,
|
||||
knobRadius: 0.1
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GaugeSegment {
|
||||
final String segment;
|
||||
final double value;
|
||||
final charts.Color color;
|
||||
class RangeContainer {
|
||||
final int startFrom;
|
||||
Color color;
|
||||
|
||||
GaugeSegment(this.segment, this.value, Color color)
|
||||
: this.color = charts.Color(
|
||||
r: color.red, g: color.green, b: color.blue, a: color.alpha);
|
||||
RangeContainer(this.startFrom, this.color);
|
||||
}
|
@ -6,7 +6,6 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
final bool showState;
|
||||
final bool nameInTheBottom;
|
||||
final double iconSize;
|
||||
final double nameFontSize;
|
||||
final bool wordsWrapInName;
|
||||
|
||||
GlanceCardEntityContainer({
|
||||
@ -15,7 +14,6 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
@required this.showState,
|
||||
this.nameInTheBottom: false,
|
||||
this.iconSize: Sizes.iconSize,
|
||||
this.nameFontSize: Sizes.smallFontSize,
|
||||
this.wordsWrapInName: false
|
||||
}) : super(key: key);
|
||||
|
||||
@ -31,7 +29,7 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
List<Widget> result = [];
|
||||
if (!nameInTheBottom) {
|
||||
if (showName) {
|
||||
result.add(_buildName());
|
||||
result.add(_buildName(context));
|
||||
}
|
||||
} else {
|
||||
if (showState) {
|
||||
@ -49,7 +47,7 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
result.add(_buildState());
|
||||
}
|
||||
} else {
|
||||
result.add(_buildName());
|
||||
result.add(_buildName(context));
|
||||
}
|
||||
|
||||
return Center(
|
||||
@ -65,13 +63,13 @@ class GlanceCardEntityContainer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildName() {
|
||||
Widget _buildName(BuildContext context) {
|
||||
return EntityName(
|
||||
padding: EdgeInsets.only(bottom: Sizes.rowPadding),
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
wordsWrap: wordsWrapInName,
|
||||
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,
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -126,10 +126,10 @@ class Sizes {
|
||||
static const extendedWidgetHeight = 50.0;
|
||||
static const iconSize = 28.0;
|
||||
static const largeIconSize = 46.0;
|
||||
static const stateFontSize = 15.0;
|
||||
static const nameFontSize = 15.0;
|
||||
static const smallFontSize = 14.0;
|
||||
static const largeFontSize = 24.0;
|
||||
//static const stateFontSize = 15.0;
|
||||
//static const nameFontSize = 15.0;
|
||||
//static const smallFontSize = 14.0;
|
||||
//static const largeFontSize = 24.0;
|
||||
static const inputWidth = 160.0;
|
||||
static const rowPadding = 10.0;
|
||||
static const doubleRowPadding = rowPadding*2;
|
||||
|
@ -248,7 +248,9 @@ class _AlarmControlPanelControlsWidgetWidgetState extends State<AlarmControlPane
|
||||
FlatButton(
|
||||
child: Text(
|
||||
"TRIGGER",
|
||||
style: TextStyle(color: Colors.redAccent)
|
||||
style: Theme.of(context).textTheme.subhead.copyWith(
|
||||
color: Theme.of(context).errorColor
|
||||
)
|
||||
),
|
||||
onPressed: () => _askToTrigger(entity),
|
||||
)
|
||||
|
@ -7,8 +7,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
double iconSize = 26.0;
|
||||
Widget badgeIcon;
|
||||
String onBadgeTextValue;
|
||||
Color iconColor = EntityColor.badgeColors[entityModel.entityWrapper.entity.domain] ??
|
||||
EntityColor.badgeColors["default"];
|
||||
Color iconColor = HAClientTheme().getBadgeColor(entityModel.entityWrapper.entity.domain);
|
||||
switch (entityModel.entityWrapper.entity.domain) {
|
||||
case "sun":
|
||||
{
|
||||
@ -30,7 +29,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
badgeIcon = EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
size: iconSize,
|
||||
color: Colors.black
|
||||
color: Theme.of(context).textTheme.body1.color
|
||||
);
|
||||
break;
|
||||
}
|
||||
@ -40,7 +39,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
badgeIcon = EntityIcon(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
size: iconSize,
|
||||
color: Colors.black
|
||||
color: Theme.of(context).textTheme.body1.color
|
||||
);
|
||||
onBadgeTextValue = entityModel.entityWrapper.entity.displayState;
|
||||
break;
|
||||
@ -64,7 +63,9 @@ class BadgeWidget extends StatelessWidget {
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: stateFontSize),
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
fontSize: stateFontSize
|
||||
)
|
||||
),
|
||||
);
|
||||
break;
|
||||
@ -77,7 +78,9 @@ class BadgeWidget extends StatelessWidget {
|
||||
onBadgeText = Container(
|
||||
padding: EdgeInsets.fromLTRB(6.0, 2.0, 6.0, 2.0),
|
||||
child: Text("$onBadgeTextValue",
|
||||
style: TextStyle(fontSize: 12.0, color: Colors.white),
|
||||
style: Theme.of(context).textTheme.overline.copyWith(
|
||||
color: HAClientTheme().getOnBadgeTextColor()
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade),
|
||||
@ -98,7 +101,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
decoration: new BoxDecoration(
|
||||
// Circle shape
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).cardColor,
|
||||
// The border you want
|
||||
border: new Border.all(
|
||||
width: 2.0,
|
||||
@ -131,7 +134,7 @@ class BadgeWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
"${entityModel.entityWrapper.displayName}",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(fontSize: 12.0),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
@ -14,8 +14,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
|
||||
CameraEntity _entity;
|
||||
String _streamUrl = "";
|
||||
VideoPlayerController _videoPlayerController;
|
||||
Timer _monitorTimer;
|
||||
bool _isLoaded = false;
|
||||
double _aspectRatio = 1.33;
|
||||
String _webViewHtml;
|
||||
@ -41,11 +39,15 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
if (_entity.supportStream) {
|
||||
HomeAssistant().getCameraStream(_entity.entityId)
|
||||
.then((data) {
|
||||
if (_videoPlayerController != null) {
|
||||
_videoPlayerController.dispose().then((_) => createPlayer(data));
|
||||
} else {
|
||||
createPlayer(data);
|
||||
}
|
||||
_jsMessageChannelName = 'HA_${_entity.entityId.replaceAll('.', '_')}';
|
||||
rootBundle.loadString('assets/html/cameraLiveView.html').then((file) {
|
||||
_webViewHtml = Uri.dataFromString(
|
||||
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) {
|
||||
_loading.completeError(e);
|
||||
@ -67,40 +69,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
return _loading.future;
|
||||
}
|
||||
|
||||
void createPlayer(data) {
|
||||
_videoPlayerController = VideoPlayerController.network("${ConnectionManager().httpWebHost}${data["url"]}");
|
||||
_videoPlayerController.initialize().then((_) {
|
||||
setState((){
|
||||
_aspectRatio = _videoPlayerController.value.aspectRatio;
|
||||
});
|
||||
_loading.complete();
|
||||
autoPlay();
|
||||
startMonitor();
|
||||
}).catchError((e) {
|
||||
_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 screenWidget;
|
||||
if (!_isLoaded) {
|
||||
@ -109,16 +77,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
);
|
||||
} else if (_entity.supportStream) {
|
||||
if (_videoPlayerController.value.initialized) {
|
||||
screenWidget = VideoPlayer(_videoPlayerController);
|
||||
} else {
|
||||
screenWidget = Center(
|
||||
child: EntityPicture(
|
||||
fit: BoxFit.contain,
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
screenWidget = WebView(
|
||||
initialUrl: _webViewHtml,
|
||||
@ -130,6 +88,7 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
JavascriptChannel(
|
||||
name: _jsMessageChannelName,
|
||||
onMessageReceived: ((message) {
|
||||
Logger.d('[Camera Player] Message from page: $message');
|
||||
setState((){
|
||||
_aspectRatio = double.tryParse(message.message) ?? 1.33;
|
||||
});
|
||||
@ -145,28 +104,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
}
|
||||
|
||||
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(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
@ -175,7 +112,7 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
IconButton(
|
||||
icon: Icon(Icons.refresh),
|
||||
iconSize: 40,
|
||||
color: Colors.amberAccent,
|
||||
color: Theme.of(context).accentColor,
|
||||
onPressed: _isLoaded ? () {
|
||||
setState(() {
|
||||
_isLoaded = false;
|
||||
@ -183,14 +120,13 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
} : null,
|
||||
),
|
||||
Expanded(
|
||||
child: playControl,
|
||||
child: Container(),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.fullscreen),
|
||||
iconSize: 40,
|
||||
color: Colors.amberAccent,
|
||||
color: Theme.of(context).accentColor,
|
||||
onPressed: _isLoaded ? () {
|
||||
_videoPlayerController?.pause();
|
||||
eventBus.fire(ShowEntityPageEvent());
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
@ -239,8 +175,6 @@ class _CameraStreamViewState extends State<CameraStreamView> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_monitorTimer?.cancel();
|
||||
_videoPlayerController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
@ -204,20 +204,20 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
//_buildOnOffControl(entity),
|
||||
_buildTemperatureControls(entity),
|
||||
_buildTargetTemperatureControls(entity),
|
||||
_buildHumidityControls(entity),
|
||||
_buildOperationControl(entity),
|
||||
_buildFanControl(entity),
|
||||
_buildSwingControl(entity),
|
||||
_buildPresetModeControl(entity),
|
||||
_buildAuxHeatControl(entity)
|
||||
_buildTemperatureControls(entity, context),
|
||||
_buildTargetTemperatureControls(entity, context),
|
||||
_buildHumidityControls(entity, context),
|
||||
_buildOperationControl(entity, context),
|
||||
_buildFanControl(entity, context),
|
||||
_buildSwingControl(entity, context),
|
||||
_buildPresetModeControl(entity, context),
|
||||
_buildAuxHeatControl(entity, context)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPresetModeControl(ClimateEntity entity) {
|
||||
Widget _buildPresetModeControl(ClimateEntity entity, BuildContext context) {
|
||||
if (entity.supportPresetMode) {
|
||||
return ModeSelectorWidget(
|
||||
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 ) {
|
||||
return ModeSwitchWidget(
|
||||
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) {
|
||||
return ModeSelectorWidget(
|
||||
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) {
|
||||
return ModeSelectorWidget(
|
||||
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) {
|
||||
return ModeSelectorWidget(
|
||||
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)) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
Text("Target temperature", style: Theme.of(context).textTheme.body1),
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTemperature,
|
||||
fontColor: _temperaturePending ? Colors.red : Colors.black,
|
||||
active: _temperaturePending,
|
||||
onDec: () => _temperatureDown(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 = [];
|
||||
if ((entity.supportTargetTemperatureRange) && (entity.targetLow != null)) {
|
||||
controls.addAll(<Widget>[
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetLow,
|
||||
fontColor: _temperaturePending ? Colors.red : Colors.black,
|
||||
active: _temperaturePending,
|
||||
onDec: () => _targetLowDown(entity),
|
||||
onInc: () => _targetLowUp(entity),
|
||||
),
|
||||
@ -333,7 +331,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
controls.add(
|
||||
TemperatureControlWidget(
|
||||
value: _tmpTargetHigh,
|
||||
fontColor: _temperaturePending ? Colors.red : Colors.black,
|
||||
active: _temperaturePending,
|
||||
onDec: () => _targetHighDown(entity),
|
||||
onInc: () => _targetHighUp(entity),
|
||||
)
|
||||
@ -343,9 +341,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Target temperature range", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
Text("Target temperature range", style: Theme.of(context).textTheme.body1),
|
||||
Row(
|
||||
children: controls,
|
||||
)
|
||||
@ -356,13 +352,13 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildHumidityControls(ClimateEntity entity) {
|
||||
Widget _buildHumidityControls(ClimateEntity entity, BuildContext context) {
|
||||
List<Widget> result = [];
|
||||
if (entity.supportTargetHumidity) {
|
||||
result.addAll(<Widget>[
|
||||
Text(
|
||||
"$_tmpTargetHumidity%",
|
||||
style: TextStyle(fontSize: Sizes.largeFontSize),
|
||||
style: Theme.of(context).textTheme.display1,
|
||||
),
|
||||
Expanded(
|
||||
child: Slider(
|
||||
@ -387,9 +383,7 @@ class _ClimateControlWidgetState extends State<ClimateControlWidget> {
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Target humidity", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
child: Text("Target humidity", style: Theme.of(context).textTheme.body1),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
|
@ -33,23 +33,16 @@ class ClimateStateWidget extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text("$displayState",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)),
|
||||
style: Theme.of(context).textTheme.body2),
|
||||
Text(" $targetTemp",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
))
|
||||
style: Theme.of(context).textTheme.body1)
|
||||
],
|
||||
),
|
||||
entity.currentTemperature != null ?
|
||||
Text("Currently: ${entity.currentTemperature}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
color: Colors.black45)
|
||||
style: Theme.of(context).textTheme.subtitle
|
||||
) :
|
||||
Container(height: 0.0,)
|
||||
],
|
||||
|
@ -5,8 +5,6 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
final String caption;
|
||||
final List options;
|
||||
final String value;
|
||||
final double captionFontSize;
|
||||
final double valueFontSize;
|
||||
final onChange;
|
||||
final EdgeInsets padding;
|
||||
|
||||
@ -16,8 +14,6 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
@required this.options,
|
||||
this.value,
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.valueFontSize,
|
||||
this.padding: const EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, 0.0),
|
||||
}) : super(key: key);
|
||||
|
||||
@ -28,9 +24,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("$caption", style: TextStyle(
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
)),
|
||||
Text("$caption", style: Theme.of(context).textTheme.body1),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
@ -40,10 +34,7 @@ class ModeSelectorWidget extends StatelessWidget {
|
||||
value: value,
|
||||
iconSize: 30.0,
|
||||
isExpanded: true,
|
||||
style: TextStyle(
|
||||
fontSize: valueFontSize ?? Sizes.largeFontSize,
|
||||
color: Colors.black,
|
||||
),
|
||||
style: Theme.of(context).textTheme.title,
|
||||
hint: Text("Select ${caption.toLowerCase()}"),
|
||||
items: options.map((value) {
|
||||
return new DropdownMenuItem<String>(
|
||||
|
@ -4,7 +4,6 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
|
||||
final String caption;
|
||||
final onChange;
|
||||
final double captionFontSize;
|
||||
final bool value;
|
||||
final bool expanded;
|
||||
final EdgeInsets padding;
|
||||
@ -13,7 +12,6 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
Key key,
|
||||
@required this.caption,
|
||||
@required this.onChange,
|
||||
this.captionFontSize,
|
||||
this.value,
|
||||
this.expanded: true,
|
||||
this.padding: const EdgeInsets.only(left: Sizes.leftWidgetPadding, right: Sizes.rightWidgetPadding)
|
||||
@ -25,7 +23,7 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
padding: this.padding,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
_buildCaption(),
|
||||
_buildCaption(context),
|
||||
Switch(
|
||||
onChanged: (value) => onChange(value),
|
||||
value: value ?? false,
|
||||
@ -35,12 +33,10 @@ class ModeSwitchWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCaption() {
|
||||
Widget _buildCaption(BuildContext context) {
|
||||
Widget captionWidget = Text(
|
||||
"$caption",
|
||||
style: TextStyle(
|
||||
fontSize: captionFontSize ?? Sizes.stateFontSize
|
||||
),
|
||||
style: Theme.of(context).textTheme.body1,
|
||||
);
|
||||
if (expanded) {
|
||||
return Expanded(
|
||||
|
@ -2,8 +2,7 @@ part of '../../../main.dart';
|
||||
|
||||
class TemperatureControlWidget extends StatelessWidget {
|
||||
final double value;
|
||||
final double fontSize;
|
||||
final Color fontColor;
|
||||
final bool active;
|
||||
final onInc;
|
||||
final onDec;
|
||||
|
||||
@ -12,8 +11,9 @@ class TemperatureControlWidget extends StatelessWidget {
|
||||
@required this.value,
|
||||
@required this.onInc,
|
||||
@required this.onDec,
|
||||
this.fontSize,
|
||||
this.fontColor})
|
||||
//this.fontSize,
|
||||
this.active: false
|
||||
})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -23,10 +23,7 @@ class TemperatureControlWidget extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"$value",
|
||||
style: TextStyle(
|
||||
fontSize: fontSize ?? 24.0,
|
||||
color: fontColor ?? Colors.black
|
||||
),
|
||||
style: active ? Theme.of(context).textTheme.display2 : Theme.of(context).textTheme.display1,
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
|
@ -64,9 +64,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Position", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
child: Text("Position"),
|
||||
),
|
||||
Slider(
|
||||
value: _tmpPosition,
|
||||
@ -118,9 +116,7 @@ class _CoverControlWidgetState extends State<CoverControlWidget> {
|
||||
controls.insert(0, Padding(
|
||||
padding: EdgeInsets.fromLTRB(
|
||||
0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding),
|
||||
child: Text("Tilt position", style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize
|
||||
)),
|
||||
child: Text("Tilt position"),
|
||||
));
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
@ -9,10 +9,8 @@ class DateTimeStateWidget extends StatelessWidget {
|
||||
padding: EdgeInsets.fromLTRB(0.0, 0.0, Sizes.rightWidgetPadding, 0.0),
|
||||
child: GestureDetector(
|
||||
child: Text("${entity.formattedState}",
|
||||
textAlign: TextAlign.right,
|
||||
style: new TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
)),
|
||||
textAlign: TextAlign.right
|
||||
),
|
||||
onTap: () => _handleStateTap(context, entity),
|
||||
));
|
||||
}
|
||||
|
@ -15,21 +15,19 @@ class DefaultEntityContainer extends StatelessWidget {
|
||||
return MissedEntityWidget();
|
||||
}
|
||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.DIVIDER) {
|
||||
return Divider(
|
||||
color: Colors.black45,
|
||||
);
|
||||
return Divider();
|
||||
}
|
||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.SECTION) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Divider(
|
||||
color: Colors.black45,
|
||||
),
|
||||
Divider(),
|
||||
Text(
|
||||
"${entityModel.entityWrapper.entity.displayName}",
|
||||
style: TextStyle(color: Colors.blue),
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Theme.of(context).primaryColor
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
@ -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,
|
||||
child: buildIcon(
|
||||
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 TextOverflow textOverflow;
|
||||
final bool wordsWrap;
|
||||
final double fontSize;
|
||||
final TextAlign textAlign;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
final EntityWrapper entityWrapper = EntityModel.of(context).entityWrapper;
|
||||
TextStyle textStyle = TextStyle(fontSize: fontSize);
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||
textStyle = textStyle.apply(color: Colors.blue, decoration: TextDecoration.underline);
|
||||
TextStyle tStyle;
|
||||
if (textStyle == null) {
|
||||
if (entityWrapper.entity.statelessType == StatelessEntityType.WEBLINK) {
|
||||
tStyle = HAClientTheme().getLinkTextStyle(context);
|
||||
} else {
|
||||
tStyle = Theme.of(context).textTheme.body1;
|
||||
}
|
||||
} else {
|
||||
tStyle = textStyle;
|
||||
}
|
||||
return Padding(
|
||||
padding: padding,
|
||||
@ -25,7 +31,7 @@ class EntityName extends StatelessWidget {
|
||||
overflow: textOverflow,
|
||||
softWrap: wordsWrap,
|
||||
maxLines: maxLines,
|
||||
style: textStyle,
|
||||
style: tStyle,
|
||||
textAlign: textAlign,
|
||||
),
|
||||
);
|
||||
|
@ -16,7 +16,7 @@ class EntityPageLayout extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
showClose ?
|
||||
Container(
|
||||
color: Colors.blue[300],
|
||||
color: Theme.of(context).primaryColor,
|
||||
height: 40,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
@ -25,18 +25,14 @@ class EntityPageLayout extends StatelessWidget {
|
||||
padding: EdgeInsets.only(left: 8),
|
||||
child: Text(
|
||||
entity.displayName,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
fontSize: 22
|
||||
),
|
||||
style: Theme.of(context).primaryTextTheme.headline
|
||||
),
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
padding: EdgeInsets.all(0),
|
||||
icon: Icon(Icons.close),
|
||||
color: Colors.white,
|
||||
color: Theme.of(context).primaryTextTheme.headline.color,
|
||||
iconSize: 36.0,
|
||||
onPressed: () {
|
||||
eventBus.fire(ShowEntityPageEvent());
|
||||
|
@ -22,7 +22,7 @@ class EntityPicture extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget buildIcon(EntityWrapper data) {
|
||||
Widget buildIcon(EntityWrapper data, BuildContext context) {
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
@ -39,7 +39,7 @@ class EntityPicture extends StatelessWidget {
|
||||
child: Icon(
|
||||
IconData(iconCode, fontFamily: 'Material Design Icons'),
|
||||
size: Sizes.largeIconSize,
|
||||
color: EntityColor.defaultStateColor,
|
||||
color: HAClientTheme().getOffStateColor(context),
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -63,7 +63,8 @@ class EntityPicture extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: buildIcon(
|
||||
entityWrapper
|
||||
entityWrapper,
|
||||
context
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ class FlatServiceButton extends StatelessWidget {
|
||||
final String serviceName;
|
||||
final String entityId;
|
||||
final String text;
|
||||
final double fontSize;
|
||||
|
||||
FlatServiceButton({
|
||||
Key key,
|
||||
@ -14,7 +13,6 @@ class FlatServiceButton extends StatelessWidget {
|
||||
@required this.serviceName,
|
||||
@required this.entityId,
|
||||
@required this.text,
|
||||
this.fontSize: Sizes.stateFontSize
|
||||
}) : super(key: key);
|
||||
|
||||
void _setNewState() {
|
||||
@ -24,7 +22,7 @@ class FlatServiceButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: fontSize*2.5,
|
||||
height: Theme.of(context).textTheme.subhead.fontSize*2.5,
|
||||
child: FlatButton(
|
||||
onPressed: (() {
|
||||
_setNewState();
|
||||
@ -32,8 +30,7 @@ class FlatServiceButton extends StatelessWidget {
|
||||
child: Text(
|
||||
text,
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: fontSize, color: Colors.blue),
|
||||
style: HAClientTheme().getActionTextStyle(context),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
@ -183,7 +183,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
}
|
||||
return UniversalSlider(
|
||||
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,
|
||||
onChangeEnd: (value) => _setColorTemp(entity, value),
|
||||
max: entity.maxMireds,
|
||||
@ -194,7 +194,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
_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 {
|
||||
return Container(width: 0.0, height: 0.0);
|
||||
@ -224,7 +224,7 @@ class _LightControlsWidgetState extends State<LightControlsWidget> {
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
color: savedColor?.toColor() ?? Colors.transparent,
|
||||
color: savedColor?.toColor() ?? Theme.of(context).backgroundColor,
|
||||
child: Text('Paste color'),
|
||||
onPressed: savedColor == null ? null : () {
|
||||
_setColor(entity, savedColor);
|
||||
|
@ -28,8 +28,7 @@ class LockStateWidget extends StatelessWidget {
|
||||
onPressed: () => _unlock(entity),
|
||||
child: Text("UNLOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
style: HAClientTheme().getActionTextStyle(context)
|
||||
),
|
||||
)
|
||||
),
|
||||
@ -39,8 +38,7 @@ class LockStateWidget extends StatelessWidget {
|
||||
onPressed: () => _lock(entity),
|
||||
child: Text("LOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
style: HAClientTheme().getActionTextStyle(context),
|
||||
),
|
||||
)
|
||||
)
|
||||
@ -56,8 +54,7 @@ class LockStateWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
entity.isLocked ? "UNLOCK" : "LOCK",
|
||||
textAlign: TextAlign.right,
|
||||
style:
|
||||
new TextStyle(fontSize: Sizes.stateFontSize, color: Colors.blue),
|
||||
style: HAClientTheme().getActionTextStyle(context),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
@ -33,7 +33,7 @@ class _MediaPlayerProgressBarState extends State<MediaPlayerProgressBar> {
|
||||
return LinearProgressIndicator(
|
||||
value: progress,
|
||||
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;
|
||||
int _savedPosition = 0;
|
||||
|
||||
final TextStyle _seekTextStyle = TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold
|
||||
);
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
@ -53,8 +47,7 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
||||
buttons.add(
|
||||
RaisedButton(
|
||||
child: Text("Jump to ${Duration(seconds: _savedPosition).toString().split('.')[0]}"),
|
||||
color: Colors.orange,
|
||||
focusColor: Colors.white,
|
||||
color: Theme.of(context).accentColor,
|
||||
onPressed: () {
|
||||
ConnectionManager().callService(
|
||||
domain: "media_player",
|
||||
@ -79,7 +72,13 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
||||
children: <Widget>[
|
||||
Text("00:00"),
|
||||
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]}")
|
||||
],
|
||||
@ -87,8 +86,7 @@ class _MediaPlayerSeekBarState extends State<MediaPlayerSeekBar> {
|
||||
Container(height: 10,),
|
||||
Slider(
|
||||
min: 0,
|
||||
activeColor: Colors.amber,
|
||||
inactiveColor: Colors.black26,
|
||||
activeColor: Theme.of(context).accentColor,
|
||||
max: entity.durationSeconds.toDouble(),
|
||||
value: _currentPosition,
|
||||
onChangeStart: (val) {
|
||||
|
@ -12,14 +12,14 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
Stack(
|
||||
alignment: AlignmentDirectional.topEnd,
|
||||
children: <Widget>[
|
||||
_buildImage(entity),
|
||||
_buildImage(entity, context),
|
||||
Positioned(
|
||||
bottom: 0.0,
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
child: Container(
|
||||
color: Colors.black45,
|
||||
child: _buildState(entity),
|
||||
child: _buildState(entity, context),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
@ -35,12 +35,9 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildState(MediaPlayerEntity entity) {
|
||||
TextStyle style = TextStyle(
|
||||
fontSize: 14.0,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.normal,
|
||||
height: 1.2
|
||||
Widget _buildState(MediaPlayerEntity entity, BuildContext context) {
|
||||
TextStyle style = Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.white
|
||||
);
|
||||
List<Widget> states = [];
|
||||
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;
|
||||
if (entity.entityPicture != null && state != EntityState.off && state != EntityState.unavailable && state != EntityState.idle) {
|
||||
return Container(
|
||||
@ -97,7 +94,7 @@ class MediaPlayerWidget extends StatelessWidget {
|
||||
Icon(
|
||||
MaterialDesignIcons.getIconDataFromIconName("mdi:movie"),
|
||||
size: 150.0,
|
||||
color: EntityColor.stateColor("$state"),
|
||||
color: HAClientTheme().getColorByEntityState("$state", context),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
@ -7,10 +7,10 @@ class SimpleEntityState extends StatelessWidget {
|
||||
final EdgeInsetsGeometry padding;
|
||||
final int maxLines;
|
||||
final String customValue;
|
||||
final double fontSize;
|
||||
final bool bold;
|
||||
final TextStyle textStyle;
|
||||
//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
|
||||
Widget build(BuildContext context) {
|
||||
@ -22,16 +22,19 @@ class SimpleEntityState extends StatelessWidget {
|
||||
} else {
|
||||
state = customValue;
|
||||
}
|
||||
TextStyle textStyle = TextStyle(
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: FontWeight.normal
|
||||
);
|
||||
if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
||||
textStyle = textStyle.apply(color: Colors.blue);
|
||||
TextStyle tStyle;
|
||||
if (textStyle != null) {
|
||||
tStyle = textStyle;
|
||||
} else if (entityModel.entityWrapper.entity.statelessType == StatelessEntityType.CALL_SERVICE) {
|
||||
tStyle = Theme.of(context).textTheme.subhead.copyWith(
|
||||
color: Colors.blue
|
||||
);
|
||||
} else {
|
||||
tStyle = Theme.of(context).textTheme.body1;
|
||||
}
|
||||
if (this.bold) {
|
||||
/*if (this.bold) {
|
||||
textStyle = textStyle.apply(fontWeightDelta: 100);
|
||||
}
|
||||
}*/
|
||||
while (state.contains(" ")){
|
||||
state = state.replaceAll(" ", " ");
|
||||
}
|
||||
@ -43,7 +46,7 @@ class SimpleEntityState extends StatelessWidget {
|
||||
maxLines: maxLines,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: true,
|
||||
style: textStyle
|
||||
style: tStyle
|
||||
)
|
||||
);
|
||||
if (expanded) {
|
||||
|
@ -62,8 +62,7 @@ class _SliderControlsWidgetState extends State<SliderControlsWidget> {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"$_newValue",
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.largeFontSize,
|
||||
style: Theme.of(context).textTheme.display1.copyWith(
|
||||
color: Colors.blue
|
||||
),
|
||||
),
|
||||
|
@ -40,10 +40,7 @@ class UniversalSlider extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Text(
|
||||
"$title",
|
||||
style: TextStyle(fontSize: Sizes.stateFontSize),
|
||||
),
|
||||
Text("$title"),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -10,7 +10,7 @@ class VacuumControls extends StatelessWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_buildStatusAndBattery(entity),
|
||||
_buildStatusAndBattery(entity, context),
|
||||
_buildCommands(entity),
|
||||
_buildFanSpeed(entity),
|
||||
_buildAdditionalInfo(entity)
|
||||
@ -19,12 +19,12 @@ class VacuumControls extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusAndBattery(VacuumEntity entity) {
|
||||
Widget _buildStatusAndBattery(VacuumEntity entity, BuildContext context) {
|
||||
List<Widget> result = [];
|
||||
if (entity.supportStatus) {
|
||||
result.addAll(
|
||||
<Widget>[
|
||||
Text("Status:", style: TextStyle(fontSize: Sizes.stateFontSize),),
|
||||
Text("Status:"),
|
||||
Container(width: 6,),
|
||||
Expanded(
|
||||
//flex: 1,
|
||||
@ -33,10 +33,7 @@ class VacuumControls extends StatelessWidget {
|
||||
maxLines: 1,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.stateFontSize,
|
||||
fontWeight: FontWeight.bold
|
||||
),
|
||||
style: Theme.of(context).textTheme.body2,
|
||||
),
|
||||
),
|
||||
]
|
||||
@ -48,7 +45,7 @@ class VacuumControls extends StatelessWidget {
|
||||
result.addAll(<Widget>[
|
||||
Icon(MaterialDesignIcons.getIconDataFromIconName(iconName)),
|
||||
Container(width: 6,),
|
||||
Text("$batteryLevel %", style: TextStyle(fontSize: Sizes.stateFontSize))
|
||||
Text("$batteryLevel %")
|
||||
]
|
||||
);
|
||||
}
|
||||
@ -172,7 +169,7 @@ class VacuumControls extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Vacuum cleaner commands:", style: TextStyle(fontSize: Sizes.stateFontSize)),
|
||||
Text("Vacuum cleaner commands:"),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
|
@ -27,10 +27,7 @@ class VacuumStateButton extends StatelessWidget {
|
||||
text: "RETURN TO DOCK"
|
||||
);
|
||||
} else {
|
||||
result = Text(entity.state.toUpperCase(), style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Colors.grey
|
||||
));
|
||||
result = Text(entity.state.toUpperCase(), style: Theme.of(context).textTheme.subhead);
|
||||
}
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(right: 15),
|
||||
|
@ -331,7 +331,7 @@ class HomeAssistant {
|
||||
void _createUI() {
|
||||
Logger.d("Creating Lovelace UI");
|
||||
ui = HomeAssistantUI(rawLovelaceConfig: _rawLovelaceData);
|
||||
if (isServiceExist('zha_map')) {
|
||||
/*if (isServiceExist('zha_map')) {
|
||||
panels.add(
|
||||
Panel(
|
||||
id: 'haclient_zha',
|
||||
@ -341,7 +341,7 @@ class HomeAssistant {
|
||||
icon: 'mdi:zigbee'
|
||||
)
|
||||
);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.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:flutter/widgets.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:flutter/services.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:flutter_webview_plugin/flutter_webview_plugin.dart' as standaloneWebview;
|
||||
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 '.secrets.dart';
|
||||
|
||||
part 'const.dart';
|
||||
part 'utils/launcher.dart';
|
||||
@ -76,7 +75,6 @@ part 'entities/universal_slider.widget.dart';
|
||||
part 'entities/flat_service_button.widget.dart';
|
||||
part 'entities/light/widgets/light_color_picker.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/simple_state_history_chart.dart';
|
||||
part 'plugins/history_chart/numeric_state_history_chart.dart';
|
||||
@ -124,6 +122,7 @@ part 'managers/mobile_app_integration_manager.class.dart';
|
||||
part 'managers/connection_manager.class.dart';
|
||||
part 'managers/device_info_manager.class.dart';
|
||||
part 'managers/startup_user_messages_manager.class.dart';
|
||||
part 'managers/theme_manager.dart';
|
||||
part 'ui.dart';
|
||||
part 'view.class.dart';
|
||||
part 'cards/card.class.dart';
|
||||
@ -148,7 +147,7 @@ EventBus eventBus = new EventBus();
|
||||
final FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
|
||||
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||
const String appName = "HA Client";
|
||||
const appVersionNumber = "0.8.0";
|
||||
const appVersionNumber = "0.8.2";
|
||||
const appVersionAdd = "";
|
||||
const appVersion = "$appVersionNumber$appVersionAdd";
|
||||
|
||||
@ -164,6 +163,7 @@ Future<void> _reportError(dynamic error, dynamic stackTrace) async {
|
||||
|
||||
void main() async {
|
||||
Crashlytics.instance.enableInDevMode = false;
|
||||
SyncfusionLicense.registerLicense(secrets['syncfusion_license_key']);
|
||||
|
||||
FlutterError.onError = (FlutterErrorDetails details) {
|
||||
Logger.e(" Caut Flutter runtime error: ${details.exception}");
|
||||
@ -226,9 +226,9 @@ class _HAClientAppState extends State<HAClientApp> {
|
||||
Widget build(BuildContext context) {
|
||||
return new MaterialApp(
|
||||
title: appName,
|
||||
theme: new ThemeData(
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
theme: HAClientTheme().lightTheme,
|
||||
darkTheme: HAClientTheme().darkTheme,
|
||||
debugShowCheckedModeBanner: false,
|
||||
initialRoute: "/",
|
||||
routes: {
|
||||
"/": (context) => MainPage(title: 'HA Client'),
|
||||
@ -262,7 +262,9 @@ class _HAClientAppState extends State<HAClientApp> {
|
||||
title: new Text("Login with HA"),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Manual", style: TextStyle(color: Colors.white)),
|
||||
child: Text("Manual", style: Theme.of(context).textTheme.button.copyWith(
|
||||
decoration: TextDecoration.underline
|
||||
)),
|
||||
onPressed: () {
|
||||
eventBus.fire(ShowPageEvent(path: "/connection-settings", goBackFirst: true));
|
||||
},
|
||||
|
@ -223,16 +223,6 @@ class ConnectionManager {
|
||||
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 {
|
||||
Logger.d("[Received unhandled] <== ${data.toString()}");
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ class StartupUserMessagesManager {
|
||||
bool _supportAppDevelopmentMessageShown;
|
||||
bool _whatsNewMessageShown;
|
||||
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-887";
|
||||
|
||||
void checkMessagesToShow() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
|
223
lib/managers/theme_manager.dart
Normal file
223
lib/managers/theme_manager.dart
Normal file
@ -0,0 +1,223 @@
|
||||
part of '../main.dart';
|
||||
|
||||
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();
|
||||
|
||||
final ThemeData lightTheme = 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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -115,15 +115,14 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
||||
scrollDirection: Axis.vertical,
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
children: <Widget>[
|
||||
Text("Location tracking", style: TextStyle(fontSize: Sizes.largeFontSize-2)),
|
||||
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: TextStyle(
|
||||
style: Theme.of(context).textTheme.subhead.copyWith(
|
||||
color: Colors.blue,
|
||||
fontSize: 16,
|
||||
decoration: TextDecoration.underline
|
||||
)
|
||||
),
|
||||
@ -153,21 +152,24 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
||||
//Expanded(child: Container(),),
|
||||
FlatButton(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
child: Text("-", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
||||
child: Text("-", style: Theme.of(context).textTheme.title),
|
||||
onPressed: () => decLocationInterval(),
|
||||
),
|
||||
Text("$_locationInterval", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
||||
Text("$_locationInterval", style: Theme.of(context).textTheme.title),
|
||||
FlatButton(
|
||||
padding: EdgeInsets.all(0.0),
|
||||
child: Text("+", style: TextStyle(fontSize: Sizes.largeFontSize)),
|
||||
child: Text("+", style: Theme.of(context).textTheme.title),
|
||||
onPressed: () => incLocationInterval(),
|
||||
),
|
||||
],
|
||||
),
|
||||
Divider(),
|
||||
Text("Integration status", style: TextStyle(fontSize: Sizes.largeFontSize-2)),
|
||||
Text("Integration status", style: Theme.of(context).textTheme.title),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Text("${HomeAssistant().userName}'s ${DeviceInfoManager().model}, ${DeviceInfoManager().osName} ${DeviceInfoManager().osVersion}"),
|
||||
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(),
|
||||
@ -177,13 +179,13 @@ class _IntegrationSettingsPageState extends State<IntegrationSettingsPage> {
|
||||
RaisedButton(
|
||||
color: Colors.blue,
|
||||
onPressed: () => updateRegistration(),
|
||||
child: Text("Check integration", style: TextStyle(color: Colors.white))
|
||||
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: TextStyle(color: Colors.white))
|
||||
child: Text("Reset integration", style: Theme.of(context).textTheme.button)
|
||||
)
|
||||
],
|
||||
),
|
||||
|
@ -347,11 +347,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
accountName: Text(HomeAssistant().userName),
|
||||
accountEmail: Text(HomeAssistant().locationName ?? ""),
|
||||
currentAccountPicture: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).backgroundColor,
|
||||
child: Text(
|
||||
HomeAssistant().userAvatarText,
|
||||
style: TextStyle(
|
||||
fontSize: 32.0
|
||||
),
|
||||
style: Theme.of(context).textTheme.display1
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -360,21 +359,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
HomeAssistant().panels.forEach((Panel panel) {
|
||||
if (!panel.isHidden) {
|
||||
menuItems.add(
|
||||
new ListTile(
|
||||
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);
|
||||
}
|
||||
)
|
||||
panel.getMenuItemWidget(context)
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -435,11 +420,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
},
|
||||
),
|
||||
new ListTile(
|
||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:forum")),
|
||||
leading: Icon(MaterialDesignIcons.getIconDataFromIconName("mdi:discord")),
|
||||
title: Text("Contacts/Discussion"),
|
||||
onTap: () {
|
||||
Navigator.of(context).pop();
|
||||
Launcher.launchURL("https://spectrum.chat/ha-client");
|
||||
Launcher.launchURL("https://discord.gg/nd6FZQ");
|
||||
},
|
||||
),
|
||||
new ListTile(
|
||||
@ -458,9 +443,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
},
|
||||
child: Text(
|
||||
"ha-client.app",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -474,9 +459,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
},
|
||||
child: Text(
|
||||
"Terms and Conditions",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -490,9 +475,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
},
|
||||
child: Text(
|
||||
"Privacy Policy",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -519,13 +504,13 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
bool _showBottomBar = false;
|
||||
String _bottomBarText;
|
||||
bool _bottomBarProgress;
|
||||
Color _bottomBarColor;
|
||||
bool _bottomBarErrorColor;
|
||||
Timer _bottomBarTimer;
|
||||
|
||||
void _showInfoBottomBar({String message, bool progress: false, Duration duration}) {
|
||||
_bottomBarTimer?.cancel();
|
||||
_bottomBarAction = Container(height: 0.0, width: 0.0,);
|
||||
_bottomBarColor = Colors.grey.shade50;
|
||||
_bottomBarErrorColor = false;
|
||||
setState(() {
|
||||
_bottomBarText = message;
|
||||
_bottomBarProgress = progress;
|
||||
@ -539,11 +524,10 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
}
|
||||
|
||||
void _showErrorBottomBar(HAError error) {
|
||||
TextStyle textStyle = TextStyle(
|
||||
color: Colors.blue,
|
||||
fontSize: Sizes.nameFontSize
|
||||
TextStyle textStyle = Theme.of(context).textTheme.button.copyWith(
|
||||
decoration: TextDecoration.underline
|
||||
);
|
||||
_bottomBarColor = Colors.red.shade100;
|
||||
_bottomBarErrorColor = true;
|
||||
List<Widget> actions = [];
|
||||
error.actions.forEach((HAErrorAction action) {
|
||||
switch (action.type) {
|
||||
@ -649,9 +633,9 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
activePlayers.map((entity) => PopupMenuItem<String>(
|
||||
child: Text(
|
||||
"${entity.displayName}",
|
||||
style: TextStyle(
|
||||
color: EntityColor.stateColor(entity.state)
|
||||
),
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: HAClientTheme().getColorByEntityState(entity.state, context)
|
||||
)
|
||||
),
|
||||
value: "${entity.entityId}",
|
||||
)).toList()
|
||||
@ -680,7 +664,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: Text("$playersCount", style: TextStyle(fontSize: 12)),
|
||||
child: Text(
|
||||
"$playersCount",
|
||||
style: Theme.of(context).textTheme.caption.copyWith(
|
||||
color: Colors.white
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -698,7 +687,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
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,
|
||||
onPressed: () => _fullLoad(),
|
||||
)
|
||||
@ -833,16 +822,16 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
|
||||
bottomBarChildren.add(
|
||||
CollectionScaleTransition(
|
||||
children: <Widget>[
|
||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.on),),
|
||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.unavailable),),
|
||||
Icon(Icons.stop, size: 10.0, color: EntityColor.stateColor(EntityState.off),),
|
||||
Icon(Icons.stop, size: 10.0, color: HAClientTheme().getOnStateColor(context),),
|
||||
Icon(Icons.stop, size: 10.0, color: HAClientTheme().getDisabledStateColor(context),),
|
||||
Icon(Icons.stop, size: 10.0, color: HAClientTheme().getOffStateColor(context),),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
if (bottomBarChildren.isNotEmpty) {
|
||||
bottomBar = Container(
|
||||
color: _bottomBarColor,
|
||||
color: _bottomBarErrorColor ? Theme.of(context).errorColor : Theme.of(context).primaryColorLight,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
|
@ -135,7 +135,9 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
|
||||
if (_validationMessage.isNotEmpty) {
|
||||
children.add(Text(
|
||||
"$_validationMessage",
|
||||
style: TextStyle(color: Colors.red)
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Theme.of(context).errorColor
|
||||
)
|
||||
));
|
||||
}
|
||||
children.addAll(<Widget>[
|
||||
@ -193,9 +195,9 @@ class _PlayMediaPageState extends State<PlayMediaPage> {
|
||||
},
|
||||
child: Text(
|
||||
"How?",
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.blue,
|
||||
decoration: TextDecoration.underline
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -138,10 +138,7 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
children: <Widget>[
|
||||
Text(
|
||||
"Connection settings",
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 20.0
|
||||
),
|
||||
style: Theme.of(context).textTheme.headline,
|
||||
),
|
||||
new Row(
|
||||
children: [
|
||||
@ -176,16 +173,13 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
),
|
||||
new Text(
|
||||
"Try ports 80 and 443 if default is not working and you don't know why.",
|
||||
style: TextStyle(color: Colors.grey),
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 20.0),
|
||||
child: Text(
|
||||
"UI",
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 20.0
|
||||
),
|
||||
style: Theme.of(context).textTheme.headline,
|
||||
),
|
||||
),
|
||||
new Row(
|
||||
@ -203,15 +197,14 @@ class _ConnectionSettingsPageState extends State<ConnectionSettingsPage> {
|
||||
),
|
||||
Text(
|
||||
"Authentication settings",
|
||||
style: TextStyle(
|
||||
color: Colors.black45,
|
||||
fontSize: 20.0
|
||||
),
|
||||
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: TextStyle(color: Colors.redAccent),
|
||||
style: Theme.of(context).textTheme.body1.copyWith(
|
||||
color: Colors.redAccent
|
||||
),
|
||||
),
|
||||
new TextField(
|
||||
decoration: InputDecoration(
|
||||
|
@ -24,7 +24,7 @@ class _WhatsNewPageState extends State<WhatsNewPage> {
|
||||
error = "";
|
||||
});
|
||||
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.2.md");
|
||||
if (response.statusCode == 200) {
|
||||
setState(() {
|
||||
data = response.body;
|
||||
|
@ -10,8 +10,7 @@ class LastUpdatedWidget extends StatelessWidget {
|
||||
child: Text(
|
||||
'${entityModel.entityWrapper.entity.lastUpdated}',
|
||||
textAlign: TextAlign.left,
|
||||
style: TextStyle(
|
||||
fontSize: Sizes.smallFontSize, color: Colors.black26),
|
||||
style: Theme.of(context).textTheme.caption
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ class PageLoadingError extends StatelessWidget {
|
||||
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),
|
||||
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>[
|
||||
Text(
|
||||
"${product.title}",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0
|
||||
),
|
||||
style: Theme.of(context).textTheme.body2,
|
||||
),
|
||||
Container(height: Sizes.rowPadding,),
|
||||
Text(
|
||||
@ -53,7 +50,9 @@ class ProductPurchase extends StatelessWidget {
|
||||
softWrap: true,
|
||||
),
|
||||
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(
|
||||
flex: 2,
|
||||
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,
|
||||
onPressed: this.purchased ? null : () => this.onBuy(this.product),
|
||||
),
|
||||
|
@ -56,7 +56,7 @@ class Panel {
|
||||
children: <Widget>[
|
||||
Text("${this.title}"),
|
||||
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: () {
|
||||
|
@ -16,7 +16,7 @@ class LinkToWebConfig extends StatelessWidget {
|
||||
title: Text("${this.name}",
|
||||
textAlign: TextAlign.left,
|
||||
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"),
|
||||
onTap: () {
|
||||
Launcher.launchAuthenticatedWebView(context: context, url: this.url, title: this.name);
|
||||
|
@ -156,7 +156,8 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
result.add(
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: "value",
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor("_", historyMoment.colorId),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||
HAClientTheme().chartHistoryStateColor("_", historyMoment.colorId, context),
|
||||
radiusPxFn: (EntityHistoryMoment historyMoment, __) {
|
||||
if (historyMoment.hiddenDot) {
|
||||
return 0.0;
|
||||
@ -179,7 +180,8 @@ class _CombinedHistoryChartWidgetState extends State<CombinedHistoryChartWidget>
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'state',
|
||||
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,
|
||||
domainLowerBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
domainUpperBoundFn: (EntityHistoryMoment historyMoment, _) => historyMoment.endTime ?? DateTime.now(),
|
||||
|
@ -28,7 +28,7 @@ class HistoryControlWidget extends StatelessWidget {
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: _buildStates(),
|
||||
child: _buildStates(context),
|
||||
),
|
||||
),
|
||||
_buildTime(),
|
||||
@ -46,18 +46,16 @@ class HistoryControlWidget extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildStates() {
|
||||
Widget _buildStates(BuildContext context) {
|
||||
List<Widget> children = [];
|
||||
for (int i = 0; i < selectedStates.length; i++) {
|
||||
children.add(
|
||||
Text(
|
||||
"${selectedStates[i] ?? '-'}",
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: EntityColor.historyStateColor(selectedStates[i], colorIndexes[i]),
|
||||
fontSize: 22.0
|
||||
),
|
||||
style: Theme.of(context).textTheme.title.copyWith(
|
||||
color: HAClientTheme().historyStateColor(selectedStates[i], colorIndexes[i], context)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -108,7 +108,8 @@ class _NumericStateHistoryChartWidgetState extends State<NumericStateHistoryChar
|
||||
return [
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) => EntityColor.chartHistoryStateColor(EntityState.on, -1),
|
||||
colorFn: (EntityHistoryMoment historyMoment, __) =>
|
||||
HAClientTheme().chartHistoryStateColor(EntityState.on, -1, context),
|
||||
domainFn: (EntityHistoryMoment historyMoment, _) => historyMoment.startTime,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => historyMoment.value ?? historyMoment.previousValue,
|
||||
data: data,
|
||||
|
@ -107,7 +107,8 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
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,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
@ -115,7 +116,8 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
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,
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
@ -123,7 +125,8 @@ class _SimpleStateHistoryChartWidgetState extends State<SimpleStateHistoryChartW
|
||||
new charts.Series<EntityHistoryMoment, DateTime>(
|
||||
id: 'State',
|
||||
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(),
|
||||
measureFn: (EntityHistoryMoment historyMoment, _) => 10,
|
||||
data: data,
|
||||
|
@ -1,7 +1,7 @@
|
||||
name: hass_client
|
||||
description: Home Assistant Android Client
|
||||
|
||||
version: 0.8.0+886
|
||||
version: 0.8.2+887
|
||||
|
||||
|
||||
environment:
|
||||
@ -32,7 +32,8 @@ dependencies:
|
||||
workmanager: ^0.2.2
|
||||
battery: ^0.3.1+7
|
||||
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:
|
||||
@ -53,6 +54,7 @@ flutter:
|
||||
- images/hassio-192x192.png
|
||||
- assets/js/externalAuth.js
|
||||
- assets/html/cameraView.html
|
||||
- assets/html/cameraLiveView.html
|
||||
|
||||
fonts:
|
||||
- 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