[Release] v4.0.0+400101 (#258)

NEW
- Nothing

TWEAKS
- [Settings] Added headers to all customization pages
- [Tautulli] Cleanup code, improve routing
- [State] Improve resetting of state when switching profiles

FIXES
- [Settings] Fix default page prompt not dismissing correctly
This commit is contained in:
Jagandeep Brar
2020-09-20 15:55:45 -05:00
committed by GitHub
parent 345aa1af63
commit 669f840fee
103 changed files with 1370 additions and 1166 deletions

View File

@@ -19,12 +19,12 @@ class Providers {
Providers._();
static void reset(BuildContext context) {
Provider.of<LunaSeaState>(context, listen: false).reset(initialize: true);
Provider.of<LunaSeaState>(context, listen: false).reset();
// General
Provider.of<HomeState>(context, listen: false).reset(initialize: true);
Provider.of<SettingsState>(context, listen: false).reset(initialize: true);
Provider.of<HomeState>(context, listen: false).reset();
Provider.of<SettingsState>(context, listen: false).reset();
// Monitoring
Provider.of<TautulliState>(context, listen: false).reset(initialize: true);
Provider.of<TautulliState>(context, listen: false).reset();
}
static MultiProvider providers({ @required Widget child }) => MultiProvider(

View File

@@ -21,4 +21,5 @@ export 'ui/rive.dart';
export 'ui/shape.dart';
export 'ui/snackbar.dart';
export 'ui/sticky_header.dart';
export 'ui/table.dart';
export 'ui/text.dart';

2
lib/core/ui/table.dart Normal file
View File

@@ -0,0 +1,2 @@
export 'table/block.dart';
export 'table/content.dart';

View File

@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
class LSTableBlock extends StatelessWidget {
final String title;
final List<LSTableContent> children;
LSTableBlock({
Key key,
this.title,
@required this.children,
}) : super(key: key);
List<Widget> _block({
String title,
@required List<LSTableContent> children,
}) => [
if(title != null) LSHeader(text: title),
LSCard(
child: Padding(
child: Column(
children: children,
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
),
];
@override
Widget build(BuildContext context) => Column(
children: _block(title: title, children: children),
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
);
}

View File

@@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
class LSTableContent extends StatelessWidget {
final String title;
final String body;
LSTableContent({
Key key,
@required this.title,
@required this.body,
}) : super(key: key);
@override
Widget build(BuildContext context) => Padding(
child: Row(
children: [
Expanded(
child: Text(
title.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
);
}

View File

@@ -12,7 +12,7 @@ class HomeDialogs {
void _setValues(bool flag, int index) {
_flag = flag;
_index = index;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(

View File

@@ -15,6 +15,7 @@ class HomeState extends ChangeNotifier {
_navigationIndex = HomeDatabaseValue.NAVIGATION_INDEX.data;
_calendarStartingType = HomeDatabaseValue.CALENDAR_STARTING_TYPE.data;
}
notifyListeners();
}
int _navigationIndex;

View File

@@ -12,7 +12,7 @@ class LidarrDialogs {
void _setValues(bool flag, LidarrQualityProfile quality) {
_flag = flag;
_quality = quality;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -39,7 +39,7 @@ class LidarrDialogs {
void _setValues(bool flag, LidarrMetadataProfile metadata) {
_flag = flag;
_metadata = metadata;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -66,7 +66,7 @@ class LidarrDialogs {
void _setValues(bool flag, bool files) {
_flag = flag;
_files = files;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -97,7 +97,7 @@ class LidarrDialogs {
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -122,7 +122,7 @@ class LidarrDialogs {
void _setValues(bool flag) {
_flag = flag;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -154,7 +154,7 @@ class LidarrDialogs {
void _setValues(bool flag, String value) {
_flag = flag;
_value = value;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -181,7 +181,7 @@ class LidarrDialogs {
void _setValues(bool flag, LidarrRootFolder folder) {
_flag = flag;
_folder = folder;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -220,7 +220,7 @@ class LidarrDialogs {
void _setValues(bool flag, String value) {
_flag = flag;
_value = value;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(
@@ -247,7 +247,7 @@ class LidarrDialogs {
void _setValues(bool flag, int index) {
_flag = flag;
_index = index;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(

View File

@@ -573,7 +573,7 @@ class NZBGetDialogs {
void _setValues(bool flag, int index) {
_flag = flag;
_index = index;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(

View File

@@ -298,7 +298,7 @@ class RadarrDialogs {
void _setValues(bool flag, int index) {
_flag = flag;
_index = index;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(

View File

@@ -661,7 +661,7 @@ class SABnzbdDialogs {
void _setValues(bool flag, int index) {
_flag = flag;
_index = index;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(

View File

@@ -1,28 +1 @@
import 'package:flutter/material.dart';
class SettingsState extends ChangeNotifier {
SettingsState() {
reset(initialize: true);
}
/// Reset the state of Home back to the default
///
/// If `initialize` is true, resets everything.
/// If false, the navigation index, etc. are not reset.
void reset({ bool initialize = false }) {
if(initialize) {
_navigationIndex = 0;
}
}
GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
int _navigationIndex;
int get navigationIndex => _navigationIndex;
set navigationIndex(int navigationIndex) {
assert(navigationIndex != null);
_navigationIndex = navigationIndex;
notifyListeners();
}
}
export 'state/global.dart';

View File

@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
class SettingsState extends ChangeNotifier {
SettingsState() {
reset(initialize: true);
}
/// Reset the state of Home back to the default
///
/// If `initialize` is true, resets everything.
/// If false, the navigation index, etc. are not reset.
void reset({ bool initialize = false }) {
if(initialize) {}
notifyListeners();
}
GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
GlobalKey<ScaffoldState> rootScaffoldKey = GlobalKey<ScaffoldState>();
}

View File

@@ -30,9 +30,25 @@ class _State extends State<SettingsCustomizationAppearanceRoute> {
Widget get _body => LSListView(
children: [
SettingsCustomizationAppearanceAMOLEDTile(),
SettingsCustomizationAppearanceAMOLEDBorderTile(),
SettingsCustomizationAppearance24HourTimeTile(),
..._theme,
..._localization,
],
);
List<Widget> get _theme => [
LSHeader(
text: 'Theme',
subtitle: 'Customize the colour theme of LunaSea',
),
SettingsCustomizationAppearanceAMOLEDTile(),
SettingsCustomizationAppearanceAMOLEDBorderTile(),
];
List<Widget> get _localization => [
LSHeader(
text: 'Localization',
subtitle: 'Customizable options for localization',
),
SettingsCustomizationAppearance24HourTimeTile(),
];
}

View File

@@ -30,10 +30,18 @@ class _State extends State<SettingsCustomizationDrawerRoute> {
Widget get _body => LSListView(
children: [
SettingsCustomizationDrawerUseCategoriesTile(),
SettingsCustomizationDrawerExpandAutomationTile(),
SettingsCustomizationDrawerExpandClientsTile(),
SettingsCustomizationDrawerExpandMonitoringTile(),
..._folders,
],
);
List<Widget> get _folders => [
LSHeader(
text: 'Folders',
subtitle: 'Customizable options related to grouping modules into folders',
),
SettingsCustomizationDrawerUseCategoriesTile(),
SettingsCustomizationDrawerExpandAutomationTile(),
SettingsCustomizationDrawerExpandClientsTile(),
SettingsCustomizationDrawerExpandMonitoringTile(),
];
}

View File

@@ -10,7 +10,7 @@ class SettingsCustomizationDrawerExpandAutomationTile extends StatelessWidget {
]),
builder: (context, box, widget) => LSCardTile(
title: LSTitle(text: 'Expand Automation'),
subtitle: LSSubtitle(text: 'Expand Automation Category Initially'),
subtitle: LSSubtitle(text: 'Expand Automation Folder Initially'),
trailing: Switch(
value: LunaSeaDatabaseValue.DRAWER_EXPAND_AUTOMATION.data,
onChanged: LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.data

View File

@@ -10,7 +10,7 @@ class SettingsCustomizationDrawerExpandClientsTile extends StatelessWidget {
]),
builder: (context, box, widget) => LSCardTile(
title: LSTitle(text: 'Expand Clients'),
subtitle: LSSubtitle(text: 'Expand Clients Category Initially'),
subtitle: LSSubtitle(text: 'Expand Clients Folder Initially'),
trailing: Switch(
value: LunaSeaDatabaseValue.DRAWER_EXPAND_CLIENTS.data,
onChanged: LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.data

View File

@@ -10,7 +10,7 @@ class SettingsCustomizationDrawerExpandMonitoringTile extends StatelessWidget {
]),
builder: (context, box, widget) => LSCardTile(
title: LSTitle(text: 'Expand Monitoring'),
subtitle: LSSubtitle(text: 'Expand Monitoring Category Initially'),
subtitle: LSSubtitle(text: 'Expand Monitoring Folder Initially'),
trailing: Switch(
value: LunaSeaDatabaseValue.DRAWER_EXPAND_MONITORING.data,
onChanged: LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.data

View File

@@ -6,7 +6,7 @@ class SettingsCustomizationDrawerUseCategoriesTile extends StatelessWidget {
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.key]),
builder: (context, box, widget) => LSCardTile(
title: LSTitle(text: 'Group by Category'),
title: LSTitle(text: 'Use Folders'),
subtitle: LSSubtitle(text: 'Group Modules into Categories'),
trailing: Switch(
value: LunaSeaDatabaseValue.DRAWER_GROUP_MODULES.data,

View File

@@ -30,8 +30,24 @@ class _State extends State<SettingsCustomizationHomeRoute> {
Widget get _body => LSListView(
children: [
SettingsCustomizationHomeDefaultPageTile(),
SettingsCustomizationHomeBrandColoursTile(),
..._defaultPages,
..._modules,
],
);
List<Widget> get _defaultPages => [
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationHomeDefaultPageTile(),
];
List<Widget> get _modules => [
LSHeader(
text: 'Modules',
subtitle: 'Customizable options related to the module list',
),
SettingsCustomizationHomeBrandColoursTile(),
];
}

View File

@@ -7,7 +7,7 @@ class SettingsCustomizationHomeDefaultPageTile extends StatelessWidget {
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [HomeDatabaseValue.NAVIGATION_INDEX.key]),
builder: (context, box, _) => LSCardTile(
title: LSTitle(text: 'Default Page'),
title: LSTitle(text: 'Home'),
subtitle: LSSubtitle(text: HomeNavigationBar.titles[HomeDatabaseValue.NAVIGATION_INDEX.data]),
trailing: LSIconButton(icon: HomeNavigationBar.icons[HomeDatabaseValue.NAVIGATION_INDEX.data]),
onTap: () async => _defaultPage(context),

View File

@@ -38,6 +38,10 @@ class _State extends State<SettingsCustomizationLidarrRoute> {
Widget get _body => LSListView(
children: [
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationLidarrDefaultPageTile(),
],
);

View File

@@ -38,6 +38,10 @@ class _State extends State<SettingsCustomizationNZBGetRoute> {
Widget get _body => LSListView(
children: [
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationNZBGetDefaultPageTile(),
],
);

View File

@@ -7,7 +7,7 @@ class SettingsCustomizationNZBGetDefaultPageTile extends StatelessWidget {
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [NZBGetDatabaseValue.NAVIGATION_INDEX.key]),
builder: (context, box, _) => LSCardTile(
title: LSTitle(text: 'Default Page'),
title: LSTitle(text: 'Home'),
subtitle: LSSubtitle(text: NZBGetNavigationBar.titles[NZBGetDatabaseValue.NAVIGATION_INDEX.data]),
trailing: LSIconButton(icon: NZBGetNavigationBar.icons[NZBGetDatabaseValue.NAVIGATION_INDEX.data]),
onTap: () async => _defaultPage(context),

View File

@@ -38,6 +38,10 @@ class _State extends State<SettingsCustomizationRadarrRoute> {
Widget get _body => LSListView(
children: [
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationRadarrDefaultPageTile(),
],
);

View File

@@ -7,7 +7,7 @@ class SettingsCustomizationRadarrDefaultPageTile extends StatelessWidget {
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [RadarrDatabaseValue.NAVIGATION_INDEX.key]),
builder: (context, box, _) => LSCardTile(
title: LSTitle(text: 'Default Page'),
title: LSTitle(text: 'Home'),
subtitle: LSSubtitle(text: RadarrNavigationBar.titles[RadarrDatabaseValue.NAVIGATION_INDEX.data]),
trailing: LSIconButton(icon: RadarrNavigationBar.icons[RadarrDatabaseValue.NAVIGATION_INDEX.data]),
onTap: () async => _defaultPage(context),

View File

@@ -38,6 +38,10 @@ class _State extends State<SettingsCustomizationSABnzbdRoute> {
Widget get _body => LSListView(
children: [
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationSABnzbdDefaultPageTile(),
],
);

View File

@@ -7,7 +7,7 @@ class SettingsCustomizationSABnzbdDefaultPageTile extends StatelessWidget {
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [SABnzbdDatabaseValue.NAVIGATION_INDEX.key]),
builder: (context, box, _) => LSCardTile(
title: LSTitle(text: 'Default Page'),
title: LSTitle(text: 'Home'),
subtitle: LSSubtitle(text: SABnzbdNavigationBar.titles[SABnzbdDatabaseValue.NAVIGATION_INDEX.data]),
trailing: LSIconButton(icon: SABnzbdNavigationBar.icons[SABnzbdDatabaseValue.NAVIGATION_INDEX.data]),
onTap: () async => _defaultPage(context),

View File

@@ -38,7 +38,15 @@ class _State extends State<SettingsCustomizationSearchRoute> {
Widget get _body => LSListView(
children: [
SettingsCustomizationSearchHideAdultCategoriesTile(),
..._categories,
],
);
List<Widget> get _categories => [
LSHeader(
text: 'Categories',
subtitle: 'Customizable options related to the category lists',
),
SettingsCustomizationSearchHideAdultCategoriesTile(),
];
}

View File

@@ -38,6 +38,10 @@ class _State extends State<SettingsCustomizationSonarrRoute> {
Widget get _body => LSListView(
children: [
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationSonarrDefaultPageTile(),
],
);

View File

@@ -7,7 +7,7 @@ class SettingsCustomizationSonarrDefaultPageTile extends StatelessWidget {
Widget build(BuildContext context) => ValueListenableBuilder(
valueListenable: Database.lunaSeaBox.listenable(keys: [SonarrDatabaseValue.NAVIGATION_INDEX.key]),
builder: (context, box, _) => LSCardTile(
title: LSTitle(text: 'Default Page'),
title: LSTitle(text: 'Home'),
subtitle: LSSubtitle(text: SonarrNavigationBar.titles[SonarrDatabaseValue.NAVIGATION_INDEX.data]),
trailing: LSIconButton(icon: SonarrNavigationBar.icons[SonarrDatabaseValue.NAVIGATION_INDEX.data]),
onTap: () async => _defaultPage(context),

View File

@@ -47,7 +47,7 @@ class _State extends State<SettingsCustomizationTautulliRoute> {
List<Widget> get _defaultPages => <Widget>[
LSHeader(
text: 'Default Pages',
subtitle: 'Choose the default/initial page when opening pages with navigation bars',
subtitle: 'Choose the default page when opening routes with navigation bars',
),
SettingsCustomizationTautulliDefaultPageHomeTile(),
SettingsCustomizationTautulliDefaultPageGraphsTile(),

View File

@@ -365,7 +365,7 @@ class SonarrDialogs {
void _setValues(bool flag, int index) {
_flag = flag;
_index = index;
Navigator.of(context).pop();
Navigator.of(context, rootNavigator: true).pop();
}
await LSDialog.dialog(

View File

@@ -1,2 +1,3 @@
export 'extensions/tautulli_history_record.dart';
export 'extensions/tautulli_session.dart';
export 'extensions/tautulli_table_library.dart';

View File

@@ -1,12 +1,49 @@
import 'package:flutter/material.dart';
import 'package:tautulli/tautulli.dart';
import 'package:lunasea/core.dart';
extension TautulliHistoryRecordExtension on TautulliHistoryRecord {
/// Returns a the header that should be used for titles, appbars, etc.
String get header => this.grandparentTitle == null || this.grandparentTitle.isEmpty
String get lsFullTitle => [
if(this.title != null && this.title.isNotEmpty) '${this.title}',
if(this.parentTitle != null && this.parentTitle.isNotEmpty) '\n${this.parentTitle}',
if(this.grandparentTitle != null && this.grandparentTitle.isNotEmpty) '\n${this.grandparentTitle}',
].join();
String get lsTitle => this.grandparentTitle == null || this.grandparentTitle.isEmpty
? this.parentTitle == null || this.parentTitle.isEmpty
? this.title == null || this.title.isEmpty
? 'Unknown Title'
: this.title
: this.parentTitle
? this.title == null || this.title.isEmpty
? 'Unknown Title'
: this.title
: this.parentTitle
: this.grandparentTitle;
}
IconData get lsWatchStatusIcon {
switch(this.watchedStatus) {
case TautulliWatchedStatus.PARTIALLY_WATCHED: return Icons.radio_button_checked;
case TautulliWatchedStatus.WATCHED: return Icons.check_circle;
case TautulliWatchedStatus.UNWATCHED:
default: return Icons.radio_button_unchecked;
}
}
String get lsDate => DateTime.now().lsDateTime_ageString(this.date);
String get lsStatus {
switch(this.watchedStatus) {
case TautulliWatchedStatus.UNWATCHED: return 'Incompleted';
case TautulliWatchedStatus.PARTIALLY_WATCHED: return 'Partially Completed';
case TautulliWatchedStatus.WATCHED: return 'Completed';
default: return 'Unknown';
}
}
String get lsState {
switch(this.state) {
case TautulliSessionState.PLAYING: return 'Playing';
case TautulliSessionState.PAUSED: return 'Paused';
case TautulliSessionState.BUFFERING: return 'Buffering';
case TautulliSessionState.NULL:
default: return 'Finished';
}
}
}

View File

@@ -0,0 +1,110 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
extension TautulliSessionExtension on TautulliSession {
String get lsFullTitle => [
if(this.title != null && this.title.isNotEmpty) '${this.title}',
if(this.parentTitle != null && this.parentTitle.isNotEmpty) '\n${this.parentTitle}',
if(this.grandparentTitle != null && this.grandparentTitle.isNotEmpty) '\n${this.grandparentTitle}',
].join();
String get lsTitle => this.grandparentTitle == null || this.grandparentTitle.isEmpty
? this.parentTitle == null || this.parentTitle.isEmpty
? this.title == null || this.title.isEmpty
? 'Unknown Title'
: this.title
: this.parentTitle
: this.grandparentTitle;
String get lsDuration {
double _percent = this.progressPercent/100;
String _progress = Duration(seconds: (this.streamDuration.inSeconds*_percent).floor()).lsDuration_timestamp();
String _total = this.streamDuration.lsDuration_timestamp();
return '$_progress/$_total (${this.progressPercent}%)';
}
String get lsBandwidth => '${this.bandwidth.lsBytes_KilobytesToString(bytes: false, decimals: 1)}ps';
String get lsQuality => '${this.qualityProfile} (${this.streamBitrate.lsBytes_KilobytesToString(bytes: false, decimals: 1)}ps)';
String get lsStream {
switch(this.transcodeDecision) {
case TautulliTranscodeDecision.TRANSCODE:
String _transcodeStatus = this.transcodeThrottled ? 'Throttled' : '${this.transcodeSpeed ?? 0.0}x';
return 'Transcode ($_transcodeStatus)';
case TautulliTranscodeDecision.COPY: return 'Direct Stream';
case TautulliTranscodeDecision.DIRECT_PLAY: return 'Direct Play';
case TautulliTranscodeDecision.NULL:
default: return 'Unknown Transcode Decision';
}
}
String get lsContainer => [
this.streamContainerDecision.name,
' (',
this.container.toUpperCase(),
if(this.streamContainerDecision != null && this.streamContainerDecision != TautulliTranscodeDecision.DIRECT_PLAY)
' ${Constants.TEXT_RARROW} ${this.streamContainer.toUpperCase()}',
')',
].join();
String get lsVideo => [
this.videoDecision.name,
' (',
this.videoCodec.toUpperCase(),
' ',
this.videoFullResolution,
if(this.transcodeVideoCodec.isNotEmpty) ' ${Constants.TEXT_RARROW} ',
if(this.transcodeVideoCodec.isNotEmpty) this.transcodeVideoCodec.toUpperCase(),
if(this.transcodeVideoCodec.isNotEmpty) ' ${this.streamVideoFullResolution}',
')',
].join();
String get lsAudio => [
this.audioDecision.name,
' (',
this.audioCodec.toUpperCase(),
if(this.transcodeAudioCodec.isNotEmpty) ' ${Constants.TEXT_RARROW} ',
if(this.transcodeAudioCodec.isNotEmpty) this.transcodeAudioCodec.toUpperCase(),
')',
].join();
String lsArtworkPath(BuildContext context) {
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
switch(this.mediaType) {
case TautulliMediaType.EPISODE: return _state.getImageURLFromRatingKey(this.grandparentRatingKey);
case TautulliMediaType.TRACK: return _state.getImageURLFromRatingKey(this.parentRatingKey);
case TautulliMediaType.MOVIE:
case TautulliMediaType.LIVE:
default: return _state.getImageURLFromRatingKey(this.ratingKey);
}
}
double get lsTranscodeProgress => min(1.0, max(0, this.transcodeProgress/100));
double get lsProgressPercent => min(1.0, max(0, this.progressPercent/100));
IconData get lsStateIcon {
switch(this.state) {
case TautulliSessionState.PAUSED: return Icons.pause;
case TautulliSessionState.PLAYING: return Icons.play_arrow;
case TautulliSessionState.BUFFERING:
default: return Icons.compare_arrows;
}
}
String get lsTranscodeDecision {
switch(this.transcodeDecision) {
case TautulliTranscodeDecision.TRANSCODE:
String _transcodeStatus = this.transcodeThrottled ? 'Throttled' : '${this.transcodeSpeed ?? 0.0}x';
return 'Transcoding ($_transcodeStatus)';
case TautulliTranscodeDecision.DIRECT_PLAY:
case TautulliTranscodeDecision.COPY:
case TautulliTranscodeDecision.NULL:
default: return this.transcodeDecision.name ?? 'Unknown';
}
}
}

View File

@@ -7,29 +7,29 @@ class TautulliRouter {
TautulliRouter._();
static void initialize() {
TautulliRoute.defineRoute(router);
TautulliHomeRouter.defineRoutes(router);
// Details
TautulliActivityDetailsRoute.defineRoute(router);
TautulliHistoryDetailsRoute.defineRoute(router);
TautulliActivityDetailsRouter.defineRoutes(router);
TautulliIPAddressDetailsRouter.defineRoutes(router);
TautulliLibrariesDetailsRouter.defineRoutes(router);
TautulliMediaDetailsRoute.defineRoute(router);
TautulliUserDetailsRoute.defineRoute(router);
// Logs
TautulliLogsRoute.defineRoute(router);
TautulliLogsLoginsRoute.defineRoute(router);
TautulliLogsNewslettersRoute.defineRoute(router);
TautulliLogsNotificationsRoute.defineRoute(router);
TautulliLogsPlexMediaScannerRoute.defineRoute(router);
TautulliLogsPlexMediaServerRoute.defineRoute(router);
TautulliLogsTautulliRoute.defineRoute(router);
// Other
TautulliCheckForUpdatesRoute.defineRoute(router);
TautulliGraphsRoute.defineRoute(router);
TautulliLibrariesRoute.defineRoute(router);
TautulliHistoryDetailsRouter.defineRoutes(router);
TautulliUserDetailsRouter.defineRoutes(router);
TautulliMediaDetailsRouter.defineRoutes(router);
// More/*
TautulliCheckForUpdatesRouter.defineRoutes(router);
TautulliSearchRouter.defineRoutes(router);
TautulliStatisticsRoute.defineRoute(router);
TautulliSyncedItemsRoute.defineRoute(router);
TautulliRecentlyAddedRoute.defineRoute(router);
TautulliGraphsRouter.defineRoutes(router);
TautulliLibrariesRouter.defineRoutes(router);
TautulliSyncedItemsRouter.defineRoutes(router);
TautulliStatisticsRouter.defineRoutes(router);
TautulliRecentlyAddedRouter.defineRoutes(router);
// Logs
TautulliLogsRouter.defineRoutes(router);
TautulliLogsLoginsRouter.defineRoutes(router);
TautulliLogsNewslettersRouter.defineRoutes(router);
TautulliLogsNotificationsRouter.defineRoutes(router);
TautulliLogsPlexMediaScannerRouter.defineRoutes(router);
TautulliLogsPlexMediaServerRouter.defineRoutes(router);
TautulliLogsTautulliRouter.defineRoutes(router);
}
}

View File

@@ -22,6 +22,8 @@ class TautulliState extends ChangeNotifier {
}
resetProfile();
resetActivity();
resetUsers();
resetHistory();
notifyListeners();
}

View File

@@ -34,8 +34,5 @@ extension TautulliMediaDetailsSwitcherTypeExtension on TautulliMediaDetailsSwitc
@required BuildContext context,
@required TautulliMediaType mediaType,
@required int ratingKey
}) => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(ratingKey: ratingKey, mediaType: mediaType),
);
}) => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: ratingKey, mediaType: mediaType);
}

View File

@@ -23,7 +23,7 @@ class _State extends State<TautulliModule> {
onWillPop: _onWillPop,
child: Navigator(
key: Provider.of<TautulliState>(context, listen: false).rootNavigatorKey,
initialRoute: TautulliRoute.route(profile: widget.profile),
initialRoute: TautulliHomeRouter.route(profile: widget.profile),
onGenerateRoute: TautulliRouter.router.generator,
),
),

View File

@@ -80,4 +80,4 @@ class _State extends State<TautulliActivityRoute> with AutomaticKeepAliveClientM
buttonText: 'Refresh',
onTapHandler: () async => _refreshKey.currentState.show(),
);
}
}

View File

@@ -1,4 +1,3 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:tautulli/tautulli.dart';
import 'package:percent_indicator/linear_percent_indicator.dart';
@@ -48,24 +47,13 @@ class TautulliActivityTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
);
String _artworkPath(BuildContext context) {
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
switch(session.mediaType) {
case TautulliMediaType.EPISODE: return _state.getImageURLFromRatingKey(session.grandparentRatingKey);
case TautulliMediaType.TRACK: return _state.getImageURLFromRatingKey(session.parentRatingKey);
case TautulliMediaType.MOVIE:
case TautulliMediaType.LIVE:
default: return _state.getImageURLFromRatingKey(session.ratingKey);
}
}
Widget get _mediaInfo => Padding(
child: Container(
child: Column(
children: [
_title,
LSTitle(text: session.lsTitle, maxLines: 1),
_subtitle,
_transcodeDecision,
LSSubtitle(text: session.lsTranscodeDecision, maxLines: 1),
_user,
_progressBar,
],
@@ -78,17 +66,6 @@ class TautulliActivityTile extends StatelessWidget {
padding: EdgeInsets.all(_padding),
);
Widget get _title => LSTitle(
text: session.grandparentTitle == null || session.grandparentTitle.isEmpty
? session.parentTitle == null || session.parentTitle.isEmpty
? session.title == null || session.title.isEmpty
? 'Unknown Title'
: session.title
: session.parentTitle
: session.grandparentTitle,
maxLines: 1,
);
Widget get _subtitle => RichText(
text: TextSpan(
style: TextStyle(
@@ -113,60 +90,37 @@ class TautulliActivityTile extends StatelessWidget {
overflow: TextOverflow.fade,
);
Widget get _transcodeDecision {
String _value = '';
switch(session.transcodeDecision) {
case TautulliTranscodeDecision.TRANSCODE:
String _transcodeStatus = session.transcodeThrottled ? 'Throttled' : '${session.transcodeSpeed ?? 0.0}x';
_value = 'Transcoding ($_transcodeStatus)';
break;
case TautulliTranscodeDecision.DIRECT_PLAY:
case TautulliTranscodeDecision.COPY:
case TautulliTranscodeDecision.NULL: _value = session.transcodeDecision.name ?? 'Unknown'; break;
}
return LSSubtitle(text: _value, maxLines: 1);
}
Widget _poster(BuildContext context) => LSNetworkImage(
url: _artworkPath(context),
url: session.lsArtworkPath(context),
placeholder: 'assets/images/sonarr/noseriesposter.png',
height: _height,
width: _width,
headers: Provider.of<TautulliState>(context, listen: false).headers.cast<String, String>(),
);
Widget get _user {
IconData _icon;
switch(session.state) {
case TautulliSessionState.PAUSED: _icon = Icons.pause; break;
case TautulliSessionState.PLAYING: _icon = Icons.play_arrow; break;
case TautulliSessionState.BUFFERING:
default: _icon = Icons.compare_arrows; break;
}
return Row(
children: [
LSIcon(
icon: _icon,
size: Constants.UI_FONT_SIZE_SUBTITLE,
color: Colors.white70,
),
LSSubtitle(text: '\t${session.friendlyName}'),
],
);
}
Widget get _user => Row(
children: [
LSIcon(
icon: session.lsStateIcon,
size: Constants.UI_FONT_SIZE_SUBTITLE,
color: Colors.white70,
),
LSSubtitle(text: '\t${session.friendlyName}'),
],
);
Widget get _progressBar => Padding(
child: Stack(
children: [
LinearPercentIndicator(
percent: min(1.0, max(0, session.transcodeProgress/100)),
percent: session.lsTranscodeProgress,
padding: EdgeInsets.symmetric(horizontal: 2.0),
progressColor: LSColors.splash.withOpacity(0.30),
backgroundColor: Colors.transparent,
lineHeight: 4.0,
),
LinearPercentIndicator(
percent: min(1.0, max(0, session.progressPercent/100)),
percent: session.lsProgressPercent,
padding: EdgeInsets.symmetric(horizontal: 2.0),
progressColor: LSColors.accent,
backgroundColor: LSColors.accent.withOpacity(0.15),
@@ -177,8 +131,5 @@ class TautulliActivityTile extends StatelessWidget {
padding: EdgeInsets.only(top: _padding),
);
Future<void> _enterDetails(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliActivityDetailsRoute.route(sessionId: session.sessionId),
);
Future<void> _enterDetails(BuildContext context) async => TautulliActivityDetailsRouter.navigateTo(context, sessionId: session.sessionId);
}

View File

@@ -4,25 +4,61 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliActivityDetailsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/activity/details/:sessionid/:profile';
final String sessionId;
class TautulliActivityDetailsRouter {
static const String ROUTE_NAME = '/tautulli/activity/details/:sessionid';
static String route({ String profile, @required String sessionId }) {
if(profile == null) return '/tautulli/activity/details/$sessionId/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/activity/details/$sessionId/$profile';
}
static void defineRoute(Router router) => router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliActivityDetailsRoute(
sessionId: params['sessionid'][0]),
),
transitionType: LunaRouter.transitionType,
static Future<void> navigateTo(BuildContext context, {
@required String sessionId,
}) async => TautulliRouter.router.navigateTo(
context,
route(sessionId: sessionId),
);
TautulliActivityDetailsRoute({
static String route({
String profile,
@required String sessionId,
}) => [
ROUTE_NAME.replaceFirst(':sessionid', sessionId ?? '0'),
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
/// With profile defined
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliActivityDetailsRoute(
sessionId: params['sessionid'] != null && params['sessionid'].length != 0
? params['sessionid'][0] ?? '-1'
: '-1',
profile: params['profile'] != null && params['profile'].length != 0
? params['profile'][0]
: null,
)),
transitionType: LunaRouter.transitionType,
);
/// Without profile defined
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliActivityDetailsRoute(
sessionId: params['sessionid'] != null && params['sessionid'].length != 0
? params['sessionid'][0] ?? '-1'
: '-1',
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliActivityDetailsRouter._();
}
class _TautulliActivityDetailsRoute extends StatefulWidget {
final String profile;
final String sessionId;
_TautulliActivityDetailsRoute({
Key key,
@required this.profile,
@required this.sessionId,
}): super(key: key);
@@ -30,7 +66,7 @@ class TautulliActivityDetailsRoute extends StatefulWidget {
State<StatefulWidget> createState() => _State();
}
class _State extends State<TautulliActivityDetailsRoute> {
class _State extends State<_TautulliActivityDetailsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -66,7 +102,7 @@ class _State extends State<TautulliActivityDetailsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliActivityDetailsRoute',
'_TautulliActivityDetailsRoute',
'_body',
'Unable to pull Tautulli activity session',
snapshot.error,

View File

@@ -16,167 +16,48 @@ class TautulliActivityDetailsInformation extends StatelessWidget {
children: [
TautulliActivityTile(session: session, disableOnTap: true),
TautulliActivityDetailsTerminateSession(session: session),
..._metadataBlock,
..._playerBlock,
..._streamBlock,
_metadataBlock,
_playerBlock,
_streamBlock,
],
);
List<Widget> get _metadataBlock => _block(
Widget get _metadataBlock => LSTableBlock(
title: 'Metadata',
children: [
_title,
_content('year', session.year.toString()),
_duration,
_content('library', session.libraryName),
_content('user', session.friendlyName),
LSTableContent(title: 'title', body: session.lsFullTitle),
LSTableContent(title: 'year', body: session.year.toString()),
LSTableContent(title: 'duration', body: session.lsDuration),
LSTableContent(title: 'library', body: session.libraryName),
LSTableContent(title: 'user', body: session.friendlyName),
]
);
Widget get _title => _content(
'title',
[
if(session.title != null && session.title.isNotEmpty) '${session.title}',
if(session.parentTitle != null && session.parentTitle.isNotEmpty) '\n${session.parentTitle}',
if(session.grandparentTitle != null && session.grandparentTitle.isNotEmpty) '\n${session.grandparentTitle}',
].join()
);
Widget get _duration {
double _percent = session.progressPercent/100;
String _progress = Duration(seconds: (session.streamDuration.inSeconds*_percent).floor()).lsDuration_timestamp();
String _total = session.streamDuration.lsDuration_timestamp();
return _content('duration', '$_progress/$_total (${session.progressPercent}%)');
}
List<Widget> get _playerBlock => _block(
Widget get _playerBlock => LSTableBlock(
title: 'Player',
children: [
_content('location', session.ipAddress),
_content('device', session.device),
_content('platform', session.platform),
_content('product', session.product),
_content('player', session.player),
_content('version', session.platformVersion),
LSTableContent(title: 'location', body: session.ipAddress),
LSTableContent(title: 'device', body: session.device),
LSTableContent(title: 'platform', body: session.platform),
LSTableContent(title: 'product', body: session.product),
LSTableContent(title: 'player', body: session.player),
LSTableContent(title: 'version', body: session.platformVersion),
],
);
List<Widget> get _streamBlock => _block(
Widget get _streamBlock => LSTableBlock(
title: 'Stream',
children: [
_bandwidth,
_quality,
_stream,
_container,
_video,
_audio,
LSTableContent(title: 'bandwidth', body: session.lsBandwidth),
LSTableContent(title: 'quality', body: session.lsQuality),
LSTableContent(title: 'stream', body: session.lsStream),
LSTableContent(title: 'container', body: session.lsContainer),
session.streamVideoDecision != null && session.streamVideoDecision != TautulliTranscodeDecision.NULL
? LSTableContent(title: 'video', body: session.lsVideo)
: Container(),
session.streamAudioDecision != null && session.streamAudioDecision != TautulliTranscodeDecision.NULL
? LSTableContent(title: 'audio', body: session.lsAudio)
: Container(),
],
);
Widget get _bandwidth => _content('bandwidth', '${session.bandwidth.lsBytes_KilobytesToString(bytes: false, decimals: 1)}ps');
Widget get _quality => _content('quality', '${session.qualityProfile} (${session.streamBitrate.lsBytes_KilobytesToString(bytes: false, decimals: 1)}ps)');
Widget get _stream {
String _value = '';
switch(session.transcodeDecision) {
case TautulliTranscodeDecision.TRANSCODE:
String _transcodeStatus = session.transcodeThrottled ? 'Throttled' : '${session.transcodeSpeed ?? 0.0}x';
_value = 'Transcode ($_transcodeStatus)';
break;
case TautulliTranscodeDecision.COPY: _value = 'Direct Stream'; break;
case TautulliTranscodeDecision.DIRECT_PLAY: _value = 'Direct Play'; break;
case TautulliTranscodeDecision.NULL: _value = 'Unknown Transcode Decision'; break;
}
return _content('stream', _value);
}
Widget get _container => _content(
'container',
[
session.streamContainerDecision.name,
' (',
session.container.toUpperCase(),
if(session.streamContainerDecision != null && session.streamContainerDecision != TautulliTranscodeDecision.DIRECT_PLAY)
' ${Constants.TEXT_RARROW} ${session.streamContainer.toUpperCase()}',
')',
].join(),
);
Widget get _video => session.streamVideoDecision != null && session.streamVideoDecision != TautulliTranscodeDecision.NULL
? _content(
'video',
[
session.videoDecision.name,
' (',
session.videoCodec.toUpperCase(),
' ',
session.videoFullResolution,
if(session.transcodeVideoCodec.isNotEmpty) ' ${Constants.TEXT_RARROW} ',
if(session.transcodeVideoCodec.isNotEmpty) session.transcodeVideoCodec.toUpperCase(),
if(session.transcodeVideoCodec.isNotEmpty) ' ${session.streamVideoFullResolution}',
')',
].join(),
)
: Container();
Widget get _audio => session.streamAudioDecision != null && session.streamAudioDecision != TautulliTranscodeDecision.NULL
? _content(
'audio',
[
session.audioDecision.name,
' (',
session.audioCodec.toUpperCase(),
if(session.transcodeAudioCodec.isNotEmpty) ' ${Constants.TEXT_RARROW} ',
if(session.transcodeAudioCodec.isNotEmpty) session.transcodeAudioCodec.toUpperCase(),
')',
].join(),
)
: Container();
List<Widget> _block({
@required String title,
@required List<Widget> children,
}) => [
LSHeader(text: title),
LSCard(
child: Padding(
child: Column(
children: children,
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
),
];
Widget _content(String header, String body) => Padding(
child: Row(
children: [
Expanded(
child: Text(
header.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
);
}

View File

@@ -21,7 +21,7 @@ class TautulliActivityDetailsMetadata extends StatelessWidget {
if(snapshot.hasData) {
TautulliSession session = snapshot.data.sessions.firstWhere((element) => element.sessionId == sessionId, orElse: () => null);
if(session != null) return LSIconButton(
icon: Icons.info,
icon: Icons.info_outline,
onPressed: () async => _onPressed(context, session),
);
}
@@ -30,11 +30,5 @@ class TautulliActivityDetailsMetadata extends StatelessWidget {
),
);
Future<void> _onPressed(BuildContext context, TautulliSession session) => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: session.ratingKey,
mediaType: session.mediaType,
),
);
Future<void> _onPressed(BuildContext context, TautulliSession session) => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: session.ratingKey, mediaType: session.mediaType);
}

View File

@@ -50,4 +50,4 @@ class TautulliActivityDetailsTerminateSession extends StatelessWidget {
});
}
}
}
}

View File

@@ -30,8 +30,8 @@ class TautulliActivityDetailsUser extends StatelessWidget {
),
);
Future<void> _onPressed(BuildContext context, int userId) => TautulliRouter.router.navigateTo(
Future<void> _onPressed(BuildContext context, int userId) => TautulliUserDetailsRouter.navigateTo(
context,
TautulliUserDetailsRoute.route(userId: userId),
userId: userId,
);
}

View File

@@ -5,29 +5,56 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliCheckForUpdatesRoute extends StatefulWidget {
static const ROUTE_NAME = '/tautulli/checkforupdates/:profile';
class TautulliCheckForUpdatesRouter {
static const ROUTE_NAME = '/tautulli/more/checkforupdates';
static String route({ String profile }) {
if(profile == null) return '/tautulli/checkforupdates/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/checkforupdates/$profile';
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliCheckForUpdatesRoute(
profile: params['profile'] != null && params['profile'].length != 0
? params['profile'][0]
: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliCheckForUpdatesRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
}
static void defineRoute(Router router) => router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliCheckForUpdatesRoute()),
transitionType: LunaRouter.transitionType,
);
TautulliCheckForUpdatesRouter._();
}
class _TautulliCheckForUpdatesRoute extends StatefulWidget {
final String profile;
_TautulliCheckForUpdatesRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
}
class _State extends State<TautulliCheckForUpdatesRoute> {
class _State extends State<_TautulliCheckForUpdatesRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
// Tracks the initial load to ensure the futures have been initialized
bool _initialLoad = false;
Future<void> _refresh() async {
@@ -67,7 +94,7 @@ class _State extends State<TautulliCheckForUpdatesRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliCheckForUpdatesRoute',
'_TautulliCheckForUpdatesRoute',
'_body',
'Unable to fetch updates',
snapshot.error,

View File

@@ -3,29 +3,54 @@ import 'package:flutter/material.dart' hide Router;
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
class TautulliGraphsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/graphs/:profile';
class TautulliGraphsRouter {
static const String ROUTE_NAME = '/tautulli/more/graphs';
TautulliGraphsRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliGraphsRoute(
profile: params['profile'] != null && params['profile'].length != 0
? params['profile'][0]
: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliGraphsRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliGraphsRouter._();
}
class _TautulliGraphsRoute extends StatefulWidget {
final String profile;
_TautulliGraphsRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<TautulliGraphsRoute> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/graphs/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/graphs/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliGraphsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliGraphsRoute()),
transitionType: LunaRouter.transitionType,
);
State<_TautulliGraphsRoute> createState() => _State();
}
class _State extends State<TautulliGraphsRoute> {
class _State extends State<_TautulliGraphsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
PageController _pageController;

View File

@@ -34,4 +34,4 @@ class TautulliGraphsTypeButton extends StatelessWidget {
)
),
);
}
}

View File

@@ -50,9 +50,9 @@ class TautulliHistoryTile extends StatelessWidget {
child: Container(
child: Column(
children: [
_title,
LSTitle(text: history.lsTitle, maxLines: 1),
_subtitle,
_date,
LSSubtitle(text: history.lsDate),
_userInfo,
],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -65,11 +65,6 @@ class TautulliHistoryTile extends StatelessWidget {
),
);
Widget get _title => LSTitle(
text: history.header,
maxLines: 1,
);
Widget get _subtitle => RichText(
text: TextSpan(
style: TextStyle(
@@ -94,41 +89,28 @@ class TautulliHistoryTile extends StatelessWidget {
overflow: TextOverflow.fade,
);
Widget get _userInfo {
IconData _icon;
switch(history.watchedStatus) {
case TautulliWatchedStatus.UNWATCHED: _icon = Icons.radio_button_unchecked; break;
case TautulliWatchedStatus.PARTIALLY_WATCHED: _icon = Icons.radio_button_checked; break;
case TautulliWatchedStatus.WATCHED: _icon = Icons.check_circle; break;
}
return Row(
children: [
Padding(
child: LSIcon(
icon: _icon,
size: Constants.UI_FONT_SIZE_SUBHEADER,
),
padding: EdgeInsets.only(right: 6.0),
Widget get _userInfo => Row(
children: [
Padding(
child: LSIcon(
icon: history.lsWatchStatusIcon,
size: Constants.UI_FONT_SIZE_SUBHEADER,
),
Expanded(
child: LSSubtitle(
text: history.friendlyName ?? 'Unknown User',
maxLines: 1,
),
)
],
);
}
padding: EdgeInsets.only(right: 6.0),
),
Expanded(
child: LSSubtitle(
text: history.friendlyName ?? 'Unknown User',
maxLines: 1,
),
)
],
);
Widget get _date => LSSubtitle(text: DateTime.now().lsDateTime_ageString(history.date));
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
Future<void> _onTap(BuildContext context) async => TautulliHistoryDetailsRouter.navigateTo(
context,
TautulliHistoryDetailsRoute.route(
profile: LunaSeaDatabaseValue.ENABLED_PROFILE.data,
ratingKey: history.ratingKey,
sessionKey: history.sessionKey,
referenceId: history.referenceId,
),
ratingKey: history.ratingKey,
sessionKey: history.sessionKey,
referenceId: history.referenceId,
);
}

View File

@@ -5,14 +5,65 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliHistoryDetailsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/history/details/:ratingkey/:key/:value/:profile';
class TautulliHistoryDetailsRouter {
static const String ROUTE_NAME = '/tautulli/history/details/:ratingkey/:key/:value';
static Future<void> navigateTo(BuildContext context, {
@required int ratingKey,
int referenceId,
int sessionKey,
}) async => TautulliRouter.router.navigateTo(
context,
route(ratingKey: ratingKey, referenceId: referenceId, sessionKey: sessionKey),
);
static String route({
String profile,
@required int ratingKey,
int referenceId,
int sessionKey,
}) {
String _route = '/tautulli';
if(referenceId != null) _route = '/tautulli/history/details/$ratingKey/referenceid/$referenceId';
if(sessionKey != null) _route = '/tautulli/history/details/$ratingKey/sessionkey/$sessionKey';
return profile != null ? _route + '/$profile' : _route;
}
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliHistoryDetailsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
ratingKey: int.tryParse(params['ratingkey'][0]),
sessionKey: params['key'][0] == 'sessionkey' ? int.tryParse(params['value'][0]) : null,
referenceId: params['key'][0] == 'referenceid' ? int.tryParse(params['value'][0]) : null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliHistoryDetailsRoute(
profile: null,
ratingKey: int.tryParse(params['ratingkey'][0]),
sessionKey: params['key'][0] == 'sessionkey' ? int.tryParse(params['value'][0]) : null,
referenceId: params['key'][0] == 'referenceid' ? int.tryParse(params['value'][0]) : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliHistoryDetailsRouter._();
}
class _TautulliHistoryDetailsRoute extends StatefulWidget {
final String profile;
final int ratingKey;
final int sessionKey;
final int referenceId;
TautulliHistoryDetailsRoute({
_TautulliHistoryDetailsRoute({
Key key,
@required this.profile,
@required this.ratingKey,
this.sessionKey,
this.referenceId,
@@ -20,31 +71,9 @@ class TautulliHistoryDetailsRoute extends StatefulWidget {
@override
State<StatefulWidget> createState() => _State();
static String route({
@required String profile,
@required int ratingKey,
int referenceId,
int sessionKey,
}) {
String _route = '/tautulli/$profile';
if(referenceId != null) _route = '/tautulli/history/details/$ratingKey/referenceid/$referenceId/$profile';
if(sessionKey != null) _route = '/tautulli/history/details/$ratingKey/sessionkey/$sessionKey/$profile';
return _route;
}
static void defineRoute(Router router) => router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliHistoryDetailsRoute(
ratingKey: int.tryParse(params['ratingkey'][0]),
sessionKey: params['key'][0] == 'sessionkey' ? int.tryParse(params['value'][0]) : null,
referenceId: params['key'][0] == 'referenceid' ? int.tryParse(params['value'][0]) : null,
)),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliHistoryDetailsRoute> {
class _State extends State<_TautulliHistoryDetailsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -91,7 +120,7 @@ class _State extends State<TautulliHistoryDetailsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliHistoryDetailsRoute',
'_TautulliHistoryDetailsRoute',
'_shared',
'Unable to pull Tautulli history session',
snapshot.error,
@@ -114,5 +143,5 @@ class _State extends State<TautulliHistoryDetailsRoute> {
),
);
Widget get _unknown => LSGenericMessage(text: 'History Record Not Found');
Widget get _unknown => LSGenericMessage(text: 'History Not Found');
}

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliHistoryDetailsInformation extends StatelessWidget {
@@ -14,123 +15,40 @@ class TautulliHistoryDetailsInformation extends StatelessWidget {
@override
Widget build(BuildContext context) => LSListView(
children: [
..._metadataBlock,
..._sessionBlock,
..._playerBlock,
_metadataBlock,
_sessionBlock,
_playerBlock,
],
);
List<Widget> get _metadataBlock => _block(
Widget get _metadataBlock => LSTableBlock(
title: 'Metadata',
children: <Widget>[
_status,
_title,
_content('year', history.year.toString()),
_content('user', history.friendlyName),
children: [
LSTableContent(title: 'status', body: history.lsStatus),
LSTableContent(title: 'title', body: history.lsFullTitle),
LSTableContent(title: 'year', body: history.year.toString()),
LSTableContent(title: 'user', body: history.friendlyName),
],
);
List<Widget> get _sessionBlock => _block(
Widget get _sessionBlock => LSTableBlock(
title: 'Session',
children: <Widget>[
_state,
_content('date', DateFormat('yyyy-MM-dd').format(history.date)),
_content('started', history.date.lsDateTime_time),
_content('stopped', history.state == null ? history.stopped.lsDateTime_time : Constants.TEXT_EMDASH),
_content('paused', history.pausedCounter.lsDuration_fullTimestamp()),
children: [
LSTableContent(title: 'state', body: history.lsState),
LSTableContent(title: 'date', body: DateFormat('yyyy-MM-dd').format(history.date)),
LSTableContent(title: 'started', body: history.date.lsDateTime_time),
LSTableContent(title: 'stopped', body: history.state == null ? history.stopped.lsDateTime_time : Constants.TEXT_EMDASH),
LSTableContent(title: 'paused', body: history.pausedCounter.lsDuration_fullTimestamp()),
],
);
List<Widget> get _playerBlock => _block(
Widget get _playerBlock => LSTableBlock(
title: 'Player',
children: <Widget>[
_content('location', history.ipAddress),
_content('platform', history.platform),
_content('product', history.product),
_content('player', history.player),
//_content('quality', history.)
children: [
LSTableContent(title: 'location', body: history.ipAddress),
LSTableContent(title: 'platform', body: history.platform),
LSTableContent(title: 'product', body: history.product),
LSTableContent(title: 'player', body: history.player),
],
);
Widget get _status {
String _progress = 'Unknown';
if(history.watchedStatus != null) switch(history.watchedStatus) {
case TautulliWatchedStatus.UNWATCHED: _progress = 'Incompleted'; break;
case TautulliWatchedStatus.PARTIALLY_WATCHED: _progress = 'Partially Completed'; break;
case TautulliWatchedStatus.WATCHED: _progress = 'Completed'; break;
}
return _content(
'status',
_progress,
);
}
Widget get _state {
String _status = 'Finished';
if(history.state != null) switch(history.state) {
case TautulliSessionState.PLAYING: _status = 'Playing'; break;
case TautulliSessionState.PAUSED: _status = 'Paused'; break;
case TautulliSessionState.BUFFERING: _status = 'Buffering'; break;
case TautulliSessionState.NULL: _status = 'Finished'; break;
}
return _content(
'state',
_status,
);
}
Widget get _title => _content(
'title',
[
if(history.title != null && history.title.isNotEmpty) '${history.title}',
if(history.parentTitle != null && history.parentTitle.isNotEmpty) '\n${history.parentTitle}',
if(history.grandparentTitle != null && history.grandparentTitle.isNotEmpty) '\n${history.grandparentTitle}',
].join()
);
List<Widget> _block({
@required String title,
@required List<Widget> children,
}) => [
LSHeader(text: title),
LSCard(
child: Padding(
child: Column(
children: children,
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
),
];
Widget _content(String header, String body) => Padding(
child: Row(
children: [
Expanded(
child: Text(
header.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
);
}

View File

@@ -26,7 +26,7 @@ class TautulliHistoryDetailsMetadata extends StatelessWidget {
return false;
}, orElse: () => null);
if(_record != null) return LSIconButton(
icon: Icons.info,
icon: Icons.info_outline,
onPressed: () async => _onPressed(context, _record),
);
}
@@ -34,11 +34,5 @@ class TautulliHistoryDetailsMetadata extends StatelessWidget {
},
);
Future<void> _onPressed(BuildContext context, TautulliHistoryRecord record) => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: record.ratingKey,
mediaType: record.mediaType,
),
);
Future<void> _onPressed(BuildContext context, TautulliHistoryRecord record) => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: record.ratingKey, mediaType: record.mediaType);
}

View File

@@ -34,8 +34,8 @@ class TautulliHistoryDetailsUser extends StatelessWidget {
},
);
Future<void> _onPressed(BuildContext context, int userId) => TautulliRouter.router.navigateTo(
Future<void> _onPressed(BuildContext context, int userId) => TautulliUserDetailsRouter.navigateTo(
context,
TautulliUserDetailsRoute.route(userId: userId),
userId: userId,
);
}

View File

@@ -8,8 +8,7 @@ import 'package:tautulli/tautulli.dart';
class TautulliIPAddressDetailsRouter {
static const String ROUTE_NAME = '/tautulli/ipaddress/:ipaddress';
static Future<void> navigateTo({
@required BuildContext context,
static Future<void> navigateTo(BuildContext context, {
@required String ip,
}) async => TautulliRouter.router.navigateTo(
context,
@@ -117,9 +116,7 @@ class _State extends State<_TautulliIPAddressRoute> {
Widget _list(TautulliGeolocationInfo geolocation, TautulliWHOISInfo whois) => LSListView(
children: [
LSHeader(text: 'Location Details'),
TautulliIPAddressDetailsGeolocationTile(geolocation: geolocation),
LSHeader(text: 'Connection Details'),
TautulliIPAddressDetailsWHOISTile(whois: whois),
],
);

View File

@@ -11,51 +11,16 @@ class TautulliIPAddressDetailsGeolocationTile extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) => LSCard(
child: Padding(
child: Column(
children: [
_content('country', geolocation.country),
_content('region', geolocation.region),
_content('city', geolocation.city),
_content('postal', geolocation.postalCode),
_content('timezone', geolocation.timezone),
_content('latitude', '${geolocation.latitude}'),
_content('longitude', '${geolocation.longitude}'),
],
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
);
Widget _content(String header, String body) => Padding(
child: Row(
children: [
Expanded(
child: Text(
header.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
Widget build(BuildContext context) => LSTableBlock(
title: 'Location Details',
children: [
LSTableContent(title: 'country', body: geolocation.country),
LSTableContent(title: 'region', body: geolocation.region),
LSTableContent(title: 'city', body: geolocation.city),
LSTableContent(title: 'postal', body: geolocation.postalCode),
LSTableContent(title: 'timezone', body: geolocation.timezone),
LSTableContent(title: 'latitude', body: '${geolocation.latitude}'),
LSTableContent(title: 'longitude', body: '${geolocation.longitude}'),
],
);
}

View File

@@ -11,55 +11,23 @@ class TautulliIPAddressDetailsWHOISTile extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) => LSCard(
child: Padding(
child: Column(
children: [
_content('host', whois.host),
...whois.subnets.fold<List<Widget>>([], (list, subnet) {
list.add(_content('isp', [
subnet.description,
'\n\n${subnet.address}',
'\n${subnet.city}, ${subnet.state}',
'\n${subnet.postalCode}',
'\n${subnet.country}',
].join()));
return list;
}),
],
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
Widget build(BuildContext context) => LSTableBlock(
title: 'Connection Details',
children: [
LSTableContent(title: 'host', body: whois.host),
...whois.subnets.fold<List<LSTableContent>>([], (list, subnet) {
list.add(LSTableContent(
title: 'isp',
body: [
subnet.description,
'\n\n${subnet.address}',
'\n${subnet.city}, ${subnet.state}',
'\n${subnet.postalCode}',
'\n${subnet.country}',
].join(),
));
return list;
}),
],
);
Widget _content(String header, String body) => Padding(
child: Row(
children: [
Expanded(
child: Text(
header.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
);
}
}

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLibrariesRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/libraries/:profile';
class TautulliLibrariesRouter {
static const String ROUTE_NAME = '/tautulli/libraries/list';
TautulliLibrariesRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLibrariesRouter._();
}
class _TautulliLibrariesRoute extends StatefulWidget {
final String profile;
_TautulliLibrariesRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<TautulliLibrariesRoute> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/libraries/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/libraries/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLibrariesRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLibrariesRoute()),
transitionType: LunaRouter.transitionType,
);
State<_TautulliLibrariesRoute> createState() => _State();
}
class _State extends State<TautulliLibrariesRoute> {
class _State extends State<_TautulliLibrariesRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLibrariesRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLibrariesRoute',
'_TautulliLibrariesRoute',
'_body',
'Unable to fetch Tautulli libraries table',
snapshot.error,

View File

@@ -48,8 +48,5 @@ class TautulliLibrariesLibraryTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) => TautulliLibrariesDetailsRouter.navigateTo(
context: context,
sectionId: library.sectionId,
);
Future<void> _onTap(BuildContext context) => TautulliLibrariesDetailsRouter.navigateTo(context, sectionId: library.sectionId);
}

View File

@@ -6,16 +6,13 @@ import 'package:lunasea/modules/tautulli.dart';
class TautulliLibrariesDetailsRouter {
static const String ROUTE_NAME = '/tautulli/libraries/details/:sectionid';
/// Static function to navigate to this route
static Future<void> navigateTo({
@required BuildContext context,
static Future<void> navigateTo(BuildContext context, {
@required int sectionId,
}) async => TautulliRouter.router.navigateTo(
context,
route(sectionId: sectionId),
);
/// Given the parameters, generates a String for routing to the route
static String route({
String profile,
@required int sectionId,
@@ -24,9 +21,7 @@ class TautulliLibrariesDetailsRouter {
if(profile != null) '/$profile',
].join();
/// Given a Fluro router, defines the routes and handlers
static void defineRoutes(Router router) {
/// With profile defined
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesDetailsRoute(
@@ -39,7 +34,6 @@ class TautulliLibrariesDetailsRouter {
)),
transitionType: LunaRouter.transitionType,
);
/// Without profile defined
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLibrariesDetailsRoute(

View File

@@ -83,21 +83,11 @@ class _State extends State<TautulliLibrariesDetailsInformation> with AutomaticKe
}) {
return LSListView(
children: [
..._details(library),
..._watchTimeStats(watchTimeStats),
TautulliLibrariesDetailsInformationDetails(library: library),
TautulliLibrariesDetailsInformationGlobalStats(watchtime: watchTimeStats),
],
);
}
Widget get _unknown => LSGenericMessage(text: 'Library Not Found');
List<Widget> _details(TautulliTableLibrary library) => <Widget>[
LSHeader(text: 'Details'),
TautulliLibrariesDetailsInformationDetails(library: library),
];
List<Widget> _watchTimeStats(List<TautulliLibraryWatchTimeStats> watchTimeStats) => <Widget>[
LSHeader(text: 'Global Stats'),
TautulliLibrariesDetailsInformationGlobalStats(watchtime: watchTimeStats),
];
}

View File

@@ -11,20 +11,16 @@ class TautulliLibrariesDetailsInformationDetails extends StatelessWidget {
}) : super(key: key);
@override
Widget build(BuildContext context) => LSCard(
child: Padding(
child: Column(
children: [
_content('name', library.sectionName),
if(library.count != null) _content(_count(library.count), '${library.count} ${_count(library.count)}'),
if(library.parentCount != null) _content(_parentCount(library.parentCount), '${library.parentCount} ${_parentCount(library.parentCount)}'),
if(library.childCount != null) _content(_childCount(library.childCount), '${library.childCount} ${_childCount(library.childCount)}'),
_content('last played', library.lastPlayed),
_content('', DateTime.now().lsDateTime_ageString(library.lastAccessed)),
]
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
Widget build(BuildContext context) => LSTableBlock(
title: 'Details',
children: [
LSTableContent(title: 'name', body: library.sectionName),
if(library.count != null) LSTableContent(title: _count(library.count), body: '${library.count} ${_count(library.count)}'),
if(library.parentCount != null) LSTableContent(title: _parentCount(library.parentCount), body: '${library.parentCount} ${_parentCount(library.parentCount)}'),
if(library.childCount != null) LSTableContent(title: _childCount(library.childCount), body: '${library.childCount} ${_childCount(library.childCount)}'),
LSTableContent(title: 'last played', body: library.lastPlayed),
LSTableContent(title: '', body: DateTime.now().lsDateTime_ageString(library.lastAccessed)),
],
);
String _count(int value) {
@@ -59,35 +55,4 @@ class TautulliLibrariesDetailsInformationDetails extends StatelessWidget {
default: return 'Unknown';
}
}
Widget _content(String header, String body) => Padding(
child: Row(
children: [
Expanded(
child: Text(
header.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
);
}

View File

@@ -9,62 +9,29 @@ class TautulliLibrariesDetailsInformationGlobalStats extends StatelessWidget {
Key key,
@required this.watchtime,
}) : super(key: key);
@override
Widget build(BuildContext context) => LSCard(
child: Padding(
child: Column(
children: List.generate(
watchtime.length,
(index) => _content(
_globalStatsTitle(watchtime[index].queryDays),
_globalStatsContent(watchtime[index].totalPlays, watchtime[index].totalTime),
),
),
Widget build(BuildContext context) => LSTableBlock(
title: 'Global Stats',
children: List.generate(
watchtime.length,
(index) => LSTableContent(
title: _title(watchtime[index].queryDays),
body: _body(watchtime[index].totalPlays, watchtime[index].totalTime),
),
padding: EdgeInsets.symmetric(vertical: 8.0),
),
);
String _globalStatsTitle(int days) {
String _title(int days) {
if(days == 0) return 'All Time';
if(days == 1) return '24 Hours';
return '$days Days';
}
String _globalStatsContent(int plays, Duration duration) {
String _body(int plays, Duration duration) {
String _plays = plays == 1 ? '1 Play': '$plays Plays';
return '$_plays\n${duration.lsDuration_fullTimestamp()}';
}
Widget _content(String header, String body) => Padding(
child: Row(
children: [
Expanded(
child: Text(
header.toUpperCase(),
textAlign: TextAlign.end,
style: TextStyle(
color: Colors.white70,
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 2,
),
Container(width: 16.0, height: 0.0),
Expanded(
child: Text(
body,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Constants.UI_FONT_SIZE_SUBTITLE,
),
),
flex: 5,
),
],
crossAxisAlignment: CrossAxisAlignment.start,
),
padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0),
);
}
}

View File

@@ -66,5 +66,5 @@ class _State extends State<TautulliLibrariesDetailsUserStats> with AutomaticKeep
);
}
Widget get _noStatsFound => LSGenericMessage(text: 'No User Stats Found');
Widget get _noStatsFound => LSGenericMessage(text: 'No User Found');
}

View File

@@ -25,7 +25,7 @@ class TautulliLibrariesDetailsUserStatsTile extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
),
onTap: () async => _enterDetails(context),
onTap: () async => _onPressed(context),
),
);
@@ -94,8 +94,8 @@ class TautulliLibrariesDetailsUserStatsTile extends StatelessWidget {
],
);
Future<void> _enterDetails(BuildContext context) async => TautulliRouter.router.navigateTo(
Future<void> _onPressed(BuildContext context) => TautulliUserDetailsRouter.navigateTo(
context,
TautulliUserDetailsRoute.route(userId: user.userId),
userId: user.userId,
);
}

View File

@@ -3,30 +3,54 @@ import 'package:flutter/material.dart' hide Router;
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
class TautulliLogsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/:profile';
class TautulliLogsRouter {
static const String ROUTE_NAME = '/tautulli/logs/list';
TautulliLogsRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsRouter._();
}
class _TautulliLogsRoute extends StatefulWidget {
final String profile;
_TautulliLogsRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsRoute()),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliLogsRoute> {
class _State extends State<_TautulliLogsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
@override
Widget build(BuildContext context) => Scaffold(
key: _scaffoldKey,

View File

@@ -14,8 +14,5 @@ class TautulliLogsLoginsTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsLoginsRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsLoginsRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliLogsNewslettersTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsNewslettersRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsNewslettersRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliLogsNotificationsTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsNotificationsRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsNotificationsRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliLogsPlexMediaScannerTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsPlexMediaScannerRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsPlexMediaScannerRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliLogsPlexMediaServerTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsPlexMediaServerRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsPlexMediaServerRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliLogsTautulliTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsTautulliRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsTautulliRouter.navigateTo(context);
}

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLogsLoginsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/logins/:profile';
class TautulliLogsLoginsRouter {
static const String ROUTE_NAME = '/tautulli/logs/logins';
TautulliLogsLoginsRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsLoginsRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsLoginsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsLoginsRouter._();
}
class _TautulliLogsLoginsRoute extends StatefulWidget {
final String profile;
_TautulliLogsLoginsRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/logins/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/logins/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsLoginsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsLoginsRoute()),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliLogsLoginsRoute> {
class _State extends State<_TautulliLogsLoginsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLogsLoginsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLogsLoginsRoute',
'_TautulliLogsLoginsRoute',
'_body',
'Unable to fetch Tautulli login logs',
snapshot.error,

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLogsNewslettersRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/newsletters/:profile';
class TautulliLogsNewslettersRouter {
static const String ROUTE_NAME = '/tautulli/logs/newsletters';
TautulliLogsNewslettersRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsNewslettersRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsNewslettersRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsNewslettersRouter._();
}
class _TautulliLogsNewslettersRoute extends StatefulWidget {
final String profile;
_TautulliLogsNewslettersRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/newsletters/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/newsletters/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsNewslettersRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsNewslettersRoute()),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliLogsNewslettersRoute> {
class _State extends State<_TautulliLogsNewslettersRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLogsNewslettersRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLogsNewslettersRoute',
'_TautulliLogsNewslettersRoute',
'_body',
'Unable to fetch Tautulli newsletter logs',
snapshot.error,

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLogsNotificationsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/notifications/:profile';
class TautulliLogsNotificationsRouter {
static const String ROUTE_NAME = '/tautulli/logs/notifications';
TautulliLogsNotificationsRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsNotificationsRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsNotificationsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsNotificationsRouter._();
}
class _TautulliLogsNotificationsRoute extends StatefulWidget {
final String profile;
_TautulliLogsNotificationsRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/notifications/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/notifications/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsNotificationsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsNotificationsRoute()),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliLogsNotificationsRoute> {
class _State extends State<_TautulliLogsNotificationsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLogsNotificationsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLogsNotificationsRoute',
'_TautulliLogsNotificationsRoute',
'_body',
'Unable to fetch Tautulli notification logs',
snapshot.error,

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLogsPlexMediaScannerRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/plexmediascanner/:profile';
class TautulliLogsPlexMediaScannerRouter {
static const String ROUTE_NAME = '/tautulli/logs/plexmediascanner';
TautulliLogsPlexMediaScannerRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaScannerRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaScannerRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsPlexMediaScannerRouter._();
}
class _TautulliLogsPlexMediaScannerRoute extends StatefulWidget {
final String profile;
_TautulliLogsPlexMediaScannerRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/plexmediascanner/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/plexmediascanner/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsPlexMediaScannerRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsPlexMediaScannerRoute()),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliLogsPlexMediaScannerRoute> {
class _State extends State<_TautulliLogsPlexMediaScannerRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLogsPlexMediaScannerRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLogsPlexMediaScannerRoute',
'_TautulliLogsPlexMediaScannerRoute',
'_body',
'Unable to fetch Tautulli plex media scanner logs',
snapshot.error,

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLogsPlexMediaServerRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/plexmediaserver/:profile';
class TautulliLogsPlexMediaServerRouter {
static const String ROUTE_NAME = '/tautulli/logs/plexmediaserver';
TautulliLogsPlexMediaServerRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaServerRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsPlexMediaServerRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsPlexMediaServerRouter._();
}
class _TautulliLogsPlexMediaServerRoute extends StatefulWidget {
final String profile;
_TautulliLogsPlexMediaServerRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/plexmediaserver/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/plexmediaserver/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsPlexMediaServerRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsPlexMediaServerRoute()),
transitionType: LunaRouter.transitionType
);
}
class _State extends State<TautulliLogsPlexMediaServerRoute> {
class _State extends State<_TautulliLogsPlexMediaServerRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLogsPlexMediaServerRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLogsPlexMediaServerRoute',
'_TautulliLogsPlexMediaServerRoute',
'_body',
'Unable to fetch Tautulli plex media server logs',
snapshot.error,

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliLogsTautulliRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/logs/tautulli/:profile';
class TautulliLogsTautulliRouter {
static const String ROUTE_NAME = '/tautulli/logs/tautulli';
TautulliLogsTautulliRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliLogsTautulliRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliLogsTautulliRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliLogsTautulliRouter._();
}
class _TautulliLogsTautulliRoute extends StatefulWidget {
final String profile;
_TautulliLogsTautulliRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/logs/tautulli/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/logs/tautulli/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliLogsTautulliRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliLogsTautulliRoute()),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliLogsTautulliRoute> {
class _State extends State<_TautulliLogsTautulliRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliLogsTautulliRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliLogsTautulliRoute',
'_TautulliLogsTautulliRoute',
'_body',
'Unable to fetch Tautulli Tautulli logs',
snapshot.error,

View File

@@ -4,40 +4,79 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliMediaDetailsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/media/details/:type/:ratingkey/:profile';
class TautulliMediaDetailsRouter {
static const String ROUTE_NAME = '/tautulli/media/details/:mediatype/:ratingkey';
static Future<void> navigateTo(BuildContext context, {
@required int ratingKey,
@required TautulliMediaType mediaType,
}) async => TautulliRouter.router.navigateTo(
context,
route(ratingKey: ratingKey, mediaType: mediaType),
);
static String route({
@required int ratingKey,
@required TautulliMediaType mediaType,
String profile,
}) => [
ROUTE_NAME
.replaceFirst(':mediatype', mediaType?.value ?? 'mediatype')
.replaceFirst(':ratingkey', ratingKey.toString()),
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliMediaDetailsRoute(
profile: null,
ratingKey: params['ratingkey'] != null && params['ratingkey'].length != 0
? int.tryParse(params['ratingkey'][0])
: null,
mediaType: params['mediatype'] != null && params['mediatype'].length != 0
? TautulliMediaType.NULL.from(params['mediatype'][0])
: null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliMediaDetailsRoute(
profile: params['profile'] != null && params['profile'].length != 0
? params['profile'][0]
: null,
ratingKey: params['ratingkey'] != null && params['ratingkey'].length != 0
? int.tryParse(params['ratingkey'][0])
: null,
mediaType: params['mediatype'] != null && params['mediatype'].length != 0
? TautulliMediaType.NULL.from(params['mediatype'][0])
: null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliMediaDetailsRouter._();
}
class _TautulliMediaDetailsRoute extends StatefulWidget {
final String profile;
final int ratingKey;
final TautulliMediaType mediaType;
TautulliMediaDetailsRoute({
_TautulliMediaDetailsRoute({
Key key,
@required this.profile,
@required this.ratingKey,
@required this.mediaType,
}) : super(key: key);
@override
State<TautulliMediaDetailsRoute> createState() => _State();
static String route({
String profile,
@required int ratingKey,
@required TautulliMediaType mediaType,
}) {
if(profile == null) return '/tautulli/media/details/${mediaType.value}/$ratingKey/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/media/details/${mediaType.value}/$ratingKey/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliMediaDetailsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliMediaDetailsRoute(
ratingKey: int.tryParse(params['ratingkey'][0]),
mediaType: TautulliMediaType.NULL.from(params['type'][0]),
)),
transitionType: LunaRouter.transitionType,
);
State<_TautulliMediaDetailsRoute> createState() => _State();
}
class _State extends State<TautulliMediaDetailsRoute> {
class _State extends State<_TautulliMediaDetailsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
PageController _pageController;

View File

@@ -14,8 +14,5 @@ class TautulliMoreCheckForUpdatesTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliCheckForUpdatesRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliCheckForUpdatesRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliMoreGraphsTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliGraphsRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliGraphsRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliMoreLibrariesTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLibrariesRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLibrariesRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliMoreLogsTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliLogsRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliLogsRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliMoreRecentlyAddedTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliRecentlyAddedRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliRecentlyAddedRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliMoreStatisticsTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliStatisticsRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliStatisticsRouter.navigateTo(context);
}

View File

@@ -14,8 +14,5 @@ class TautulliMoreSyncedItemsTile extends StatelessWidget {
onTap: () async => _onTap(context),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliSyncedItemsRoute.route(),
);
Future<void> _onTap(BuildContext context) async => TautulliSyncedItemsRouter.navigateTo(context);
}

View File

@@ -5,29 +5,54 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliRecentlyAddedRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/recentlyadded/:profile';
class TautulliRecentlyAddedRouter {
static const String ROUTE_NAME = '/tautulli/recentlyadded/list';
TautulliRecentlyAddedRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({
String profile,
}) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliRecentlyAddedRoute(profile: null)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliRecentlyAddedRoute(
profile: params['profile'] != null && params['profile'].length != 0
? params['profile'][0]
: null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliRecentlyAddedRouter._();
}
class _TautulliRecentlyAddedRoute extends StatefulWidget {
final String profile;
_TautulliRecentlyAddedRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<TautulliRecentlyAddedRoute> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/recentlyadded/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/recentlyadded/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliRecentlyAddedRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliRecentlyAddedRoute()),
transitionType: LunaRouter.transitionType,
);
State<_TautulliRecentlyAddedRoute> createState() => _State();
}
class _State extends State<TautulliRecentlyAddedRoute> {
class _State extends State<_TautulliRecentlyAddedRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +88,7 @@ class _State extends State<TautulliRecentlyAddedRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliRecentlyAddedRoute',
'_TautulliRecentlyAddedRoute',
'_body',
'Unable to fetch Tautulli recently added',
snapshot.error,

View File

@@ -127,11 +127,5 @@ class TautulliRecentlyAddedContentTile extends StatelessWidget {
Widget get _library => LSSubtitle(text: recentlyAdded.libraryName);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: recentlyAdded.ratingKey,
mediaType: recentlyAdded.mediaType,
),
);
Future<void> _onTap(BuildContext context) async => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: recentlyAdded.ratingKey, mediaType: recentlyAdded.mediaType);
}

View File

@@ -96,11 +96,5 @@ class TautulliSearchResultTile extends StatelessWidget {
),
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: result.ratingKey,
mediaType: mediaType,
),
);
Future<void> _onTap(BuildContext context) async => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: result.ratingKey, mediaType: mediaType);
}

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliStatisticsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/statistics/:profile';
class TautulliStatisticsRouter {
static const String ROUTE_NAME = '/tautulli/statistics/list';
TautulliStatisticsRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliStatisticsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliStatisticsRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliStatisticsRouter._();
}
class _TautulliStatisticsRoute extends StatefulWidget {
final String profile;
_TautulliStatisticsRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<TautulliStatisticsRoute> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/statistics/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/statistics/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliStatisticsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliStatisticsRoute()),
transitionType: LunaRouter.transitionType,
);
State<_TautulliStatisticsRoute> createState() => _State();
}
class _State extends State<TautulliStatisticsRoute> {
class _State extends State<_TautulliStatisticsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -69,7 +92,7 @@ class _State extends State<TautulliStatisticsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliStatisticsRoute',
'_TautulliStatisticsRoute',
'_body',
'Unable to fetch Tautulli statistics',
snapshot.error,

View File

@@ -116,11 +116,5 @@ class TautulliStatisticsMediaTile extends StatelessWidget {
overflow: TextOverflow.fade,
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: data['rating_key'],
mediaType: mediaType,
),
);
Future<void> _onTap(BuildContext context) async => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: data['rating_key'], mediaType: mediaType);
}

View File

@@ -94,11 +94,5 @@ class TautulliStatisticsRecentlyWatchedTile extends StatelessWidget {
overflow: TextOverflow.fade,
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: data['rating_key'],
mediaType: TautulliMediaType.NULL.from(data['media_type']),
),
);
}
Future<void> _onTap(BuildContext context) async => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: data['rating_key'], mediaType: TautulliMediaType.NULL.from(data['media_type']));
}

View File

@@ -119,10 +119,7 @@ class TautulliStatisticsUserTile extends StatelessWidget {
type: SNACKBAR_TYPE.failure,
);
} else {
TautulliRouter.router.navigateTo(
context,
TautulliUserDetailsRoute.route(userId: _user.userId),
);
TautulliUserDetailsRouter.navigateTo(context, userId: _user.userId);
}
}
}

View File

@@ -5,29 +5,52 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliSyncedItemsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/synceditems/:profile';
class TautulliSyncedItemsRouter {
static const String ROUTE_NAME = '/tautulli/synceditems/list';
TautulliSyncedItemsRoute({
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliSyncedItemsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliSyncedItemsRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliSyncedItemsRouter._();
}
class _TautulliSyncedItemsRoute extends StatefulWidget {
final String profile;
_TautulliSyncedItemsRoute({
Key key,
@required this.profile,
}): super(key: key);
@override
State<TautulliSyncedItemsRoute> createState() => _State();
static String route({ String profile }) {
if(profile == null) return '/tautulli/synceditems/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/synceditems/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliSyncedItemsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliSyncedItemsRoute()),
transitionType: LunaRouter.transitionType,
);
State<_TautulliSyncedItemsRoute> createState() => _State();
}
class _State extends State<TautulliSyncedItemsRoute> {
class _State extends State<_TautulliSyncedItemsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
@@ -63,7 +86,7 @@ class _State extends State<TautulliSyncedItemsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliSyncedItemsRoute',
'_TautulliSyncedItemsRoute',
'_body',
'Unable to fetch Tautulli synced items',
snapshot.error,

View File

@@ -60,11 +60,5 @@ class TautulliSyncedItemTile extends StatelessWidget {
padContent: true,
);
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliMediaDetailsRoute.route(
ratingKey: syncedItem.ratingKey,
mediaType: TautulliMediaType.NULL.from(syncedItem.metadataType),
),
);
Future<void> _onTap(BuildContext context) async => TautulliMediaDetailsRouter.navigateTo(context, ratingKey: syncedItem.ratingKey, mediaType: TautulliMediaType.NULL.from(syncedItem.metadataType));
}

View File

@@ -3,25 +3,56 @@ import 'package:flutter/material.dart' hide Router;
import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
class TautulliRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/:profile';
class TautulliHomeRouter {
static const ROUTE_NAME = '/tautulli';
@override
State<TautulliRoute> createState() => _State();
static Future<void> navigateTo(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
route(),
);
static String route({ String profile }) {
if(profile == null) return '/tautulli/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/$profile';
static String route({ String profile }) => [
ROUTE_NAME,
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
/// With profile defined
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliHomeRoute(
profile: params['profile'] != null && params['profile'].length != 0
? params['profile'][0]
: null,
)),
transitionType: LunaRouter.transitionType,
);
/// Without profile defined
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliHomeRoute(
profile: null,
)),
transitionType: LunaRouter.transitionType,
);
}
static void defineRoute(Router router) => router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliRoute()),
transitionType: LunaRouter.transitionType,
);
TautulliHomeRouter._();
}
class _State extends State<TautulliRoute> {
class _TautulliHomeRoute extends StatefulWidget {
final String profile;
_TautulliHomeRoute({
Key key,
@required this.profile,
}) : super(key: key);
@override
State<_TautulliHomeRoute> createState() => _State();
}
class _State extends State<_TautulliHomeRoute> {
PageController _pageController;
@override

View File

@@ -87,7 +87,7 @@ class _State extends State<TautulliNavigationBar> {
? LSColors.accent
: Colors.white,
),
showBadge: state.navigationIndex != 0 && snapshot.hasData && snapshot.data.streamCount > 0,
showBadge: state.enabled && state.navigationIndex != 0 && snapshot.hasData && snapshot.data.streamCount > 0,
),
),
),

View File

@@ -104,8 +104,5 @@ class TautulliUserTile extends StatelessWidget {
],
);
Future<void> _enterDetails(BuildContext context) async => TautulliRouter.router.navigateTo(
context,
TautulliUserDetailsRoute.route(userId: user.userId),
);
Future<void> _enterDetails(BuildContext context) async => TautulliUserDetailsRouter.navigateTo(context, userId: user.userId);
}

View File

@@ -5,33 +5,61 @@ import 'package:lunasea/core.dart';
import 'package:lunasea/modules/tautulli.dart';
import 'package:tautulli/tautulli.dart';
class TautulliUserDetailsRoute extends StatefulWidget {
static const String ROUTE_NAME = '/tautulli/users/details/:userid/:profile';
final int userId;
class TautulliUserDetailsRouter {
static const String ROUTE_NAME = '/tautulli/users/details/:userid';
TautulliUserDetailsRoute({
static Future<void> navigateTo(BuildContext context, {
@required int userId,
}) async => TautulliRouter.router.navigateTo(
context,
route(userId: userId),
);
static String route({
String profile,
@required int userId,
}) => [
ROUTE_NAME.replaceFirst(':userid', userId.toString()),
if(profile != null) '/$profile',
].join();
static void defineRoutes(Router router) {
router.define(
ROUTE_NAME + '/:profile',
handler: Handler(handlerFunc: (context, params) => _TautulliUserDetailsRoute(
profile: params['profile'] != null && params['profile'].length != 0 ? params['profile'][0] : null,
userId: int.tryParse(params['userid'][0]) ?? -1,
)),
transitionType: LunaRouter.transitionType,
);
router.define(
ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => _TautulliUserDetailsRoute(
profile: null,
userId: int.tryParse(params['userid'][0]) ?? -1,
)),
transitionType: LunaRouter.transitionType,
);
}
TautulliUserDetailsRouter._();
}
class _TautulliUserDetailsRoute extends StatefulWidget {
final int userId;
final String profile;
_TautulliUserDetailsRoute({
Key key,
@required this.userId,
@required this.profile,
}): super(key: key);
@override
State<StatefulWidget> createState() => _State();
static String route({ String profile, @required int userId }) {
if(profile == null) return '/tautulli/users/details/$userId/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}';
return '/tautulli/users/details/$userId/$profile';
}
static void defineRoute(Router router) => router.define(
TautulliUserDetailsRoute.ROUTE_NAME,
handler: Handler(handlerFunc: (context, params) => TautulliUserDetailsRoute(
userId: int.tryParse(params['userid'][0]),
)),
transitionType: LunaRouter.transitionType,
);
}
class _State extends State<TautulliUserDetailsRoute> {
class _State extends State<_TautulliUserDetailsRoute> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
PageController _pageController;
@@ -82,7 +110,7 @@ class _State extends State<TautulliUserDetailsRoute> {
if(snapshot.hasError) {
if(snapshot.connectionState != ConnectionState.waiting) {
Logger.error(
'TautulliUserDetailsRoute',
'_TautulliUserDetailsRoute',
'_body',
'Unable to pull Tautulli user table',
snapshot.error,

Some files were not shown because too many files have changed in this diff Show More