Skip to content

Commit

Permalink
feat(tautulli): add ability to open media in Plex
Browse files Browse the repository at this point in the history
  • Loading branch information
JagandeepBrar committed Feb 16, 2023
1 parent 47ff262 commit 5cf1a06
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 19 deletions.
3 changes: 3 additions & 0 deletions android/app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
3 changes: 3 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
3 changes: 3 additions & 0 deletions android/app/src/profile/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
5 changes: 5 additions & 0 deletions ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
21 changes: 12 additions & 9 deletions lib/extensions/string/links.dart
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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',
Expand All @@ -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();

Expand Down
20 changes: 20 additions & 0 deletions lib/modules/tautulli/core/state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class TautulliState extends LunaModuleState {
_playCountByPlatformStreamTypeGraph = null;
_playCountByUserStreamTypeGraph = null;
_librariesTable = null;
_serverIdentity = null;
_searchQuery = '';

// Clear user data
Expand All @@ -52,6 +53,7 @@ class TautulliState extends LunaModuleState {
resetActivity();
resetUsers();
resetHistory();
resetServerIdentity();
notifyListeners();
}

Expand Down Expand Up @@ -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 ///
//////////////////
Expand Down
6 changes: 6 additions & 0 deletions lib/modules/tautulli/routes/media_details/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
),
],
);
}

Expand Down
1 change: 1 addition & 0 deletions lib/modules/tautulli/routes/media_details/widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -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();
}
}
45 changes: 36 additions & 9 deletions lib/utils/links.dart
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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';
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit 5cf1a06

Please sign in to comment.