diff --git a/lib/entity_class/media_player_entity.class.dart b/lib/entity_class/media_player_entity.class.dart index c94af5d..6ff62cd 100644 --- a/lib/entity_class/media_player_entity.class.dart +++ b/lib/entity_class/media_player_entity.class.dart @@ -72,4 +72,9 @@ class MediaPlayerEntity extends Entity { MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE) == MediaPlayerEntity.SUPPORT_SELECT_SOUND_MODE); + @override + Widget _buildAdditionalControlsForPage(BuildContext context) { + return MediaPlayerControls(); + } + } \ No newline at end of file diff --git a/lib/entity_widgets/badge.dart b/lib/entity_widgets/common/badge.dart similarity index 99% rename from lib/entity_widgets/badge.dart rename to lib/entity_widgets/common/badge.dart index 21b23fb..203b292 100644 --- a/lib/entity_widgets/badge.dart +++ b/lib/entity_widgets/common/badge.dart @@ -1,4 +1,4 @@ -part of '../main.dart'; +part of '../../main.dart'; class BadgeWidget extends StatelessWidget { @override diff --git a/lib/entity_widgets/entity_attributes_list.dart b/lib/entity_widgets/common/entity_attributes_list.dart similarity index 98% rename from lib/entity_widgets/entity_attributes_list.dart rename to lib/entity_widgets/common/entity_attributes_list.dart index 52ebbd2..41b0831 100644 --- a/lib/entity_widgets/entity_attributes_list.dart +++ b/lib/entity_widgets/common/entity_attributes_list.dart @@ -1,4 +1,4 @@ -part of '../main.dart'; +part of '../../main.dart'; class EntityAttributesList extends StatelessWidget { EntityAttributesList({Key key}) : super(key: key); diff --git a/lib/entity_widgets/last_updated.dart b/lib/entity_widgets/common/last_updated.dart similarity index 94% rename from lib/entity_widgets/last_updated.dart rename to lib/entity_widgets/common/last_updated.dart index 5b55d47..f4957f7 100644 --- a/lib/entity_widgets/last_updated.dart +++ b/lib/entity_widgets/common/last_updated.dart @@ -1,4 +1,4 @@ -part of '../main.dart'; +part of '../../main.dart'; class LastUpdatedWidget extends StatelessWidget { @override diff --git a/lib/entity_widgets/mode_selector.dart b/lib/entity_widgets/common/mode_selector.dart similarity index 98% rename from lib/entity_widgets/mode_selector.dart rename to lib/entity_widgets/common/mode_selector.dart index 00feaef..1f6a8c8 100644 --- a/lib/entity_widgets/mode_selector.dart +++ b/lib/entity_widgets/common/mode_selector.dart @@ -1,4 +1,4 @@ -part of '../main.dart'; +part of '../../main.dart'; class ModeSelectorWidget extends StatelessWidget { diff --git a/lib/entity_widgets/mode_swicth.dart b/lib/entity_widgets/common/mode_swicth.dart similarity index 96% rename from lib/entity_widgets/mode_swicth.dart rename to lib/entity_widgets/common/mode_swicth.dart index b8ef25d..925c6ff 100644 --- a/lib/entity_widgets/mode_swicth.dart +++ b/lib/entity_widgets/common/mode_swicth.dart @@ -1,4 +1,4 @@ -part of '../main.dart'; +part of '../../main.dart'; class ModeSwitchWidget extends StatelessWidget { diff --git a/lib/entity_widgets/common/universal_slider.dart b/lib/entity_widgets/common/universal_slider.dart new file mode 100644 index 0000000..2c5ef30 --- /dev/null +++ b/lib/entity_widgets/common/universal_slider.dart @@ -0,0 +1,54 @@ +part of '../../main.dart'; + +class UniversalSlider extends StatelessWidget { + + final onChanged; + final onChangeEnd; + final Widget leading; + final Widget closing; + final String title; + final double min; + final double max; + final double value; + + const UniversalSlider({Key key, this.onChanged, this.onChangeEnd, this.leading, this.closing, this.title, this.min, this.max, this.value}) : super(key: key); + + @override + Widget build(BuildContext context) { + List row = []; + if (leading != null) { + row.add(leading); + } + row.add( + Flexible( + child: Slider( + value: value, + min: min, + max: max, + onChanged: (value) => onChanged(value), + onChangeEnd: (value) => onChangeEnd(value), + ), + ) + ); + if (closing != null) { + row.add(closing); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container(height: Sizes.rowPadding,), + Text( + "$title", + style: TextStyle(fontSize: Sizes.stateFontSize), + ), + Container(height: Sizes.rowPadding,), + Row( + mainAxisSize: MainAxisSize.min, + children: row, + ), + Container(height: Sizes.rowPadding,) + ], + ); + } + +} \ No newline at end of file diff --git a/lib/entity_widgets/controls/light_controls.dart b/lib/entity_widgets/controls/light_controls.dart index 0cde589..e8bec5b 100644 --- a/lib/entity_widgets/controls/light_controls.dart +++ b/lib/entity_widgets/controls/light_controls.dart @@ -99,36 +99,19 @@ class _LightControlsWidgetState extends State { Widget _buildBrightnessControl(LightEntity entity) { if ((entity.supportBrightness) && (_tmpBrightness != null) && (entity.state != EntityState.unavailable)) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container(height: Sizes.rowPadding,), - Text( - "Brightness", - style: TextStyle(fontSize: Sizes.stateFontSize), - ), - Container(height: Sizes.rowPadding,), - Row( - children: [ - Icon(Icons.brightness_5), - Expanded( - child: Slider( - value: _tmpBrightness.toDouble(), - min: 0.0, - max: 255.0, - onChanged: (value) { - setState(() { - _changedHere = true; - _tmpBrightness = value.round(); - }); - }, - onChangeEnd: (value) => _setBrightness(entity, value), - ), - ) - ], - ), - Container(height: Sizes.rowPadding,) - ], + return UniversalSlider( + onChanged: (value) { + setState(() { + _changedHere = true; + _tmpBrightness = value.round(); + }); + }, + min: 0.0, + max: 255.0, + onChangeEnd: (value) => _setBrightness(entity, value), + value: _tmpBrightness.toDouble(), + leading: Icon(Icons.brightness_5), + title: "Brightness", ); } else { return Container(width: 0.0, height: 0.0); @@ -137,37 +120,20 @@ class _LightControlsWidgetState extends State { Widget _buildColorTempControl(LightEntity entity) { if ((entity.supportColorTemp) && (_tmpColorTemp != null)) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container(height: Sizes.rowPadding,), - Text( - "Color temperature", - style: TextStyle(fontSize: Sizes.stateFontSize), - ), - Container(height: Sizes.rowPadding,), - Row( - children: [ - Text("Cold", style: TextStyle(color: Colors.lightBlue),), - Expanded( - child: Slider( - value: _tmpColorTemp.toDouble(), - min: entity.minMireds, - max: entity.maxMireds, - onChanged: (value) { - setState(() { - _changedHere = true; - _tmpColorTemp = value.round(); - }); - }, - onChangeEnd: (value) => _setColorTemp(entity, value), - ), - ), - Text("Warm", style: TextStyle(color: Colors.amberAccent),), - ], - ), - Container(height: Sizes.rowPadding,) - ], + return UniversalSlider( + title: "Color temperature", + leading: Text("Cold", style: TextStyle(color: Colors.lightBlue),), + value: _tmpColorTemp.toDouble(), + onChangeEnd: (value) => _setColorTemp(entity, value), + max: entity.maxMireds, + min: entity.minMireds, + onChanged: (value) { + setState(() { + _changedHere = true; + _tmpColorTemp = value.round(); + }); + }, + closing: Text("Warm", style: TextStyle(color: Colors.amberAccent),), ); } else { return Container(width: 0.0, height: 0.0); diff --git a/lib/entity_widgets/controls/media_player_widget.dart b/lib/entity_widgets/controls/media_player_widgets.dart similarity index 60% rename from lib/entity_widgets/controls/media_player_widget.dart rename to lib/entity_widgets/controls/media_player_widgets.dart index cc691bc..397a76b 100644 --- a/lib/entity_widgets/controls/media_player_widget.dart +++ b/lib/entity_widgets/controls/media_player_widgets.dart @@ -1,34 +1,12 @@ part of '../../main.dart'; class MediaPlayerWidget extends StatelessWidget { - - void _setPower(MediaPlayerEntity entity) { - if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) { - if (entity.state == EntityState.off) { - TheLogger.debug("${entity.entityId} turn_on"); - eventBus.fire(new ServiceCallEvent( - entity.domain, "turn_on", entity.entityId, - null)); - } else { - TheLogger.debug("${entity.entityId} turn_off"); - eventBus.fire(new ServiceCallEvent( - entity.domain, "turn_off", entity.entityId, - null)); - } - } - } - - void _callAction(MediaPlayerEntity entity, String action) { - TheLogger.debug("${entity.entityId} $action"); - eventBus.fire(new ServiceCallEvent( - entity.domain, "$action", entity.entityId, - null)); - } @override Widget build(BuildContext context) { final EntityModel entityModel = EntityModel.of(context); final MediaPlayerEntity entity = entityModel.entityWrapper.entity; + //TheLogger.debug("stop: ${entity.supportStop}, seek: ${entity.supportSeek}"); return Column( children: [ Stack( @@ -52,105 +30,11 @@ class MediaPlayerWidget extends StatelessWidget { ) ], ), - _buildControls(entity) + MediaPlayerPlaybackControls() ] ); } - Widget _buildControls(MediaPlayerEntity entity) { - List result = []; - if (entity.supportTurnOn || entity.supportTurnOff) { - result.add( - IconButton( - icon: Icon(Icons.power_settings_new), - onPressed: () => _setPower(entity), - iconSize: Sizes.iconSize, - ) - ); - } else { - result.add( - Container( - width: Sizes.iconSize, - ) - ); - } - List centeredControlsChildren = []; - if (entity.supportPreviousTrack) { - centeredControlsChildren.add( - IconButton( - icon: Icon(Icons.skip_previous), - onPressed: () => _callAction(entity, "media_previous_track"), - iconSize: Sizes.iconSize, - ) - ); - } - if (entity.supportPlay || entity.supportPause) { - if (entity.state == EntityState.playing) { - centeredControlsChildren.add( - IconButton( - icon: Icon(Icons.pause_circle_filled), - color: Colors.blue, - onPressed: () => _callAction(entity, "media_pause"), - iconSize: Sizes.iconSize*1.8, - ) - ); - } else if (entity.state == EntityState.paused) { - centeredControlsChildren.add( - IconButton( - icon: Icon(Icons.play_circle_filled), - color: Colors.blue, - onPressed: () => _callAction(entity, "media_play"), - iconSize: Sizes.iconSize*1.8, - ) - ); - } else { - centeredControlsChildren.add( - Container( - width: Sizes.iconSize*1.8, - height: Sizes.iconSize*2.0, - ) - ); - } - } - if (entity.supportNextTrack) { - centeredControlsChildren.add( - IconButton( - icon: Icon(Icons.skip_next), - onPressed: () => _callAction(entity, "media_next_track"), - iconSize: Sizes.iconSize, - ) - ); - } - if (centeredControlsChildren.isNotEmpty) { - result.add( - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: centeredControlsChildren, - ) - ) - ); - } else { - result.add( - Expanded( - child: Container( - height: 10.0, - ), - ) - ); - } - result.add( - IconButton( - icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:dots-vertical")), - onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity)) - ) - ); - return Row( - children: result, - mainAxisAlignment: MainAxisAlignment.center, - ); - } - Widget _buildState(MediaPlayerEntity entity) { TextStyle style = TextStyle( fontSize: 14.0, @@ -165,7 +49,13 @@ class MediaPlayerWidget extends StatelessWidget { states.add(Text("${entity.state}", style: style.apply(fontSizeDelta: 4.0),)); } if (entity.attributes['media_title'] != null) { - states.add(Text("${entity.attributes['media_title']}", style: style.apply(fontSizeDelta: 6.0, fontWeightDelta: 50),)); + states.add(Text( + "${entity.attributes['media_title']}", + style: style.apply(fontSizeDelta: 6.0, fontWeightDelta: 50), + maxLines: 1, + softWrap: true, + overflow: TextOverflow.ellipsis, + )); } if (entity.attributes['media_content_type'] == "music") { states.add(Text("${entity.attributes['media_artist'] ?? entity.attributes['app_name']}", style: style.apply(fontSizeDelta: 4.0),)); @@ -217,6 +107,254 @@ class MediaPlayerWidget extends StatelessWidget { } } +class MediaPlayerPlaybackControls extends StatelessWidget { + + final bool showMenu; + final bool showStop; + + const MediaPlayerPlaybackControls({Key key, this.showMenu: true, this.showStop: false}) : super(key: key); + + + void _setPower(MediaPlayerEntity entity) { + if (entity.state != EntityState.unavailable && entity.state != EntityState.unknown) { + if (entity.state == EntityState.off) { + TheLogger.debug("${entity.entityId} turn_on"); + eventBus.fire(new ServiceCallEvent( + entity.domain, "turn_on", entity.entityId, + null)); + } else { + TheLogger.debug("${entity.entityId} turn_off"); + eventBus.fire(new ServiceCallEvent( + entity.domain, "turn_off", entity.entityId, + null)); + } + } + } + + void _callAction(MediaPlayerEntity entity, String action) { + TheLogger.debug("${entity.entityId} $action"); + eventBus.fire(new ServiceCallEvent( + entity.domain, "$action", entity.entityId, + null)); + } + + @override + Widget build(BuildContext context) { + final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity; + List result = []; + if (entity.supportTurnOn || entity.supportTurnOff) { + result.add( + IconButton( + icon: Icon(Icons.power_settings_new), + onPressed: () => _setPower(entity), + iconSize: Sizes.iconSize, + ) + ); + } else { + result.add( + Container( + width: Sizes.iconSize, + ) + ); + } + List centeredControlsChildren = []; + if (entity.supportPreviousTrack && entity.state != EntityState.off && entity.state != EntityState.unavailable) { + centeredControlsChildren.add( + IconButton( + icon: Icon(Icons.skip_previous), + onPressed: () => _callAction(entity, "media_previous_track"), + iconSize: Sizes.iconSize, + ) + ); + } + if (entity.supportPlay || entity.supportPause) { + if (entity.state == EntityState.playing) { + centeredControlsChildren.add( + IconButton( + icon: Icon(Icons.pause_circle_filled), + color: Colors.blue, + onPressed: () => _callAction(entity, "media_pause"), + iconSize: Sizes.iconSize*1.8, + ) + ); + } else if (entity.state == EntityState.paused || entity.state == EntityState.idle) { + centeredControlsChildren.add( + IconButton( + icon: Icon(Icons.play_circle_filled), + color: Colors.blue, + onPressed: () => _callAction(entity, "media_play"), + iconSize: Sizes.iconSize*1.8, + ) + ); + } else { + centeredControlsChildren.add( + Container( + width: Sizes.iconSize*1.8, + height: Sizes.iconSize*2.0, + ) + ); + } + } + if (entity.supportNextTrack && entity.state != EntityState.off && entity.state != EntityState.unavailable) { + centeredControlsChildren.add( + IconButton( + icon: Icon(Icons.skip_next), + onPressed: () => _callAction(entity, "media_next_track"), + iconSize: Sizes.iconSize, + ) + ); + } + if (centeredControlsChildren.isNotEmpty) { + result.add( + Expanded( + child: Row( + mainAxisAlignment: showMenu ? MainAxisAlignment.center : MainAxisAlignment.end, + children: centeredControlsChildren, + ) + ) + ); + } else { + result.add( + Expanded( + child: Container( + height: 10.0, + ), + ) + ); + } + if (showMenu) { + result.add( + IconButton( + icon: Icon(MaterialDesignIcons.createIconDataFromIconName( + "mdi:dots-vertical")), + onPressed: () => eventBus.fire(new ShowEntityPageEvent(entity)) + ) + ); + } else if (entity.supportStop && entity.state != EntityState.off && entity.state != EntityState.unavailable) { + result.add( + IconButton( + icon: Icon(Icons.stop), + onPressed: () => _callAction(entity, "media_stop") + ) + ); + } + return Row( + children: result, + mainAxisAlignment: MainAxisAlignment.center, + ); + } + +} + +class MediaPlayerControls extends StatefulWidget { + @override + _MediaPlayerControlsState createState() => _MediaPlayerControlsState(); +} + +class _MediaPlayerControlsState extends State { + + double _newVolumeLevel; + bool _changedHere = false; + + void _setVolume(double value, String entityId) { + setState(() { + _changedHere = true; + _newVolumeLevel = value; + eventBus.fire(ServiceCallEvent("media_player", "volume_set", entityId, {"volume_level": value})); + }); + } + + void _setVolumeMute(bool isMuted, String entityId) { + eventBus.fire(ServiceCallEvent("media_player", "volume_mute", entityId, {"is_volume_muted": isMuted})); + } + + void _setVolumeUp(String entityId) { + eventBus.fire(ServiceCallEvent("media_player", "volume_up", entityId, null)); + } + + void _setVolumeDown(String entityId) { + eventBus.fire(ServiceCallEvent("media_player", "volume_down", entityId, null)); + } + + @override + Widget build(BuildContext context) { + final MediaPlayerEntity entity = EntityModel.of(context).entityWrapper.entity; + List children = [ + MediaPlayerPlaybackControls( + showMenu: false, + ) + ]; + if (entity.state != EntityState.off && entity.state != EntityState.unknown && entity.state != EntityState.unavailable) { + Widget muteWidget; + Widget volumeStepWidget; + if (entity.supportVolumeMute) { + bool isMuted = entity.attributes["is_volume_muted"] ?? false; + muteWidget = + IconButton( + icon: Icon(isMuted ? Icons.volume_off : Icons.volume_up), + onPressed: () => _setVolumeMute(!isMuted, entity.entityId) + ); + } else { + muteWidget = Container(width: 0.0, height: 0.0,); + } + if (entity.supportVolumeStep) { + volumeStepWidget = Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:plus")), + onPressed: () => _setVolumeUp(entity.entityId) + ), + IconButton( + icon: Icon(MaterialDesignIcons.createIconDataFromIconName("mdi:minus")), + onPressed: () => _setVolumeDown(entity.entityId) + ) + ], + ); + } else { + volumeStepWidget = Container(width: 0.0, height: 0.0,); + } + if (entity.supportVolumeSet) { + if (!_changedHere) { + _newVolumeLevel = entity._getDoubleAttributeValue("volume_level"); + } else { + _changedHere = false; + } + children.add( + UniversalSlider( + leading: muteWidget, + closing: volumeStepWidget, + title: "Volume", + onChanged: (value) { + setState(() { + _changedHere = true; + _newVolumeLevel = value; + }); + }, + value: _newVolumeLevel, + onChangeEnd: (value) => _setVolume(value, entity.entityId), + max: 1.0, + min: 0.0, + ) + ); + } else { + children.add(Row( + mainAxisSize: MainAxisSize.min, + children: [ + muteWidget, + volumeStepWidget + ], + )); + } + + } + return Column( + children: children, + ); + } + +} + class MediaPlayerProgressWidget extends StatefulWidget { @override _MediaPlayerProgressWidgetState createState() => _MediaPlayerProgressWidgetState(); diff --git a/lib/main.dart b/lib/main.dart index b9ce301..5c9deb0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,16 +29,17 @@ part 'entity_class/select_entity.class.dart'; part 'entity_class/other_entity.class.dart'; part 'entity_class/slider_entity.dart'; part 'entity_class/media_player_entity.class.dart'; -part 'entity_widgets/badge.dart'; +part 'entity_widgets/common/badge.dart'; part 'entity_widgets/model_widgets.dart'; part 'entity_widgets/default_entity_container.dart'; part 'entity_widgets/glance_entity_container.dart'; -part 'entity_widgets/entity_attributes_list.dart'; +part 'entity_widgets/common/entity_attributes_list.dart'; part 'entity_widgets/entity_icon.dart'; part 'entity_widgets/entity_name.dart'; -part 'entity_widgets/last_updated.dart'; -part 'entity_widgets/mode_swicth.dart'; -part 'entity_widgets/mode_selector.dart'; +part 'entity_widgets/common/last_updated.dart'; +part 'entity_widgets/common/mode_swicth.dart'; +part 'entity_widgets/common/mode_selector.dart'; +part 'entity_widgets/common/universal_slider.dart'; part 'entity_widgets/entity_colors.class.dart'; part 'entity_widgets/entity_page_container.dart'; part 'entity_widgets/history_chart/entity_history.dart'; @@ -59,7 +60,7 @@ part 'entity_widgets/state/button_state.dart'; part 'entity_widgets/controls/climate_controls.dart'; part 'entity_widgets/controls/cover_controls.dart'; part 'entity_widgets/controls/light_controls.dart'; -part 'entity_widgets/controls/media_player_widget.dart'; +part 'entity_widgets/controls/media_player_widgets.dart'; part 'settings.page.dart'; part 'home_assistant.class.dart'; part 'log.page.dart';