Resolves #207 Entity filter card support
This commit is contained in:
		| @@ -428,13 +428,16 @@ class HomeAssistant { | ||||
|   List<HACard> _createLovelaceCards(List rawCards) { | ||||
|     List<HACard> result = []; | ||||
|     rawCards.forEach((rawCard){ | ||||
|       bool isThereCardOptionsInside = rawCard["card"] != null; | ||||
|       HACard card = HACard( | ||||
|         id: "card", | ||||
|         name: rawCard["title"] ?? rawCard["name"], | ||||
|         type: rawCard['type'], | ||||
|         columnsCount: rawCard['columns'] ?? 4, | ||||
|         showName: rawCard['show_name'] ?? true, | ||||
|         showState: rawCard['show_state'] ?? true, | ||||
|         name: isThereCardOptionsInside ? rawCard["card"]["title"] ?? rawCard["card"]["name"] : rawCard["title"] ?? rawCard["name"], | ||||
|         type: isThereCardOptionsInside ? rawCard["card"]['type'] : rawCard['type'], | ||||
|         columnsCount: isThereCardOptionsInside ? rawCard["card"]['columns'] ?? 4 : rawCard['columns'] ?? 4, | ||||
|         showName: isThereCardOptionsInside ? rawCard["card"]['show_name'] ?? true : rawCard['show_name'] ?? true, | ||||
|         showState: isThereCardOptionsInside ? rawCard["card"]['show_state'] ?? true : rawCard['show_state'] ?? true, | ||||
|         showEmpty: rawCard['show_empty'] ?? true, | ||||
|         stateFilter: rawCard['state_filter'] ?? [] | ||||
|       ); | ||||
|       if (rawCard["cards"] != null) { | ||||
|         card.childCards = _createLovelaceCards(rawCard["cards"]); | ||||
|   | ||||
| @@ -79,11 +79,7 @@ part 'ui_class/view.class.dart'; | ||||
| part 'ui_class/card.class.dart'; | ||||
| part 'ui_class/sizes_class.dart'; | ||||
| part 'ui_widgets/view.dart'; | ||||
| part 'ui_widgets/entities_card.dart'; | ||||
| part 'ui_widgets/glance_card.dart'; | ||||
| part 'ui_widgets/entity_button_card.dart'; | ||||
| part 'ui_widgets/unsupported_card.dart'; | ||||
| part 'ui_widgets/media_control_card.dart'; | ||||
| part 'ui_widgets/card_widget.dart'; | ||||
| part 'ui_widgets/card_header_widget.dart'; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,9 @@ class HACard { | ||||
|   String type; | ||||
|   bool showName; | ||||
|   bool showState; | ||||
|   bool showEmpty; | ||||
|   int columnsCount; | ||||
|   List stateFilter; | ||||
|  | ||||
|   HACard({ | ||||
|     this.name, | ||||
| @@ -18,104 +20,27 @@ class HACard { | ||||
|     this.columnsCount: 4, | ||||
|     this.showName: true, | ||||
|     this.showState: true, | ||||
|     this.stateFilter: const [], | ||||
|     this.showEmpty: true, | ||||
|     @required this.type | ||||
|   }); | ||||
|  | ||||
|   Widget build(BuildContext context) { | ||||
|       switch (type) { | ||||
|  | ||||
|         case CardType.entities: { | ||||
|           return EntitiesCardWidget( | ||||
|             card: this, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         case CardType.glance: { | ||||
|           return GlanceCardWidget( | ||||
|             card: this, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         case CardType.mediaControl: { | ||||
|           return MediaControlCardWidget( | ||||
|             card: this, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         case CardType.entityButton: { | ||||
|           return EntityButtonCardWidget( | ||||
|             card: this, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         case CardType.horizontalStack: { | ||||
|           if (childCards.isNotEmpty) { | ||||
|             List<Widget> children = []; | ||||
|             childCards.forEach((card) { | ||||
|               children.add( | ||||
|                 Flexible( | ||||
|                   fit: FlexFit.tight, | ||||
|                   child: card.build(context), | ||||
|                 ) | ||||
|               ); | ||||
|             }); | ||||
|             return Row( | ||||
|               mainAxisSize: MainAxisSize.max, | ||||
|               mainAxisAlignment: MainAxisAlignment.center, | ||||
|               crossAxisAlignment: CrossAxisAlignment.start, | ||||
|               children: children, | ||||
|             ); | ||||
|           } | ||||
|           return Container(height: 0.0, width: 0.0,); | ||||
|         } | ||||
|  | ||||
|         case CardType.verticalStack: { | ||||
|           if (childCards.isNotEmpty) { | ||||
|             List<Widget> children = []; | ||||
|             childCards.forEach((card) { | ||||
|               children.add( | ||||
|                   card.build(context) | ||||
|               ); | ||||
|             }); | ||||
|             return Column( | ||||
|               mainAxisSize: MainAxisSize.min, | ||||
|               mainAxisAlignment: MainAxisAlignment.start, | ||||
|               children: children, | ||||
|             ); | ||||
|           } | ||||
|           return Container(height: 0.0, width: 0.0,); | ||||
|         } | ||||
|  | ||||
|         case CardType.weatherForecast: | ||||
|         case CardType.thermostat: | ||||
|         case CardType.sensor: | ||||
|         case CardType.plantStatus: | ||||
|         case CardType.pictureEntity: | ||||
|         case CardType.pictureElements: | ||||
|         case CardType.picture: | ||||
|         case CardType.map: | ||||
|         case CardType.iframe: | ||||
|         case CardType.gauge: | ||||
|         case CardType.conditional: | ||||
|         case CardType.alarmPanel: { | ||||
|           return UnsupportedCardWidget( | ||||
|             card: this, | ||||
|           ); | ||||
|         } | ||||
|  | ||||
|         default: { | ||||
|           if ((linkedEntityWrapper == null) && (entities.isNotEmpty)) { | ||||
|             return EntitiesCardWidget( | ||||
|               card: this, | ||||
|             ); | ||||
|           } else { | ||||
|             return UnsupportedCardWidget( | ||||
|               card: this, | ||||
|             ); | ||||
|           } | ||||
|         } | ||||
|  | ||||
|   List<EntityWrapper> getEntitiesToShow() { | ||||
|     return entities.where((entityWrapper) { | ||||
|       if (entityWrapper.entity.isHidden) { | ||||
|         return false; | ||||
|       } | ||||
|       if (stateFilter.isNotEmpty) { | ||||
|         return stateFilter.contains(entityWrapper.entity.state); | ||||
|       } | ||||
|       return true; | ||||
|     }).toList(); | ||||
|   } | ||||
|  | ||||
|   Widget build(BuildContext context) { | ||||
|     return CardWidget( | ||||
|       card: this, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
							
								
								
									
										206
									
								
								lib/ui_widgets/card_widget.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										206
									
								
								lib/ui_widgets/card_widget.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,206 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class CardWidget extends StatelessWidget { | ||||
|  | ||||
|   final HACard card; | ||||
|  | ||||
|   const CardWidget({ | ||||
|     Key key, | ||||
|     this.card | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if ((card.linkedEntityWrapper!= null) && (card.linkedEntityWrapper.entity.isHidden)) { | ||||
|       return Container(width: 0.0, height: 0.0,); | ||||
|     } | ||||
|  | ||||
|     switch (card.type) { | ||||
|  | ||||
|       case CardType.entities: { | ||||
|         return _buildEntitiesCard(context); | ||||
|       } | ||||
|  | ||||
|       case CardType.glance: { | ||||
|         return _buildGlanceCard(context); | ||||
|       } | ||||
|  | ||||
|       case CardType.mediaControl: { | ||||
|         return _buildMediaControlsCard(context); | ||||
|       } | ||||
|  | ||||
|       case CardType.entityButton: { | ||||
|         return _buildEntityButtonCard(context); | ||||
|       } | ||||
|  | ||||
|       case CardType.horizontalStack: { | ||||
|         if (card.childCards.isNotEmpty) { | ||||
|           List<Widget> children = []; | ||||
|           card.childCards.forEach((card) { | ||||
|             children.add( | ||||
|                 Flexible( | ||||
|                   fit: FlexFit.tight, | ||||
|                   child: card.build(context), | ||||
|                 ) | ||||
|             ); | ||||
|           }); | ||||
|           return Row( | ||||
|             mainAxisSize: MainAxisSize.max, | ||||
|             mainAxisAlignment: MainAxisAlignment.center, | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             children: children, | ||||
|           ); | ||||
|         } | ||||
|         return Container(height: 0.0, width: 0.0,); | ||||
|       } | ||||
|  | ||||
|       case CardType.verticalStack: { | ||||
|         if (card.childCards.isNotEmpty) { | ||||
|           List<Widget> children = []; | ||||
|           card.childCards.forEach((card) { | ||||
|             children.add( | ||||
|                 card.build(context) | ||||
|             ); | ||||
|           }); | ||||
|           return Column( | ||||
|             mainAxisSize: MainAxisSize.min, | ||||
|             mainAxisAlignment: MainAxisAlignment.start, | ||||
|             children: children, | ||||
|           ); | ||||
|         } | ||||
|         return Container(height: 0.0, width: 0.0,); | ||||
|       } | ||||
|  | ||||
|       default: { | ||||
|         if ((card.linkedEntityWrapper == null) && (card.entities.isNotEmpty)) { | ||||
|           return _buildEntitiesCard(context); | ||||
|         } else { | ||||
|           return _buildUnsupportedCard(context); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Widget _buildEntitiesCard(BuildContext context) { | ||||
|     List<EntityWrapper> entitiesToShow = card.getEntitiesToShow(); | ||||
|     if (entitiesToShow.isEmpty && !card.showEmpty) { | ||||
|       return Container(height: 0.0, width: 0.0,); | ||||
|     } | ||||
|     List<Widget> body = []; | ||||
|     body.add(CardHeaderWidget(name: card.name)); | ||||
|     entitiesToShow.forEach((EntityWrapper entity) { | ||||
|       if (!entity.entity.isHidden) { | ||||
|         body.add( | ||||
|             Padding( | ||||
|               padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), | ||||
|               child: EntityModel( | ||||
|                   entityWrapper: entity, | ||||
|                   handleTap: true, | ||||
|                   child: entity.entity.buildDefaultWidget(context) | ||||
|               ), | ||||
|             )); | ||||
|       } | ||||
|     }); | ||||
|     return Card( | ||||
|         child: new Column(mainAxisSize: MainAxisSize.min, children: body) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildGlanceCard(BuildContext context) { | ||||
|     List<EntityWrapper> entitiesToShow = card.getEntitiesToShow(); | ||||
|     if (entitiesToShow.isEmpty && !card.showEmpty) { | ||||
|       return Container(height: 0.0, width: 0.0,); | ||||
|     } | ||||
|     List<Widget> rows = []; | ||||
|     rows.add(CardHeaderWidget(name: card.name)); | ||||
|  | ||||
|     List<Widget> result = []; | ||||
|     int columnsCount = entitiesToShow.length >= card.columnsCount ? card.columnsCount : entitiesToShow.length; | ||||
|  | ||||
|     entitiesToShow.forEach((EntityWrapper entity) { | ||||
|       result.add( | ||||
|           FractionallySizedBox( | ||||
|             widthFactor: 1/columnsCount, | ||||
|             child: EntityModel( | ||||
|                 entityWrapper: entity, | ||||
|                 child: GlanceEntityContainer( | ||||
|                   showName: card.showName, | ||||
|                   showState: card.showState, | ||||
|                 ), | ||||
|                 handleTap: true | ||||
|             ), | ||||
|           ) | ||||
|       ); | ||||
|     }); | ||||
|     rows.add( | ||||
|         Padding( | ||||
|           padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding), | ||||
|           child: Wrap( | ||||
|             //alignment: WrapAlignment.spaceAround, | ||||
|             runSpacing: Sizes.rowPadding*2, | ||||
|             children: result, | ||||
|           ), | ||||
|         ) | ||||
|     ); | ||||
|  | ||||
|     return Card( | ||||
|         child: new Column(mainAxisSize: MainAxisSize.min, children: rows) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildMediaControlsCard(BuildContext context) { | ||||
|     return Card( | ||||
|       child: EntityModel( | ||||
|         entityWrapper: card.linkedEntityWrapper, | ||||
|           handleTap: null, | ||||
|           child: MediaPlayerWidget() | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildEntityButtonCard(BuildContext context) { | ||||
|     card.linkedEntityWrapper.displayName = card.name?.toUpperCase() ?? card.linkedEntityWrapper.displayName.toUpperCase(); | ||||
|     return Card( | ||||
|         child: EntityModel( | ||||
|             entityWrapper: card.linkedEntityWrapper, | ||||
|             child: ButtonEntityContainer(), | ||||
|             handleTap: true | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildUnsupportedCard(BuildContext context) { | ||||
|     List<Widget> body = []; | ||||
|     body.add(CardHeaderWidget(name: card.name ?? "")); | ||||
|     List<Widget> result = []; | ||||
|     if (card.linkedEntityWrapper != null) { | ||||
|       result.addAll(<Widget>[ | ||||
|         Padding( | ||||
|           padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), | ||||
|           child: EntityModel( | ||||
|               entityWrapper: card.linkedEntityWrapper, | ||||
|               handleTap: true, | ||||
|               child: card.linkedEntityWrapper.entity.buildDefaultWidget(context) | ||||
|           ), | ||||
|         ) | ||||
|       ]); | ||||
|     } else { | ||||
|       result.addAll(<Widget>[ | ||||
|         Padding( | ||||
|           padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding), | ||||
|           child: Text("'${card.type}' card is not supported yet"), | ||||
|         ), | ||||
|       ]); | ||||
|     } | ||||
|     body.addAll(result); | ||||
|     return Card( | ||||
|         child: new Column( | ||||
|             crossAxisAlignment: CrossAxisAlignment.start, | ||||
|             mainAxisSize: MainAxisSize.min, | ||||
|             children: body | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class EntitiesCardWidget extends StatelessWidget { | ||||
|  | ||||
|   final HACard card; | ||||
|  | ||||
|   const EntitiesCardWidget({ | ||||
|     Key key, | ||||
|     this.card | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if ((card.linkedEntityWrapper!= null) && (card.linkedEntityWrapper.entity.isHidden)) { | ||||
|       return Container(width: 0.0, height: 0.0,); | ||||
|     } | ||||
|     List<Widget> body = []; | ||||
|     body.add(CardHeaderWidget(name: card.name)); | ||||
|     body.addAll(_buildCardBody(context)); | ||||
|     return Card( | ||||
|         child: new Column(mainAxisSize: MainAxisSize.min, children: body) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   List<Widget> _buildCardBody(BuildContext context) { | ||||
|     List<Widget> result = []; | ||||
|     card.entities.forEach((EntityWrapper entity) { | ||||
|       if (!entity.entity.isHidden) { | ||||
|         result.add( | ||||
|             Padding( | ||||
|               padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), | ||||
|               child: EntityModel( | ||||
|                   entityWrapper: entity, | ||||
|                   handleTap: true, | ||||
|                   child: entity.entity.buildDefaultWidget(context) | ||||
|               ), | ||||
|             )); | ||||
|       } | ||||
|     }); | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,27 +0,0 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class EntityButtonCardWidget extends StatelessWidget { | ||||
|  | ||||
|   final HACard card; | ||||
|  | ||||
|   const EntityButtonCardWidget({ | ||||
|     Key key, | ||||
|     this.card | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if (card.linkedEntityWrapper!= null && card.linkedEntityWrapper.entity.isHidden) { | ||||
|       return Container(width: 0.0, height: 0.0,); | ||||
|     } | ||||
|     card.linkedEntityWrapper.displayName = card.name?.toUpperCase() ?? card.linkedEntityWrapper.displayName.toUpperCase(); | ||||
|     return Card( | ||||
|       child: EntityModel( | ||||
|         entityWrapper: card.linkedEntityWrapper, | ||||
|         child: ButtonEntityContainer(), | ||||
|         handleTap: true | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,55 +0,0 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class GlanceCardWidget extends StatelessWidget { | ||||
|  | ||||
|   final HACard card; | ||||
|  | ||||
|   const GlanceCardWidget({ | ||||
|     Key key, | ||||
|     this.card | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if ((card.linkedEntityWrapper!= null) && (card.linkedEntityWrapper.entity.isHidden)) { | ||||
|       return Container(width: 0.0, height: 0.0,); | ||||
|     } | ||||
|     List<Widget> rows = []; | ||||
|     rows.add(CardHeaderWidget(name: card.name)); | ||||
|     rows.add(_buildRows(context)); | ||||
|     return Card( | ||||
|         child: new Column(mainAxisSize: MainAxisSize.min, children: rows) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Widget _buildRows(BuildContext context) { | ||||
|     List<Widget> result = []; | ||||
|     List<EntityWrapper> toShow = card.entities.where((entity) {return !entity.entity.isHidden;}).toList(); | ||||
|     int columnsCount = toShow.length >= card.columnsCount ? card.columnsCount : toShow.length; | ||||
|  | ||||
|     toShow.forEach((EntityWrapper entity) { | ||||
|       result.add( | ||||
|         FractionallySizedBox( | ||||
|           widthFactor: 1/columnsCount, | ||||
|           child: EntityModel( | ||||
|             entityWrapper: entity, | ||||
|             child: GlanceEntityContainer( | ||||
|               showName: card.showName, | ||||
|               showState: card.showState, | ||||
|             ), | ||||
|             handleTap: true | ||||
|           ), | ||||
|         ) | ||||
|       ); | ||||
|     }); | ||||
|     return Padding( | ||||
|       padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, 2*Sizes.rowPadding), | ||||
|       child: Wrap( | ||||
|         //alignment: WrapAlignment.spaceAround, | ||||
|         runSpacing: Sizes.rowPadding*2, | ||||
|         children: result, | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class MediaControlCardWidget extends StatelessWidget { | ||||
|  | ||||
|   final HACard card; | ||||
|  | ||||
|   const MediaControlCardWidget({ | ||||
|     Key key, | ||||
|     this.card | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if ((card.linkedEntityWrapper == null) || (card.linkedEntityWrapper.entity.isHidden)) { | ||||
|       return Container(width: 0.0, height: 0.0,); | ||||
|     } | ||||
|  | ||||
|     return Card( | ||||
|         child: EntityModel( | ||||
|             entityWrapper: card.linkedEntityWrapper, | ||||
|             handleTap: null, | ||||
|             child: MediaPlayerWidget() | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|  | ||||
|  | ||||
| } | ||||
| @@ -1,53 +0,0 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class UnsupportedCardWidget extends StatelessWidget { | ||||
|  | ||||
|   final HACard card; | ||||
|  | ||||
|   const UnsupportedCardWidget({ | ||||
|     Key key, | ||||
|     this.card | ||||
|   }) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     if ((card.linkedEntityWrapper!= null) && (card.linkedEntityWrapper.entity.isHidden)) { | ||||
|       return Container(width: 0.0, height: 0.0,); | ||||
|     } | ||||
|     List<Widget> body = []; | ||||
|     body.add(CardHeaderWidget(name: card.name ?? "")); | ||||
|     body.addAll(_buildCardBody(context)); | ||||
|     return Card( | ||||
|         child: new Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|           mainAxisSize: MainAxisSize.min, | ||||
|           children: body | ||||
|         ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   List<Widget> _buildCardBody(BuildContext context) { | ||||
|     List<Widget> result = []; | ||||
|     if (card.linkedEntityWrapper != null) { | ||||
|       result.addAll(<Widget>[ | ||||
|           Padding( | ||||
|             padding: EdgeInsets.fromLTRB(0.0, Sizes.rowPadding, 0.0, Sizes.rowPadding), | ||||
|             child: EntityModel( | ||||
|                 entityWrapper: card.linkedEntityWrapper, | ||||
|                 handleTap: true, | ||||
|                 child: card.linkedEntityWrapper.entity.buildDefaultWidget(context) | ||||
|             ), | ||||
|           ) | ||||
|       ]); | ||||
|     } else { | ||||
|       result.addAll(<Widget>[ | ||||
|         Padding( | ||||
|           padding: EdgeInsets.fromLTRB(Sizes.leftWidgetPadding, Sizes.rowPadding, Sizes.rightWidgetPadding, Sizes.rowPadding), | ||||
|           child: Text("'${card.type}' card is not supported yet"), | ||||
|         ), | ||||
|       ]); | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -152,7 +152,7 @@ packages: | ||||
|       name: image | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.4" | ||||
|     version: "2.0.5" | ||||
|   intl: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -290,7 +290,7 @@ packages: | ||||
|       name: url_launcher | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "4.0.1" | ||||
|     version: "4.0.2" | ||||
|   uuid: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user