mirror of
https://github.com/jagandeepbrar/lunasea.git
synced 2026-08-30 20:24:25 +00:00
feat(tautulli): add ability to open media in Plex
This commit is contained in:
@@ -7,4 +7,7 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<queries>
|
||||
<package android:name="com.plexapp.android" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -40,4 +40,7 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<queries>
|
||||
<package android:name="com.plexapp.android" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -8,4 +8,7 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
<queries>
|
||||
<package android:name="com.plexapp.android" />
|
||||
</queries>
|
||||
</manifest>
|
||||
|
||||
@@ -61,6 +61,11 @@
|
||||
<string>Main</string>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<false/>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>plex</string>
|
||||
<string>https</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import 'package:lunasea/system/logger.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
extension StringAsLinksExtension on String {
|
||||
Future<bool> _launchUniversal(uri) async {
|
||||
return await launchUrl(
|
||||
Future<bool> _launchUniversal(String uri) async {
|
||||
return await launchUrlString(
|
||||
uri,
|
||||
webOnlyWindowName: '_blank',
|
||||
mode: LaunchMode.externalNonBrowserApplication,
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _launchDefault(uri) async {
|
||||
return await launchUrl(
|
||||
Future<bool> _launchDefault(String uri) async {
|
||||
return await launchUrlString(
|
||||
uri,
|
||||
webOnlyWindowName: '_blank',
|
||||
mode: LaunchMode.platformDefault,
|
||||
@@ -20,9 +20,8 @@ extension StringAsLinksExtension on String {
|
||||
|
||||
Future<void> openLink() async {
|
||||
try {
|
||||
Uri uri = Uri.parse(this);
|
||||
if (await _launchUniversal(uri)) return;
|
||||
await _launchDefault(uri);
|
||||
if (await _launchUniversal(this)) return;
|
||||
await _launchDefault(this);
|
||||
} catch (error, stack) {
|
||||
LunaLogger().error(
|
||||
'Unable to open URL',
|
||||
@@ -32,6 +31,10 @@ extension StringAsLinksExtension on String {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> canOpenUrl() async {
|
||||
return canLaunchUrlString(this);
|
||||
}
|
||||
|
||||
Future<void> openImdb() async =>
|
||||
await 'https://www.imdb.com/title/$this'.openLink();
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class TautulliState extends LunaModuleState {
|
||||
_playCountByPlatformStreamTypeGraph = null;
|
||||
_playCountByUserStreamTypeGraph = null;
|
||||
_librariesTable = null;
|
||||
_serverIdentity = null;
|
||||
_searchQuery = '';
|
||||
|
||||
// Clear user data
|
||||
@@ -52,6 +53,7 @@ class TautulliState extends LunaModuleState {
|
||||
resetActivity();
|
||||
resetUsers();
|
||||
resetHistory();
|
||||
resetServerIdentity();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -194,6 +196,24 @@ class TautulliState extends LunaModuleState {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
///////////////////////
|
||||
/// SERVER IDENTITY ///
|
||||
///////////////////////
|
||||
|
||||
Future<TautulliServerIdentity>? _serverIdentity;
|
||||
Future<TautulliServerIdentity>? get serverIdentity => _serverIdentity;
|
||||
set serverIdentity(Future<TautulliServerIdentity>? serverIdentity) {
|
||||
_serverIdentity = serverIdentity;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetServerIdentity() {
|
||||
if (_api != null) {
|
||||
_serverIdentity = _api!.miscellaneous.getServerIdentity();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
//////////////////
|
||||
/// STATISTICS ///
|
||||
//////////////////
|
||||
|
||||
@@ -42,6 +42,12 @@ class _State extends State<MediaDetailsRoute> {
|
||||
title: 'Media Details',
|
||||
scrollControllers: TautulliMediaDetailsNavigationBar.scrollControllers,
|
||||
pageController: _pageController,
|
||||
actions: [
|
||||
TautulliMediaDetailsOpenPlexButton(
|
||||
ratingKey: widget.ratingKey,
|
||||
mediaType: widget.mediaType,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ export 'widgets/metadata_header.dart';
|
||||
export 'widgets/metadata_metadata.dart';
|
||||
export 'widgets/metadata_summary.dart';
|
||||
export 'widgets/navigation_bar.dart';
|
||||
export 'widgets/open_plex_button.dart';
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/extensions/string/links.dart';
|
||||
import 'package:lunasea/modules/tautulli.dart';
|
||||
import 'package:lunasea/utils/links.dart';
|
||||
|
||||
class TautulliMediaDetailsOpenPlexButton extends StatelessWidget {
|
||||
final TautulliMediaType mediaType;
|
||||
final int ratingKey;
|
||||
|
||||
const TautulliMediaDetailsOpenPlexButton({
|
||||
Key? key,
|
||||
required this.mediaType,
|
||||
required this.ratingKey,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: context.watch<TautulliState>().serverIdentity,
|
||||
builder: (context, snapshot) {
|
||||
if (_isValidMediaType() && snapshot.hasData) {
|
||||
return LunaIconButton.appBar(
|
||||
icon: LunaIcons.PLEX,
|
||||
onPressed: () => _openPlex(snapshot.data as TautulliServerIdentity),
|
||||
);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool _isValidMediaType() {
|
||||
const invalidTypes = [
|
||||
TautulliMediaType.TRACK,
|
||||
TautulliMediaType.PHOTO,
|
||||
];
|
||||
return !invalidTypes.contains(mediaType);
|
||||
}
|
||||
|
||||
Future<void> _openPlex(TautulliServerIdentity identity) async {
|
||||
final mobile = LunaLinkedContent.plexMobile(
|
||||
identity.machineIdentifier!,
|
||||
ratingKey,
|
||||
);
|
||||
|
||||
if (await mobile.canOpenUrl()) {
|
||||
mobile.openLink();
|
||||
return;
|
||||
}
|
||||
|
||||
final web = LunaLinkedContent.plexWeb(
|
||||
identity.machineIdentifier!,
|
||||
ratingKey,
|
||||
mediaType == TautulliMediaType.CLIP,
|
||||
);
|
||||
web.openLink();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:lunasea/core.dart';
|
||||
import 'package:lunasea/extensions/string/links.dart';
|
||||
import 'package:lunasea/system/platform.dart';
|
||||
|
||||
enum LinkedContentType {
|
||||
MOVIE,
|
||||
@@ -28,29 +29,55 @@ enum LunaLinkedContent {
|
||||
|
||||
static String? imdb(String? id) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://www.imdb.com';
|
||||
const base = 'https://www.imdb.com';
|
||||
|
||||
return '$base/title/$id';
|
||||
}
|
||||
|
||||
static String? letterboxd(int? id) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://letterboxd.com';
|
||||
const base = 'https://letterboxd.com';
|
||||
|
||||
return '$base/tmdb/$id';
|
||||
}
|
||||
|
||||
static String? musicBrainz(String? id) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://musicbrainz.org/artist';
|
||||
const base = 'https://musicbrainz.org/artist';
|
||||
|
||||
return '$base/$id';
|
||||
}
|
||||
|
||||
static String plexMobile(
|
||||
String plexIdentifier,
|
||||
int ratingKey,
|
||||
) {
|
||||
if (LunaPlatform.isAndroid) {
|
||||
const base = 'plex://server://';
|
||||
const path = '/com.plexapp.plugins.library/library/metadata/';
|
||||
return '$base$plexIdentifier$path$ratingKey';
|
||||
} else {
|
||||
const base = 'plex://preplay/?server=';
|
||||
const path = '&metadataKey=/library/metadata/';
|
||||
return '$base$plexIdentifier$path$ratingKey';
|
||||
}
|
||||
}
|
||||
|
||||
static String plexWeb(
|
||||
String plexIdentifier,
|
||||
int ratingKey, [
|
||||
bool useLegacy = false,
|
||||
]) {
|
||||
const base = 'https://app.plex.tv/desktop#!/server/';
|
||||
const path = '/details?key=%2Flibrary%2Fmetadata%2F';
|
||||
final legacy = useLegacy ? '&legacy=1' : '';
|
||||
return '$base$plexIdentifier$path$ratingKey$legacy';
|
||||
}
|
||||
|
||||
static String? theMovieDB(dynamic id, LinkedContentType type) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://www.themoviedb.org';
|
||||
String baseImage = 'https://image.tmdb.org/t/p';
|
||||
const base = 'https://www.themoviedb.org';
|
||||
const baseImage = 'https://image.tmdb.org/t/p';
|
||||
|
||||
switch (type) {
|
||||
case LinkedContentType.MOVIE:
|
||||
@@ -70,7 +97,7 @@ enum LunaLinkedContent {
|
||||
|
||||
static String? trakt(int? id, LinkedContentType type) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://trakt.tv';
|
||||
const base = 'https://trakt.tv';
|
||||
|
||||
switch (type) {
|
||||
case LinkedContentType.MOVIE:
|
||||
@@ -84,14 +111,14 @@ enum LunaLinkedContent {
|
||||
|
||||
static String? tvMaze(int? id) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://www.tvmaze.com';
|
||||
const base = 'https://www.tvmaze.com';
|
||||
|
||||
return '$base/shows/$id';
|
||||
}
|
||||
|
||||
static String? theTVDB(int? id, LinkedContentType type) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://thetvdb.com';
|
||||
const base = 'https://thetvdb.com';
|
||||
|
||||
switch (type) {
|
||||
case LinkedContentType.MOVIE:
|
||||
@@ -107,7 +134,7 @@ enum LunaLinkedContent {
|
||||
|
||||
static String? youtube(String? id) {
|
||||
if (id == null) return null;
|
||||
String base = 'https://www.youtube.com';
|
||||
const base = 'https://www.youtube.com';
|
||||
|
||||
return '$base/watch?v=$id';
|
||||
}
|
||||
|
||||
@@ -1612,5 +1612,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
sdks:
|
||||
dart: ">=2.18.0 <4.0.0"
|
||||
dart: ">=2.18.0 <3.0.0"
|
||||
flutter: ">=3.3.0"
|
||||
|
||||
Reference in New Issue
Block a user