Share media url to HA CLient to play on media_player
This commit is contained in:
		| @@ -3,8 +3,9 @@ package com.keyboardcrumbs.hassclient; | ||||
| import android.os.Bundle; | ||||
| import io.flutter.app.FlutterActivity; | ||||
| import io.flutter.plugins.GeneratedPluginRegistrant; | ||||
| import io.flutter.plugins.share.FlutterShareReceiverActivity; | ||||
|  | ||||
| public class MainActivity extends FlutterActivity { | ||||
| public class MainActivity extends FlutterShareReceiverActivity { | ||||
|   @Override | ||||
|   protected void onCreate(Bundle savedInstanceState) { | ||||
|     super.onCreate(savedInstanceState); | ||||
|   | ||||
| @@ -253,7 +253,7 @@ class CardWidget extends StatelessWidget { | ||||
|                   return Wrap( | ||||
|                     //spacing: 5.0, | ||||
|                     //alignment: WrapAlignment.spaceEvenly, | ||||
|                     runSpacing: Sizes.rowPadding*2, | ||||
|                     runSpacing: Sizes.doubleRowPadding, | ||||
|                     children: buttons, | ||||
|                   ); | ||||
|                 } | ||||
|   | ||||
| @@ -112,4 +112,5 @@ class Sizes { | ||||
|   static const largeFontSize = 24.0; | ||||
|   static const inputWidth = 160.0; | ||||
|   static const rowPadding = 10.0; | ||||
|   static const doubleRowPadding = rowPadding*2; | ||||
| } | ||||
| @@ -149,6 +149,17 @@ class EntityCollection { | ||||
|     return _allEntities[entityId] != null; | ||||
|   } | ||||
|  | ||||
|   List<Entity> getByDomains(List<String> domains) { | ||||
|     List<Entity> result = []; | ||||
|     _allEntities.forEach((id, entity) { | ||||
|       if (domains.contains(entity.domain)) { | ||||
|         Logger.d("getByDomain: ${entity.isHidden}"); | ||||
|         result.add(entity); | ||||
|       } | ||||
|     }); | ||||
|     return result; | ||||
|   } | ||||
|  | ||||
|   List<Entity> filterEntitiesForDefaultView() { | ||||
|     List<Entity> result = []; | ||||
|     List<Entity> groups = []; | ||||
|   | ||||
| @@ -34,32 +34,37 @@ class DefaultEntityContainer extends StatelessWidget { | ||||
|         ], | ||||
|       ); | ||||
|     } | ||||
|     return InkWell( | ||||
|       onLongPress: () { | ||||
|         if (entityModel.handleTap) { | ||||
|           entityModel.entityWrapper.handleHold(); | ||||
|         } | ||||
|       }, | ||||
|       onTap: () { | ||||
|         if (entityModel.handleTap) { | ||||
|           entityModel.entityWrapper.handleTap(); | ||||
|         } | ||||
|       }, | ||||
|       child: Row( | ||||
|         mainAxisSize: MainAxisSize.max, | ||||
|         children: <Widget>[ | ||||
|           EntityIcon(), | ||||
|     Widget result = Row( | ||||
|       mainAxisSize: MainAxisSize.max, | ||||
|       children: <Widget>[ | ||||
|         EntityIcon(), | ||||
|  | ||||
|           Flexible( | ||||
|             fit: FlexFit.tight, | ||||
|             flex: 3, | ||||
|             child: EntityName( | ||||
|               padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0), | ||||
|             ), | ||||
|         Flexible( | ||||
|           fit: FlexFit.tight, | ||||
|           flex: 3, | ||||
|           child: EntityName( | ||||
|             padding: EdgeInsets.fromLTRB(10.0, 2.0, 10.0, 2.0), | ||||
|           ), | ||||
|           state | ||||
|         ], | ||||
|       ), | ||||
|         ), | ||||
|         state | ||||
|       ], | ||||
|     ); | ||||
|     if (entityModel.handleTap) { | ||||
|       return InkWell( | ||||
|         onLongPress: () { | ||||
|           if (entityModel.handleTap) { | ||||
|             entityModel.entityWrapper.handleHold(); | ||||
|           } | ||||
|         }, | ||||
|         onTap: () { | ||||
|           if (entityModel.handleTap) { | ||||
|             entityModel.entityWrapper.handleTap(); | ||||
|           } | ||||
|         }, | ||||
|         child: result, | ||||
|       ); | ||||
|     } else { | ||||
|       return result; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -11,6 +11,7 @@ class HomeAssistant { | ||||
|   EntityCollection entities; | ||||
|   HomeAssistantUI ui; | ||||
|   Map _instanceConfig = {}; | ||||
|   Map services; | ||||
|   String _userName; | ||||
|   HSVColor savedColor; | ||||
|  | ||||
| @@ -115,7 +116,11 @@ class HomeAssistant { | ||||
|   } | ||||
|  | ||||
|   Future _getServices() async { | ||||
|     await ConnectionManager().sendSocketMessage(type: "get_services").then((data) => Logger.d("Services received")).catchError((e) { | ||||
|     await ConnectionManager().sendSocketMessage(type: "get_services").then((data) { | ||||
|       Logger.d("Got ${data.length} services"); | ||||
|       Logger.d("Media extractor: ${data["media_extractor"]}"); | ||||
|       services = data; | ||||
|     }).catchError((e) { | ||||
|       Logger.w("Can't get services: ${e}"); | ||||
|     }); | ||||
|   } | ||||
|   | ||||
| @@ -23,6 +23,8 @@ import 'package:device_info/device_info.dart'; | ||||
| import 'package:flutter_local_notifications/flutter_local_notifications.dart'; | ||||
| import 'package:in_app_purchase/in_app_purchase.dart'; | ||||
| import 'plugins/circular_slider/single_circular_slider.dart'; | ||||
| import 'package:share/receive_share_state.dart'; | ||||
| import 'package:share/share.dart'; | ||||
|  | ||||
| import 'utils/logger.dart'; | ||||
|  | ||||
| @@ -121,6 +123,7 @@ part 'types/ha_error.dart'; | ||||
| part 'types/event_bus_events.dart'; | ||||
| part 'cards/widgets/gauge_card_body.dart'; | ||||
| part 'cards/widgets/light_card_body.dart'; | ||||
| part 'pages/play_media.page.dart'; | ||||
|  | ||||
|  | ||||
| EventBus eventBus = new EventBus(); | ||||
| @@ -167,6 +170,7 @@ class HAClientApp extends StatelessWidget { | ||||
|         "/": (context) => MainPage(title: 'HA Client'), | ||||
|         "/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"), | ||||
|         "/putchase": (context) => PurchasePage(title: "Support app development"), | ||||
|         "/play-media": (context) => PlayMediaPage(mediaUrl: "${(ModalRoute.of(context).settings.arguments as Map)['url']}",), | ||||
|         "/log-view": (context) => LogViewPage(title: "Log"), | ||||
|         "/login": (context) => WebviewScaffold( | ||||
|           url: "${ConnectionManager().oauthUrl}", | ||||
|   | ||||
| @@ -9,7 +9,7 @@ class MainPage extends StatefulWidget { | ||||
|   _MainPageState createState() => new _MainPageState(); | ||||
| } | ||||
|  | ||||
| class _MainPageState extends State<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin { | ||||
| class _MainPageState extends ReceiveShareState<MainPage> with WidgetsBindingObserver, TickerProviderStateMixin { | ||||
|  | ||||
|   StreamSubscription<List<PurchaseDetails>> _subscription; | ||||
|   StreamSubscription _stateSubscription; | ||||
| @@ -25,6 +25,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker | ||||
|   int _previousViewCount; | ||||
|   bool _showLoginButton = false; | ||||
|   bool _preventAppRefresh = false; | ||||
|   String _savedSharedText; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
| @@ -34,6 +35,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker | ||||
|       _handlePurchaseUpdates(purchases); | ||||
|     }); | ||||
|     super.initState(); | ||||
|     enableShareReceiving(); | ||||
|     WidgetsBinding.instance.addObserver(this); | ||||
|  | ||||
|     _firebaseMessaging.configure( | ||||
| @@ -76,6 +78,12 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker | ||||
|  | ||||
|   } | ||||
|  | ||||
|   @override void receiveShare(Share shared) { | ||||
|     if (shared.mimeType == ShareType.TYPE_PLAIN_TEXT) { | ||||
|       _savedSharedText = shared.text; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   Future onSelectNotification(String payload) async { | ||||
|     if (payload != null) { | ||||
|       Logger.d('Notification clicked: ' + payload); | ||||
| @@ -121,6 +129,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker | ||||
|   } | ||||
|  | ||||
|   _fetchData() async { | ||||
|     if (_savedSharedText != null && !HomeAssistant().isNoEntities) { | ||||
|       Logger.d("Got shared text: $_savedSharedText"); | ||||
|       Navigator.pushNamed(context, "/play-media", arguments: {"url": _savedSharedText}); | ||||
|       _savedSharedText = null; | ||||
|     } | ||||
|     await HomeAssistant().fetchData().then((_) { | ||||
|       _hideBottomBar(); | ||||
|       int currentViewCount = HomeAssistant().ui?.views?.length ?? 0; | ||||
| @@ -659,6 +672,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker | ||||
|             primary: true, | ||||
|             title: Text(HomeAssistant().locationName ?? ""), | ||||
|             actions: <Widget>[ | ||||
|               IconButton( | ||||
|                   icon: Icon(MaterialDesignIcons.getIconDataFromIconName( | ||||
|                       "mdi:television"), color: Colors.white,), | ||||
|                   onPressed: () => Navigator.pushNamed(context, "/play-media", arguments: {"url": ""}) | ||||
|               ), | ||||
|               IconButton( | ||||
|                   icon: Icon(MaterialDesignIcons.getIconDataFromIconName( | ||||
|                       "mdi:dots-vertical"), color: Colors.white,), | ||||
|   | ||||
							
								
								
									
										225
									
								
								lib/pages/play_media.page.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								lib/pages/play_media.page.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | ||||
| part of '../main.dart'; | ||||
|  | ||||
| class PlayMediaPage extends StatefulWidget { | ||||
|  | ||||
|   final String mediaUrl; | ||||
|  | ||||
|   PlayMediaPage({Key key, this.mediaUrl}) : super(key: key); | ||||
|  | ||||
|   @override | ||||
|   _PlayMediaPageState createState() => new _PlayMediaPageState(); | ||||
| } | ||||
|  | ||||
| class _PlayMediaPageState extends State<PlayMediaPage> { | ||||
|  | ||||
|   bool _loaded = false; | ||||
|   String _error = ""; | ||||
|   String _validationMessage = ""; | ||||
|   List<Entity> _players; | ||||
|   String _mediaUrl; | ||||
|   String _contentType; | ||||
|   bool _useMediaExtractor = false; | ||||
|   bool _isMediaExtractorExist = false; | ||||
|   StreamSubscription _stateSubscription; | ||||
|   StreamSubscription _refreshDataSubscription; | ||||
|   final List<String> _contentTypes = ["movie", "video", "music", "image", "image/jpg", "playlist"]; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _mediaUrl = widget.mediaUrl; | ||||
|     _contentType = _contentTypes[0]; | ||||
|     _stateSubscription = eventBus.on<StateChangedEvent>().listen((event) { | ||||
|       if (event.entityId.contains("media_player")) { | ||||
|         Logger.d("State change event handled by play media page: ${event.entityId}"); | ||||
|         setState(() {}); | ||||
|       } | ||||
|     }); | ||||
|     _refreshDataSubscription = eventBus.on<RefreshDataFinishedEvent>().listen((event) { | ||||
|       _loadMediaEntities(); | ||||
|     }); | ||||
|     _loadMediaEntities(); | ||||
|   } | ||||
|  | ||||
|   _loadMediaEntities() async { | ||||
|     if (HomeAssistant().isNoEntities) { | ||||
|       setState(() { | ||||
|         _loaded = false; | ||||
|       }); | ||||
|     } else { | ||||
|       _isMediaExtractorExist = HomeAssistant().services.containsKey("media_extractor"); | ||||
|       //_useMediaExtractor = _isMediaExtractorExist; | ||||
|       _players = HomeAssistant().entities.getByDomains(["media_player"]); | ||||
|       setState(() { | ||||
|         if (_players.isNotEmpty) { | ||||
|           _loaded = true; | ||||
|         } else { | ||||
|           _loaded = false; | ||||
|           _error = "Looks like you don't have any media player"; | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   void _playMedia(Entity entity) { | ||||
|     if (_mediaUrl == null || _mediaUrl.isEmpty) { | ||||
|       setState(() { | ||||
|         _validationMessage = "Media url must be specified"; | ||||
|       }); | ||||
|     } else { | ||||
|       String serviceDomain; | ||||
|       if (_useMediaExtractor) { | ||||
|         serviceDomain = "media_extractor"; | ||||
|       } else { | ||||
|         serviceDomain = "media_player"; | ||||
|       } | ||||
|       Navigator.pop(context); | ||||
|       ConnectionManager().callService( | ||||
|           domain: serviceDomain, | ||||
|           entityId: entity.entityId, | ||||
|           service: "play_media", | ||||
|           additionalServiceData: { | ||||
|             "media_content_id": _mediaUrl, | ||||
|             "media_content_type": _contentType | ||||
|           } | ||||
|       ); | ||||
|       eventBus.fire(ShowEntityPageEvent(entity)); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     Widget body; | ||||
|     if (!_loaded) { | ||||
|       body = _error.isEmpty ? PageLoadingIndicator() : PageLoadingError(errorText: _error); | ||||
|     } else { | ||||
|       List<Widget> children = []; | ||||
|       children.add(CardHeader(name: "Media:")); | ||||
|       children.add( | ||||
|         TextField( | ||||
|           maxLines: 5, | ||||
|           minLines: 1, | ||||
|           decoration: InputDecoration( | ||||
|               labelText: "Media url" | ||||
|           ), | ||||
|           controller: TextEditingController.fromValue(TextEditingValue(text: _mediaUrl)), | ||||
|           onChanged: (value) { | ||||
|             _mediaUrl = value; | ||||
|           } | ||||
|         ), | ||||
|       ); | ||||
|       if (_validationMessage.isNotEmpty) { | ||||
|         children.add(Text( | ||||
|           "$_validationMessage", | ||||
|           style: TextStyle(color: Colors.red) | ||||
|         )); | ||||
|       } | ||||
|       children.addAll(<Widget>[ | ||||
|         Container(height: Sizes.rowPadding,), | ||||
|         DropdownButton<String>( | ||||
|           value: _contentType, | ||||
|           isExpanded: true, | ||||
|           items: _contentTypes.map((String value) { | ||||
|             return new DropdownMenuItem<String>( | ||||
|               value: value, | ||||
|               child: new Text(value), | ||||
|             ); | ||||
|           }).toList(), | ||||
|           onChanged: (value) { | ||||
|             setState(() { | ||||
|               _contentType = value; | ||||
|             }); | ||||
|           }, | ||||
|         ) | ||||
|       ] | ||||
|       ); | ||||
|       if (_isMediaExtractorExist) { | ||||
|         children.addAll(<Widget>[ | ||||
|             Row( | ||||
|               mainAxisSize: MainAxisSize.max, | ||||
|               children: <Widget>[ | ||||
|                 Expanded( | ||||
|                   child: Text("Use media extractor"), | ||||
|                 ), | ||||
|                 Switch( | ||||
|                   value: _useMediaExtractor, | ||||
|                   onChanged: (value) => setState((){_useMediaExtractor = value;}), | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|             Container( | ||||
|               height: Sizes.rowPadding, | ||||
|             ) | ||||
|           ] | ||||
|         ); | ||||
|       } else { | ||||
|         children.addAll(<Widget>[ | ||||
|           Row( | ||||
|             mainAxisSize: MainAxisSize.max, | ||||
|             children: <Widget>[ | ||||
|               Expanded( | ||||
|                 child: Text("You can use media extractor here"), | ||||
|               ), | ||||
|               GestureDetector( | ||||
|                 onTap: () { | ||||
|                   Launcher.launchURLInCustomTab( | ||||
|                     context: context, | ||||
|                     url: "https://www.home-assistant.io/components/media_extractor/" | ||||
|                   ); | ||||
|                 }, | ||||
|                 child: Text( | ||||
|                   "How?", | ||||
|                   style: TextStyle( | ||||
|                       color: Colors.blue, | ||||
|                       decoration: TextDecoration.underline | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|           Container( | ||||
|             height: Sizes.doubleRowPadding, | ||||
|           ) | ||||
|         ] | ||||
|         ); | ||||
|       } | ||||
|       children.add(CardHeader(name: "Play on:")); | ||||
|       children.addAll( | ||||
|           _players.map((player) => InkWell( | ||||
|             child: EntityModel( | ||||
|                 entityWrapper: EntityWrapper(entity: player), | ||||
|                 handleTap: false, | ||||
|                 child: Padding( | ||||
|                   padding: EdgeInsets.only(bottom: Sizes.doubleRowPadding), | ||||
|                   child: DefaultEntityContainer(state: player._buildStatePart(context)), | ||||
|                 ) | ||||
|             ), | ||||
|             onTap: () => _playMedia(player), | ||||
|           )) | ||||
|       ); | ||||
|       body = ListView( | ||||
|         padding: EdgeInsets.all(Sizes.leftWidgetPadding), | ||||
|           scrollDirection: Axis.vertical, | ||||
|           children: children | ||||
|       ); | ||||
|     } | ||||
|     return new Scaffold( | ||||
|       appBar: new AppBar( | ||||
|         leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: (){ | ||||
|           Navigator.pop(context); | ||||
|         }), | ||||
|         title: new Text("Play media"), | ||||
|       ), | ||||
|       body: body, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   void dispose(){ | ||||
|     _stateSubscription?.cancel(); | ||||
|     _refreshDataSubscription?.cancel(); | ||||
|     super.dispose(); | ||||
|   } | ||||
|    | ||||
| } | ||||
| @@ -23,7 +23,6 @@ class Panel { | ||||
|     if (icon == null || !icon.startsWith("mdi:")) { | ||||
|       icon = Panel.iconsByComponent[type]; | ||||
|     } | ||||
|     Logger.d("New panel '$title'. type=$type, icon=$icon, urlPath=$urlPath"); | ||||
|     isHidden = (type == 'lovelace' || type == 'kiosk' || type == 'states' || type == 'profile' || type == 'developer-tools'); | ||||
|     isWebView = (type != 'config'); | ||||
|   } | ||||
|   | ||||
| @@ -17,7 +17,7 @@ class LinkToWebConfig extends StatelessWidget { | ||||
|                 textAlign: TextAlign.left, | ||||
|                 overflow: TextOverflow.ellipsis, | ||||
|                 style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)), | ||||
|             subtitle: Text("Tap to opne web version"), | ||||
|             subtitle: Text("Tap to open web version"), | ||||
|             onTap: () { | ||||
|               Launcher.launchAuthenticatedWebView(context: context, url: this.url, title: this.name); | ||||
|             }, | ||||
|   | ||||
| @@ -284,6 +284,15 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.3" | ||||
|   share: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       path: "." | ||||
|       ref: HEAD | ||||
|       resolved-ref: "1f8b139ca0bd35b643ef4f5ccce3a1b09931f16a" | ||||
|       url: "https://github.com/d-silveira/flutter-share.git" | ||||
|     source: git | ||||
|     version: "0.6.0" | ||||
|   shared_preferences: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|   | ||||
| @@ -26,6 +26,9 @@ dependencies: | ||||
|   flutter_secure_storage: ^3.2.1+1 | ||||
|   device_info: ^0.4.0+2 | ||||
|   flutter_local_notifications: ^0.8.2 | ||||
|   share: | ||||
|     git: | ||||
|       url: https://github.com/d-silveira/flutter-share.git | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user