Share media url to HA CLient to play on media_player
This commit is contained in:
parent
fc228d85ae
commit
0a6ff4586d
@ -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