Compare commits

..

23 Commits

Author SHA1 Message Date
89513ca4e5 Secrets config for CI/CD 2020-04-09 17:10:21 +00:00
a934ee2335 Hide zha panel 2020-04-09 16:25:35 +00:00
49aeea634f 0.8.2 2020-04-09 16:24:37 +00:00
e18b9ebe14 Replace VideoPlayer with web player 2020-04-08 16:48:13 +00:00
08ee3f3d80 Fix inactive gauge color 2020-04-07 20:30:39 +00:00
62d07bf8b9 Gauge card refactoring 2020-04-07 20:20:57 +00:00
ab398cbdc3 Remove debugg banner 2020-04-06 21:43:38 +00:00
007d12719c WIP #102 Moving all colors to theme 2020-04-06 21:39:16 +00:00
524d195800 WIP #102 Colors from theme 2020-04-06 20:03:41 +00:00
405de64249 Fix double events handling 2020-04-04 22:34:54 +00:00
f53554702e WIP Themes: new dark theme 2020-04-04 22:08:50 +00:00
379e1a4a7e WIP Themes: State colors from themes 2020-04-04 21:39:12 +00:00
d6f7096055 WIP Themes: New light theme 2020-04-04 20:54:32 +00:00
37c721e4f6 WIP Themes: Entity page heade color 2020-04-04 16:19:25 +00:00
d94235ef6d WIP Themes: Dark theme fonts 2020-04-04 16:13:12 +00:00
eb4184713f WIP Themes: badges colors 2020-04-04 15:44:06 +00:00
a0a0cb4612 WIP Themes: Make all fonts depend on theme 2020-04-04 15:13:55 +00:00
f448a20784 WIP Themes: primary colors fix 2020-04-04 13:36:32 +00:00
36eff26862 WIP themes: fix sudhead style 2020-04-04 13:27:23 +00:00
5b2a1163b9 Merge pull request #529 from estevez-dev/hotfix/0.8.1
Fix for older HA versions
2020-04-04 16:22:24 +03:00
4432124e8c WIP Themes: font size standartization 2020-04-04 12:47:40 +00:00
b8ba3c59e9 WIP Themes: climate controls and entity name 2020-04-04 12:00:15 +00:00
c40a496b6b Replace Spectrum with Discord 2020-04-04 10:12:21 +00:00
60 changed files with 689 additions and 639 deletions

2
.gitignore vendored
View File

@ -18,5 +18,5 @@ flutter_export_environment.sh
.flutter-plugins-dependencies
key.properties
premium_features_manager.class.dart
.secrets.dart
pubspec.lock

View File

@ -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: |

View File

@ -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)
[![Gitpod Ready-to-Code](https://img.shields.io/badge/Gitpod-Ready--to--Code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/estevez-dev/ha_client)

View 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>

View File

@ -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,

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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,
);
}

View File

@ -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,
),
);
}
),
)
]
)
),
);
}
}

View File

@ -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;

View File

@ -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),
)

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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,

View File

@ -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,)
],

View File

@ -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>(

View File

@ -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(

View File

@ -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>[

View File

@ -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,

View File

@ -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),
));
}

View File

@ -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
),
)
],
);

View File

@ -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];
}
}
}
}

View File

@ -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)
),
);
}

View File

@ -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,
),
);

View File

@ -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());

View File

@ -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
),
);
}

View File

@ -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),
),
)
);

View File

@ -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);

View File

@ -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),
),
)
);

View File

@ -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)),
);
}

View File

@ -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) {

View File

@ -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),
)
],
);

View File

@ -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) {

View File

@ -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
),
),

View File

@ -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,

View File

@ -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,

View File

@ -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),

View File

@ -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'
)
);
}
}*/
}
}

View File

@ -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));
},

View File

@ -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()}");
}

View File

@ -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();

View 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);
}
}
}
}

View File

@ -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)
)
],
),

View File

@ -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>[

View File

@ -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
),
),
),

View File

@ -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(

View File

@ -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;

View File

@ -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
),
);
}

View File

@ -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)
],
)
],

View File

@ -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)
],
)
],

View File

@ -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),
),

View File

@ -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: () {

View File

@ -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);

View File

@ -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(),

View File

@ -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)
)
)
);
}

View File

@ -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,

View File

@ -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,

View File

@ -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
View 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)};');
}