Share media url to HA CLient to play on media_player

This commit is contained in:
estevez-dev 2019-09-09 12:25:13 +03:00
parent fc228d85ae
commit 0a6ff4586d
13 changed files with 311 additions and 30 deletions

View File

@ -3,8 +3,9 @@ package com.keyboardcrumbs.hassclient;
import android.os.Bundle; import android.os.Bundle;
import io.flutter.app.FlutterActivity; import io.flutter.app.FlutterActivity;
import io.flutter.plugins.GeneratedPluginRegistrant; import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugins.share.FlutterShareReceiverActivity;
public class MainActivity extends FlutterActivity { public class MainActivity extends FlutterShareReceiverActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

View File

@ -253,7 +253,7 @@ class CardWidget extends StatelessWidget {
return Wrap( return Wrap(
//spacing: 5.0, //spacing: 5.0,
//alignment: WrapAlignment.spaceEvenly, //alignment: WrapAlignment.spaceEvenly,
runSpacing: Sizes.rowPadding*2, runSpacing: Sizes.doubleRowPadding,
children: buttons, children: buttons,
); );
} }

View File

@ -112,4 +112,5 @@ class Sizes {
static const largeFontSize = 24.0; static const largeFontSize = 24.0;
static const inputWidth = 160.0; static const inputWidth = 160.0;
static const rowPadding = 10.0; static const rowPadding = 10.0;
static const doubleRowPadding = rowPadding*2;
} }

View File

@ -149,6 +149,17 @@ class EntityCollection {
return _allEntities[entityId] != null; 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> filterEntitiesForDefaultView() {
List<Entity> result = []; List<Entity> result = [];
List<Entity> groups = []; List<Entity> groups = [];

View File

@ -34,18 +34,7 @@ class DefaultEntityContainer extends StatelessWidget {
], ],
); );
} }
return InkWell( Widget result = Row(
onLongPress: () {
if (entityModel.handleTap) {
entityModel.entityWrapper.handleHold();
}
},
onTap: () {
if (entityModel.handleTap) {
entityModel.entityWrapper.handleTap();
}
},
child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
EntityIcon(), EntityIcon(),
@ -59,7 +48,23 @@ class DefaultEntityContainer extends StatelessWidget {
), ),
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;
}
} }
} }

View File

@ -11,6 +11,7 @@ class HomeAssistant {
EntityCollection entities; EntityCollection entities;
HomeAssistantUI ui; HomeAssistantUI ui;
Map _instanceConfig = {}; Map _instanceConfig = {};
Map services;
String _userName; String _userName;
HSVColor savedColor; HSVColor savedColor;
@ -115,7 +116,11 @@ class HomeAssistant {
} }
Future _getServices() async { 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}"); Logger.w("Can't get services: ${e}");
}); });
} }

View File

@ -23,6 +23,8 @@ import 'package:device_info/device_info.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:in_app_purchase/in_app_purchase.dart';
import 'plugins/circular_slider/single_circular_slider.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'; import 'utils/logger.dart';
@ -121,6 +123,7 @@ part 'types/ha_error.dart';
part 'types/event_bus_events.dart'; part 'types/event_bus_events.dart';
part 'cards/widgets/gauge_card_body.dart'; part 'cards/widgets/gauge_card_body.dart';
part 'cards/widgets/light_card_body.dart'; part 'cards/widgets/light_card_body.dart';
part 'pages/play_media.page.dart';
EventBus eventBus = new EventBus(); EventBus eventBus = new EventBus();
@ -167,6 +170,7 @@ class HAClientApp extends StatelessWidget {
"/": (context) => MainPage(title: 'HA Client'), "/": (context) => MainPage(title: 'HA Client'),
"/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"), "/connection-settings": (context) => ConnectionSettingsPage(title: "Settings"),
"/putchase": (context) => PurchasePage(title: "Support app development"), "/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"), "/log-view": (context) => LogViewPage(title: "Log"),
"/login": (context) => WebviewScaffold( "/login": (context) => WebviewScaffold(
url: "${ConnectionManager().oauthUrl}", url: "${ConnectionManager().oauthUrl}",

View File

@ -9,7 +9,7 @@ class MainPage extends StatefulWidget {
_MainPageState createState() => new _MainPageState(); _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<List<PurchaseDetails>> _subscription;
StreamSubscription _stateSubscription; StreamSubscription _stateSubscription;
@ -25,6 +25,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
int _previousViewCount; int _previousViewCount;
bool _showLoginButton = false; bool _showLoginButton = false;
bool _preventAppRefresh = false; bool _preventAppRefresh = false;
String _savedSharedText;
@override @override
void initState() { void initState() {
@ -34,6 +35,7 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
_handlePurchaseUpdates(purchases); _handlePurchaseUpdates(purchases);
}); });
super.initState(); super.initState();
enableShareReceiving();
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_firebaseMessaging.configure( _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 { Future onSelectNotification(String payload) async {
if (payload != null) { if (payload != null) {
Logger.d('Notification clicked: ' + payload); Logger.d('Notification clicked: ' + payload);
@ -121,6 +129,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
} }
_fetchData() async { _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((_) { await HomeAssistant().fetchData().then((_) {
_hideBottomBar(); _hideBottomBar();
int currentViewCount = HomeAssistant().ui?.views?.length ?? 0; int currentViewCount = HomeAssistant().ui?.views?.length ?? 0;
@ -659,6 +672,11 @@ class _MainPageState extends State<MainPage> with WidgetsBindingObserver, Ticker
primary: true, primary: true,
title: Text(HomeAssistant().locationName ?? ""), title: Text(HomeAssistant().locationName ?? ""),
actions: <Widget>[ actions: <Widget>[
IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:television"), color: Colors.white,),
onPressed: () => Navigator.pushNamed(context, "/play-media", arguments: {"url": ""})
),
IconButton( IconButton(
icon: Icon(MaterialDesignIcons.getIconDataFromIconName( icon: Icon(MaterialDesignIcons.getIconDataFromIconName(
"mdi:dots-vertical"), color: Colors.white,), "mdi:dots-vertical"), color: Colors.white,),

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

View File

@ -23,7 +23,6 @@ class Panel {
if (icon == null || !icon.startsWith("mdi:")) { if (icon == null || !icon.startsWith("mdi:")) {
icon = Panel.iconsByComponent[type]; 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'); isHidden = (type == 'lovelace' || type == 'kiosk' || type == 'states' || type == 'profile' || type == 'developer-tools');
isWebView = (type != 'config'); isWebView = (type != 'config');
} }

View File

@ -17,7 +17,7 @@ class LinkToWebConfig extends StatelessWidget {
textAlign: TextAlign.left, textAlign: TextAlign.left,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)), style: new TextStyle(fontWeight: FontWeight.bold, fontSize: Sizes.largeFontSize)),
subtitle: Text("Tap to opne web version"), subtitle: Text("Tap to open web version"),
onTap: () { onTap: () {
Launcher.launchAuthenticatedWebView(context: context, url: this.url, title: this.name); Launcher.launchAuthenticatedWebView(context: context, url: this.url, title: this.name);
}, },

View File

@ -284,6 +284,15 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.3" 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: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -26,6 +26,9 @@ dependencies:
flutter_secure_storage: ^3.2.1+1 flutter_secure_storage: ^3.2.1+1
device_info: ^0.4.0+2 device_info: ^0.4.0+2
flutter_local_notifications: ^0.8.2 flutter_local_notifications: ^0.8.2
share:
git:
url: https://github.com/d-silveira/flutter-share.git
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: