From 3ecb8e8a3c61586de6164bda20dbf6991e4d0871 Mon Sep 17 00:00:00 2001 From: Jagandeep Brar Date: Fri, 11 Sep 2020 10:18:22 -0500 Subject: [PATCH] [TestFlight] v4.0.0+400010 (#254) NEW - [Tautulli/Graphs] Implemented graphs page - [Tautulli/Updates] Implemented check for updates page TWEAKS - None FIXES - None --- lib/core/constants.dart | 2 + lib/core/ui/colors.dart | 9 + lib/modules/tautulli/core.dart | 1 + lib/modules/tautulli/core/graphs.dart | 3 + .../tautulli/core/graphs/bar_graph.dart | 80 +++++++ lib/modules/tautulli/core/graphs/graph.dart | 102 +++++++++ .../tautulli/core/graphs/line_graph.dart | 98 +++++++++ lib/modules/tautulli/core/router.dart | 1 + lib/modules/tautulli/core/state/local.dart | 206 +++++++++++++++++- .../modules/activity_details/route.dart | 7 +- .../activity_details/widgets/metadata.dart | 28 ++- .../modules/check_for_updates/route.dart | 98 +++++++++ .../modules/check_for_updates/widgets.dart | 2 + .../check_for_updates/widgets/pms_tile.dart | 60 +++++ .../widgets/tautulli_tile.dart | 65 ++++++ .../modules/graphs/widgets/type_button.dart | 2 +- .../modules/graphs_play_by_period/route.dart | 101 ++++----- .../graphs_play_by_period/widgets.dart | 5 +- .../widgets/daily_play_count_graph.dart | 59 +++++ .../widgets/play_count_by_date_graph.dart | 160 -------------- .../play_count_by_day_of_week_graph.dart | 59 +++++ .../play_count_by_top_platforms_graph.dart | 59 +++++ .../play_count_by_top_users_graph.dart | 59 +++++ .../widgets/plays_by_month_graph.dart | 106 ++------- .../graphs_stream_information/route.dart | 62 +++++- .../graphs_stream_information/widgets.dart | 5 + .../daily_stream_type_breakdown_graph.dart | 59 +++++ ...y_count_by_platform_stream_type_graph.dart | 59 +++++ ...play_count_by_source_resolution_graph.dart | 59 +++++ ...play_count_by_stream_resolution_graph.dart | 61 ++++++ .../play_count_by_user_stream_type_graph.dart | 59 +++++ .../modules/history_details/route.dart | 5 +- .../more/widgets/check_for_updates_tile.dart | 9 +- .../modules/more/widgets/graphs_tile.dart | 13 +- .../recently_added/widgets/content_tile.dart | 2 +- .../statistics/widgets/media_tile.dart | 2 +- .../widgets/recently_watched_tile.dart | 2 +- .../tautulli/widgets/global_settings.dart | 8 +- pubspec.lock | 6 +- pubspec.yaml | 8 +- 40 files changed, 1440 insertions(+), 351 deletions(-) create mode 100644 lib/modules/tautulli/core/graphs.dart create mode 100644 lib/modules/tautulli/core/graphs/bar_graph.dart create mode 100644 lib/modules/tautulli/core/graphs/graph.dart create mode 100644 lib/modules/tautulli/core/graphs/line_graph.dart create mode 100644 lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart create mode 100644 lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart create mode 100644 lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart delete mode 100644 lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_date_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart create mode 100644 lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart diff --git a/lib/core/constants.dart b/lib/core/constants.dart index e550b17e..e7a4e7fd 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -46,6 +46,7 @@ class Constants { static const TEXT_BULLET = '•'; static const TEXT_RARROW = '→'; static const TEXT_LARROW = '←'; + static const TEXT_ELLIPSIS = '…'; //UI static const UI_ELEVATION = 0.0; static const UI_CARD_MARGIN = EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0); @@ -57,6 +58,7 @@ class Constants { static const UI_FONT_SIZE_SUBHEADER = 12.0; static const UI_FONT_SIZE_TITLE = 16.0; static const UI_FONT_SIZE_SUBTITLE = 13.0; + static const UI_FONT_SIZE_GRAPH_LEGEND = 10.0; //General static const EMPTY_MAP = {}; static const EMPTY_LIST = []; diff --git a/lib/core/ui/colors.dart b/lib/core/ui/colors.dart index 8a111023..5f4645a1 100644 --- a/lib/core/ui/colors.dart +++ b/lib/core/ui/colors.dart @@ -18,4 +18,13 @@ class LSColors { static Color list(int i) { return Constants.LIST_COLOR_ICONS[i%Constants.LIST_COLOR_ICONS.length]; } + + static Color graph(int i) { + switch(i) { + case 0: return LSColors.accent; + case 1: return LSColors.purple; + case 2: return LSColors.blue; + default: return LSColors.list(i); + } + } } diff --git a/lib/modules/tautulli/core.dart b/lib/modules/tautulli/core.dart index c2a19ad4..995da03f 100644 --- a/lib/modules/tautulli/core.dart +++ b/lib/modules/tautulli/core.dart @@ -2,6 +2,7 @@ export 'core/constants.dart'; export 'core/database.dart'; export 'core/dialogs.dart'; export 'core/extensions.dart'; +export 'core/graphs.dart'; export 'core/router.dart'; export 'core/state.dart'; export 'core/types.dart'; diff --git a/lib/modules/tautulli/core/graphs.dart b/lib/modules/tautulli/core/graphs.dart new file mode 100644 index 00000000..7fc80bbc --- /dev/null +++ b/lib/modules/tautulli/core/graphs.dart @@ -0,0 +1,3 @@ +export 'graphs/bar_graph.dart'; +export 'graphs/graph.dart'; +export 'graphs/line_graph.dart'; diff --git a/lib/modules/tautulli/core/graphs/bar_graph.dart b/lib/modules/tautulli/core/graphs/bar_graph.dart new file mode 100644 index 00000000..aaa311f6 --- /dev/null +++ b/lib/modules/tautulli/core/graphs/bar_graph.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliBarGraphHelper { + static const int BAR_COUNT = 7; + static const double BAR_WIDTH = 30.0; + + TautulliBarGraphHelper._(); + + static List barGroups(BuildContext context, TautulliGraphData data) => List.generate( + data.categories.take(BAR_COUNT).length, + (cIndex) => BarChartGroupData( + x: cIndex, + barRods: [ + BarChartRodData( + y: data.series.fold(0, (value, data) => value+data.data[cIndex]), + width: BAR_WIDTH, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(Constants.UI_BORDER_RADIUS/3), + topRight: Radius.circular(Constants.UI_BORDER_RADIUS/3), + ), + rodStackItems: List.generate( + data.series.length, + (sIndex) => BarChartRodStackItem( + _fromY(cIndex, sIndex, data.series), + _toY(cIndex, sIndex, data.series), + LSColors.graph(sIndex), + ), + ), + ), + ], + ), + ); + + static BarTouchData barTouchData(BuildContext context, TautulliGraphData data) => BarTouchData( + enabled: true, + touchTooltipData: BarTouchTooltipData( + tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LSColors.primary, + tooltipRoundedRadius: Constants.UI_BORDER_RADIUS, + tooltipPadding: EdgeInsets.all(8.0), + maxContentWidth: MediaQuery.of(context).size.width/1.25, + fitInsideVertically: true, + fitInsideHorizontally: true, + getTooltipItem: (group, gIndex, rod, rIndex) { + String _header = '${data.categories[gIndex]}\n\n'; + String _body = ''; + for(int i=0; i(context, listen: false).graphYAxis == TautulliGraphYAxis.PLAYS + ? (_number?.truncate() ?? 0).toString() + : Duration(seconds: _number?.truncate() ?? 0).lsDuration_fullTimestamp(); + _body += '$_value: $_text\n'; + } + return BarTooltipItem( + (_header + _body).trim(), + TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBHEADER, + ), + ); + }, + ), + ); + + static double _fromY( + int cIndex, + int sIndex, + List series, + ) => series.take(sIndex).fold(0, (value, data) => value+data.data[cIndex]); + + static double _toY( + int cIndex, + int sIndex, + List series, + ) => series.take(sIndex+1).fold(0, (value, data) => value+data.data[cIndex]); +} diff --git a/lib/modules/tautulli/core/graphs/graph.dart b/lib/modules/tautulli/core/graphs/graph.dart new file mode 100644 index 00000000..69cb72ce --- /dev/null +++ b/lib/modules/tautulli/core/graphs/graph.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphHelper { + static const GRAPH_HEIGHT = 225.0; + static const LEGEND_HEIGHT = 26.0; + static const DEFAULT_MAX_TITLE_LENGTH = 5; + + TautulliGraphHelper._(); + + static BarChartAlignment chartAlignment() => BarChartAlignment.spaceEvenly; + + static FlGridData gridData() => FlGridData(show: false); + + static FlBorderData borderData() => FlBorderData( + show: true, + border: Border.all(color: Colors.white12), + ); + + static FlTitlesData titlesData(TautulliGraphData data, { + int maxTitleLength = DEFAULT_MAX_TITLE_LENGTH, + bool titleOverFlowShowEllipsis = true, + }) => FlTitlesData( + leftTitles: SideTitles(showTitles: false), + rightTitles: SideTitles(showTitles: false), + topTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles( + showTitles: true, + margin: 8.0, + reservedSize: 8.0, + getTitles: (value) => data.categories[value.truncate()].length > maxTitleLength+1 + ? [ + data.categories[value.truncate()].substring(0, maxTitleLength).toUpperCase(), + if(titleOverFlowShowEllipsis) Constants.TEXT_ELLIPSIS, + ].join() + : data.categories[value.truncate()].toUpperCase(), + textStyle: TextStyle( + color: Colors.white30, + fontSize: Constants.UI_FONT_SIZE_GRAPH_LEGEND, + ), + ), + ); + + static Widget createLegend(List data) => Container( + child: Row( + children: List.generate( + data.length, + (index) => Padding( + child: Row( + children: [ + Padding( + child: Container( + height: Constants.UI_FONT_SIZE_GRAPH_LEGEND, + width: Constants.UI_FONT_SIZE_GRAPH_LEGEND, + decoration: BoxDecoration( + color: LSColors.graph(index), + borderRadius: BorderRadius.circular(8.0), + ), + ), + padding: EdgeInsets.only(right: 6.0), + ), + Text( + data[index].name, + style: TextStyle( + fontSize: Constants.UI_FONT_SIZE_GRAPH_LEGEND, + color: LSColors.graph(index), + ), + ), + ], + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + ), + padding: EdgeInsets.symmetric(horizontal: 6.0), + ), + ), + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + ), + height: LEGEND_HEIGHT, + ); + + static Widget get loadingContainer => LSCard( + child: Container( + height: GRAPH_HEIGHT+LEGEND_HEIGHT, + child: LSLoader(), + ), + ); + + static Widget get errorContainer => LSCard( + child: Container( + height: GRAPH_HEIGHT+LEGEND_HEIGHT, + alignment: Alignment.center, + child: LSIconButton( + icon: Icons.error, + iconSize: 60.0, + color: Colors.white12, + ), + ), + ); +} diff --git a/lib/modules/tautulli/core/graphs/line_graph.dart b/lib/modules/tautulli/core/graphs/line_graph.dart new file mode 100644 index 00000000..a1611d1a --- /dev/null +++ b/lib/modules/tautulli/core/graphs/line_graph.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:intl/intl.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliLineGraphHelper { + TautulliLineGraphHelper._(); + + static FlTitlesData titlesData(TautulliGraphData data) => FlTitlesData( + leftTitles: SideTitles(showTitles: false), + rightTitles: SideTitles(showTitles: false), + topTitles: SideTitles(showTitles: false), + bottomTitles: SideTitles( + showTitles: true, + margin: 8.0, + reservedSize: 8.0, + getTitles: (value) => DateTime.tryParse((data.categories[value.truncate()])) != null + ? DateFormat('dd').format(DateTime.parse((data.categories[value.truncate()])))?.toString() + : '??', + textStyle: TextStyle( + color: Colors.white30, + fontSize: Constants.UI_FONT_SIZE_GRAPH_LEGEND, + ), + ), + ); + + static List lineBarsData(TautulliGraphData data) => List.generate( + data.series.length, + (sIndex) => LineChartBarData( + isCurved: true, + isStrokeCapRound: true, + barWidth: 3.0, + colors: [LSColors.graph(sIndex)], + spots: List.generate( + data.series[sIndex].data.length, + (dIndex) => FlSpot(dIndex.toDouble(), data.series[sIndex].data[dIndex].toDouble()), + ), + belowBarData: BarAreaData( + show: true, + colors: [LSColors.graph(sIndex).withOpacity(0.20)], + ), + dotData: FlDotData( + show: true, + getDotPainter: (FlSpot spot, double xPercentage, LineChartBarData bar, int index) => FlDotCirclePainter( + radius: 2.50, + strokeColor: bar.colors[0], + color: bar.colors[0], + ), + ), + ), + ); + + static LineTouchData lineTouchData(BuildContext context, TautulliGraphData data) => LineTouchData( + enabled: true, + touchTooltipData: LineTouchTooltipData( + tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LSColors.primary, + tooltipRoundedRadius: Constants.UI_BORDER_RADIUS, + tooltipPadding: EdgeInsets.all(8.0), + maxContentWidth: MediaQuery.of(context).size.width/1.25, + fitInsideVertically: true, + fitInsideHorizontally: true, + getTooltipItems: (List spots) => List.generate( + spots.length, + (index) => LineTooltipItem( + [ + '${data.series[spots[index].barIndex].name}: ', + Provider.of(context, listen: false).graphYAxis == TautulliGraphYAxis.PLAYS + ? '${spots[index]?.y?.truncate() ?? 0}' + : '${Duration(seconds: spots[index]?.y?.truncate() ?? 0).lsDuration_fullTimestamp()}', + ].join().trim(), + TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBHEADER, + ), + ), + ), + ), + getTouchedSpotIndicator: (bar, data) => List.generate( + data.length, + (index) => TouchedSpotIndicatorData( + FlLine( + strokeWidth: 3.0, + color: bar.colors[0].withOpacity(0.50), + ), + FlDotData( + show: true, + getDotPainter: (FlSpot spot, double xPercentage, LineChartBarData bar, int index) => FlDotCirclePainter( + radius: 5.0, + strokeColor: bar.colors[0], + color: bar.colors[0], + ), + ), + ), + ), + ); +} diff --git a/lib/modules/tautulli/core/router.dart b/lib/modules/tautulli/core/router.dart index 399b878d..c15aa8c0 100644 --- a/lib/modules/tautulli/core/router.dart +++ b/lib/modules/tautulli/core/router.dart @@ -21,6 +21,7 @@ class TautulliRouter { TautulliLogsPlexMediaServerRoute.defineRoute(router); TautulliLogsTautulliRoute.defineRoute(router); // Other/More + TautulliCheckForUpdatesRoute.defineRoute(router); TautulliGraphsRoute.defineRoute(router); TautulliStatisticsRoute.defineRoute(router); TautulliSyncedItemsRoute.defineRoute(router); diff --git a/lib/modules/tautulli/core/state/local.dart b/lib/modules/tautulli/core/state/local.dart index 903a2a30..92dece99 100644 --- a/lib/modules/tautulli/core/state/local.dart +++ b/lib/modules/tautulli/core/state/local.dart @@ -251,17 +251,17 @@ class TautulliLocalState extends ChangeNotifier { /// GRAPHS /// ////////////// - Future _playCountByDateGraph; - Future get playCountByDateGraph => _playCountByDateGraph; - set playCountByDateGraph(Future playCountByDateGraph) { - assert(playCountByDateGraph != null); - _playCountByDateGraph = playCountByDateGraph; + Future _dailyPlayCountGraph; + Future get dailyPlayCountGraph => _dailyPlayCountGraph; + set dailyPlayCountGraph(Future dailyPlayCountGraph) { + assert(dailyPlayCountGraph != null); + _dailyPlayCountGraph = dailyPlayCountGraph; notifyListeners(); } - void _resetPlayCountByDateGraph(BuildContext context) { + void resetDailyPlayCountGraph(BuildContext context) { TautulliState _state = Provider.of(context, listen: false); - if(_state.api != null) _playCountByDateGraph = _state.api.history.getPlaysByDate( + if(_state.api != null) _dailyPlayCountGraph = _state.api.history.getPlaysByDate( timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data, yAxis: _state.graphYAxis, ); @@ -276,7 +276,7 @@ class TautulliLocalState extends ChangeNotifier { notifyListeners(); } - void _resetPlaysByMonthGraph(BuildContext context) { + void resetPlaysByMonthGraph(BuildContext context) { TautulliState _state = Provider.of(context, listen: false); if(_state.api != null) _playsByMonthGraph = _state.api.history.getPlaysPerMonth( timeRange: TautulliDatabaseValue.GRAPHS_MONTHS.data, @@ -285,10 +285,192 @@ class TautulliLocalState extends ChangeNotifier { notifyListeners(); } - void resetAllPlayPeriodGraphs(BuildContext context) { - _resetPlayCountByDateGraph(context); - _resetPlaysByMonthGraph(context); + Future _playCountByDayOfWeekGraph; + Future get playCountByDayOfWeekGraph => _playCountByDayOfWeekGraph; + set playCountByDayOfWeekGraph(Future playCountByDayOfWeekGraph) { + assert(playCountByDayOfWeekGraph != null); + _playCountByDayOfWeekGraph = playCountByDayOfWeekGraph; + notifyListeners(); } - void resetAllStreamInfoGraphs(BuildContext context) {} + void resetPlayCountByDayOfWeekGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountByDayOfWeekGraph = _state.api.history.getPlaysByDayOfWeek( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByTopPlatformsGraph; + Future get playCountByTopPlatformsGraph => _playCountByTopPlatformsGraph; + set playCountByTopPlatformsGraph(Future playCountByTopPlatformsGraph) { + assert(playCountByTopPlatformsGraph != null); + _playCountByTopPlatformsGraph = playCountByTopPlatformsGraph; + notifyListeners(); + } + + void resetPlayCountByTopPlatformsGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountByTopPlatformsGraph = _state.api.history.getPlaysByTopTenPlatforms( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByTopUsersGraph; + Future get playCountByTopUsersGraph => _playCountByTopUsersGraph; + set playCountByTopUsersGraph(Future playCountByTopUsersGraph) { + assert(playCountByTopUsersGraph != null); + _playCountByTopUsersGraph = playCountByTopUsersGraph; + notifyListeners(); + } + + void resetPlayCountByTopUsersGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountByTopUsersGraph = _state.api.history.getPlaysByTopTenUsers( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + void resetAllPlayPeriodGraphs(BuildContext context) { + resetDailyPlayCountGraph(context); + resetPlaysByMonthGraph(context); + resetPlayCountByDayOfWeekGraph(context); + resetPlayCountByTopPlatformsGraph(context); + resetPlayCountByTopUsersGraph(context); + } + + Future _dailyStreamTypeBreakdownGraph; + Future get dailyStreamTypeBreakdownGraph => _dailyStreamTypeBreakdownGraph; + set dailyStreamTypeBreakdownGraph(Future dailyStreamTypeBreakdownGraph) { + assert(dailyStreamTypeBreakdownGraph != null); + _dailyStreamTypeBreakdownGraph = dailyStreamTypeBreakdownGraph; + notifyListeners(); + } + + void resetDailyStreamTypeBreakdownGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _dailyStreamTypeBreakdownGraph = _state.api.history.getPlaysByStreamType( + timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + Future _playCountBySourceResolutionGraph; + Future get playCountBySourceResolutionGraph => _playCountBySourceResolutionGraph; + set playCountBySourceResolutionGraph(Future playCountBySourceResolutionGraph) { + assert(playCountBySourceResolutionGraph != null); + _playCountBySourceResolutionGraph = playCountBySourceResolutionGraph; + notifyListeners(); + } + + void resetPlayCountBySourceResolutionGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountBySourceResolutionGraph = _state.api.history.getPlaysBySourceResolution( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByStreamResolutionGraph; + Future get playCountByStreamResolutionGraph => _playCountByStreamResolutionGraph; + set playCountByStreamResolutionGraph(Future playCountByStreamResolutionGraph) { + assert(playCountByStreamResolutionGraph != null); + _playCountByStreamResolutionGraph = playCountByStreamResolutionGraph; + notifyListeners(); + } + + void resetPlayCountByStreamResolutionGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountByStreamResolutionGraph = _state.api.history.getPlaysByStreamResolution( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByPlatformStreamTypeGraph; + Future get playCountByPlatformStreamTypeGraph => _playCountByPlatformStreamTypeGraph; + set playCountByPlatformStreamTypeGraph(Future playCountByPlatformStreamTypeGraph) { + assert(playCountByPlatformStreamTypeGraph != null); + _playCountByPlatformStreamTypeGraph = playCountByPlatformStreamTypeGraph; + notifyListeners(); + } + + void resetPlayCountByPlatformStreamTypeGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountByPlatformStreamTypeGraph = _state.api.history.getStreamTypeByTopTenPlatforms( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + Future _playCountByUserStreamTypeGraph; + Future get playCountByUserStreamTypeGraph => _playCountByUserStreamTypeGraph; + set playCountByUserStreamTypeGraph(Future playCountByUserStreamTypeGraph) { + assert(playCountByUserStreamTypeGraph != null); + _playCountByUserStreamTypeGraph = playCountByUserStreamTypeGraph; + notifyListeners(); + } + + void resetPlayCountByUserStreamTypeGraph(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _playCountByUserStreamTypeGraph = _state.api.history.getStreamTypeByTopTenUsers( + timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data, + yAxis: _state.graphYAxis, + ); + notifyListeners(); + } + + void resetAllStreamInformationGraphs(BuildContext context) { + resetDailyStreamTypeBreakdownGraph(context); + resetPlayCountBySourceResolutionGraph(context); + resetPlayCountByStreamResolutionGraph(context); + resetPlayCountByPlatformStreamTypeGraph(context); + resetPlayCountByUserStreamTypeGraph(context); + } + + /////////////// + /// UPDATES /// + /////////////// + + Future _updatePlexMediaServer; + Future get updatePlexMediaServer => _updatePlexMediaServer; + set updatePlexMediaServer(Future updatePlexMediaServer) { + assert(updatePlexMediaServer != null); + _updatePlexMediaServer = updatePlexMediaServer; + notifyListeners(); + } + + void resetUpdatePlexMediaServer(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _updatePlexMediaServer = _state.api.system.getPMSUpdate(); + notifyListeners(); + } + + Future _updateTautulli; + Future get updateTautulli => _updateTautulli; + set updateTautulli(Future updateTautulli) { + assert(updateTautulli != null); + _updateTautulli = updateTautulli; + notifyListeners(); + } + + void resetUpdateTautulli(BuildContext context) { + TautulliState _state = Provider.of(context, listen: false); + if(_state.api != null) _updateTautulli = _state.api.system.updateCheck(); + notifyListeners(); + } + + void resetAllUpdates(BuildContext context) { + resetUpdatePlexMediaServer(context); + resetUpdateTautulli(context); + } } diff --git a/lib/modules/tautulli/modules/activity_details/route.dart b/lib/modules/tautulli/modules/activity_details/route.dart index fc7eaa99..9b1b76bd 100644 --- a/lib/modules/tautulli/modules/activity_details/route.dart +++ b/lib/modules/tautulli/modules/activity_details/route.dart @@ -47,7 +47,12 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'Activity Details'); + Widget get _appBar => LSAppBar( + title: 'Activity Details', + actions: [ + TautulliActivityDetailsMetadata(sessionId: widget.sessionId), + ] + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, diff --git a/lib/modules/tautulli/modules/activity_details/widgets/metadata.dart b/lib/modules/tautulli/modules/activity_details/widgets/metadata.dart index be46c5f1..3e7d5380 100644 --- a/lib/modules/tautulli/modules/activity_details/widgets/metadata.dart +++ b/lib/modules/tautulli/modules/activity_details/widgets/metadata.dart @@ -1,11 +1,33 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; class TautulliActivityDetailsMetadata extends StatelessWidget { + final String sessionId; + + TautulliActivityDetailsMetadata({ + Key key, + @required this.sessionId, + }): super(key: key); + @override - Widget build(BuildContext context) => LSIconButton( - icon: Icons.info_outline, - onPressed: () async => _onPressed(context), + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.activity, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) return Container(); + if(snapshot.hasData) { + TautulliSession session = snapshot.data.sessions.firstWhere((element) => element.sessionId == sessionId, orElse: () => null); + if(session != null) return LSIconButton( + icon: Icons.info_outline, + onPressed: () async => _onPressed(context), + ); + } + return Container(); + }, + ), ); Future _onPressed(BuildContext context) => LSSnackBar( diff --git a/lib/modules/tautulli/modules/check_for_updates/route.dart b/lib/modules/tautulli/modules/check_for_updates/route.dart index e69de29b..5f889d92 100644 --- a/lib/modules/tautulli/modules/check_for_updates/route.dart +++ b/lib/modules/tautulli/modules/check_for_updates/route.dart @@ -0,0 +1,98 @@ +import 'package:fluro_fork/fluro_fork.dart'; +import 'package:flutter/material.dart' hide Router; +import 'package:flutter/scheduler.dart'; +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'; + + static String route({ String profile }) { + if(profile == null) return '/tautulli/checkforupdates/${LunaSeaDatabaseValue.ENABLED_PROFILE.data}'; + return '/tautulli/checkforupdates/$profile'; + } + + static void defineRoute(Router router) => router.define( + ROUTE_NAME, + handler: Handler(handlerFunc: (context, params) => TautulliCheckForUpdatesRoute()), + transitionType: LunaRouter.transitionType, + ); + + @override + State createState() => _State(); +} + +class _State extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + final GlobalKey _refreshKey = GlobalKey(); + + // Tracks the initial load to ensure the futures have been initialized + bool _initialLoad = false; + + Future _refresh() async { + TautulliLocalState _local = Provider.of(context, listen: false); + _local.resetAllUpdates(context); + setState(() => _initialLoad = true); + await Future.wait([ + _local.updatePlexMediaServer, + _local.updateTautulli, + ]); + } + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.scheduleFrameCallback((_) => _refresh()); + } + + @override + Widget build(BuildContext context) => Scaffold( + key: _scaffoldKey, + appBar: _appBar, + body: _initialLoad ? _body : LSLoader(), + ); + + Widget get _appBar => LSAppBar(title: 'Check for Updates'); + + Widget get _body => LSRefreshIndicator( + refreshKey: _refreshKey, + onRefresh: _refresh, + child: FutureBuilder( + future: Future.wait([ + Provider.of(context).updatePlexMediaServer, + Provider.of(context).updateTautulli, + ]), + builder: (context, AsyncSnapshot> snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliCheckForUpdatesRoute', + '_body', + 'Unable to fetch updates', + snapshot.error, + null, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return LSErrorMessage(onTapHandler: () async => _refreshKey.currentState.show()); + } + if(snapshot.hasData) return _list( + pms: snapshot.data[0], + tautulli: snapshot.data[1], + ); + return LSLoader(); + }, + ), + ); + + Widget _list({ + @required TautulliPMSUpdate pms, + @required TautulliUpdateCheck tautulli, + }) => LSListView( + children: [ + TautulliCheckForUpdatesPMSTile(update: pms), + TautulliCheckForUpdatesTautulliTile(update: tautulli), + ], + ); +} diff --git a/lib/modules/tautulli/modules/check_for_updates/widgets.dart b/lib/modules/tautulli/modules/check_for_updates/widgets.dart index e69de29b..04fcec89 100644 --- a/lib/modules/tautulli/modules/check_for_updates/widgets.dart +++ b/lib/modules/tautulli/modules/check_for_updates/widgets.dart @@ -0,0 +1,2 @@ +export 'widgets/pms_tile.dart'; +export 'widgets/tautulli_tile.dart'; diff --git a/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart b/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart new file mode 100644 index 00000000..a15598f9 --- /dev/null +++ b/lib/modules/tautulli/modules/check_for_updates/widgets/pms_tile.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliCheckForUpdatesPMSTile extends StatelessWidget { + final TautulliPMSUpdate update; + + TautulliCheckForUpdatesPMSTile({ + Key key, + @required this.update, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Plex Media Server'), + subtitle: _subtitle, + trailing: _trailing, + padContent: true, + ); + + Widget get _trailing => Column( + children: [ + LSIconButton( + icon: CustomIcons.plex, + color: LSColors.list(0), + ), + ], + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + ); + + Widget get _subtitle => RichText( + text: TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + if(!update.updateAvailable) TextSpan( + text: 'No Updates Available\n', + style: TextStyle( + color: LSColors.accent, + fontWeight: FontWeight.w600, + ), + ), + if(!update.updateAvailable) TextSpan(text: 'Current Version: ${update.version}'), + if(update.updateAvailable) TextSpan( + text: 'Update Available\n', + style: TextStyle( + color: LSColors.orange, + fontWeight: FontWeight.w600, + ), + ), + if(update.updateAvailable) TextSpan(text: 'Latest Version: ${update.version}'), + ], + ), + overflow: TextOverflow.fade, + maxLines: 3, + ); +} diff --git a/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart b/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart new file mode 100644 index 00000000..c4fadc95 --- /dev/null +++ b/lib/modules/tautulli/modules/check_for_updates/widgets/tautulli_tile.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:lunasea/core.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliCheckForUpdatesTautulliTile extends StatelessWidget { + final TautulliUpdateCheck update; + + TautulliCheckForUpdatesTautulliTile({ + Key key, + @required this.update, + }) : super(key: key); + + @override + Widget build(BuildContext context) => LSCardTile( + title: LSTitle(text: 'Tautulli'), + subtitle: _subtitle, + trailing: _trailing, + padContent: true, + ); + + Widget get _trailing => Column( + children: [ + LSIconButton( + icon: CustomIcons.tautulli, + color: LSColors.list(1), + ), + ], + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + ); + + Widget get _subtitle => RichText( + text: TextSpan( + style: TextStyle( + color: Colors.white70, + fontSize: Constants.UI_FONT_SIZE_SUBTITLE, + ), + children: [ + if(!update.update) TextSpan( + text: 'No Updates Available\n', + style: TextStyle( + color: LSColors.accent, + fontWeight: FontWeight.w600, + ), + ), + if(update.update) TextSpan( + text: 'Update Available\n', + style: TextStyle( + color: LSColors.orange, + fontWeight: FontWeight.w600, + ), + ), + if(update.update) TextSpan( + text: 'Current Version: ${update.currentRelease?? update.currentVersion?.substring(0, 7) ?? 'Unknown'}\n', + ), + if(update.update) TextSpan( + text: 'Latest Version: ${update.latestRelease?? update.latestVersion?.substring(0, 7) ?? 'Unknown'}\n', + ), + TextSpan(text: 'Install Type: ${update.installType}'), + ], + ), + overflow: TextOverflow.fade, + maxLines: 4, + ); +} diff --git a/lib/modules/tautulli/modules/graphs/widgets/type_button.dart b/lib/modules/tautulli/modules/graphs/widgets/type_button.dart index c604a9af..cd4573fb 100644 --- a/lib/modules/tautulli/modules/graphs/widgets/type_button.dart +++ b/lib/modules/tautulli/modules/graphs/widgets/type_button.dart @@ -15,7 +15,7 @@ class TautulliGraphsTypeButton extends StatelessWidget { onSelected: (value) { Provider.of(context, listen: false).graphYAxis = value; Provider.of(context, listen: false).resetAllPlayPeriodGraphs(context); - Provider.of(context, listen: false).resetAllStreamInfoGraphs(context); + Provider.of(context, listen: false).resetAllStreamInformationGraphs(context); }, itemBuilder: (context) => List>.generate( TautulliStatsType.values.length, diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/route.dart b/lib/modules/tautulli/modules/graphs_play_by_period/route.dart index 5109ca12..75a5880b 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/route.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/route.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:lunasea/core.dart'; import 'package:lunasea/modules/tautulli.dart'; -import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlayByPeriodRoute extends StatefulWidget { TautulliGraphsPlayByPeriodRoute({ @@ -16,7 +15,6 @@ class TautulliGraphsPlayByPeriodRoute extends StatefulWidget { class _State extends State with AutomaticKeepAliveClientMixin { final GlobalKey _scaffoldKey = GlobalKey(); final GlobalKey _refreshKey = GlobalKey(); - final String _placeholder = '<>'; @override bool get wantKeepAlive => true; @@ -31,7 +29,11 @@ class _State extends State with AutomaticKeepAl TautulliLocalState _state = Provider.of(context, listen: false); _state.resetAllPlayPeriodGraphs(context); await Future.wait([ - _state.playCountByDateGraph, + _state.dailyPlayCountGraph, + _state.playsByMonthGraph, + _state.playCountByDayOfWeekGraph, + _state.playCountByTopPlatformsGraph, + _state.playCountByTopUsersGraph, ]); } @@ -49,53 +51,52 @@ class _State extends State with AutomaticKeepAl onRefresh: _refresh, child: LSListView( children: [ - ..._dailyPlayCount, - ..._playsByMonth, - //..._playCountByDayOfWeek, - //..._playCountByTopPlatforms, - //..._playCountByTopUsers, + LSHeader( + text: 'Daily', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data} Days', + '\n\n', + 'The total play count or duration of television, movies, and music played per day.' + ].join(), + ), + TautulliGraphsDailyPlayCountGraph(), + LSHeader( + text: 'Monthly', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_MONTHS.data} Months', + '\n\n', + 'The combined total of television, movies, and music by month.', + ].join(), + ), + TautulliGraphsPlaysByMonthGraph(), + LSHeader( + text: 'By Day Of Week', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television, movies, and music played per day of the week.', + ].join(), + ), + TautulliGraphsPlayCountByDayOfWeekGraph(), + LSHeader( + text: 'By Top Platforms', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television, movies, and music played by the top most active platforms.', + ].join(), + ), + TautulliGraphsPlayCountByTopPlatformsGraph(), + LSHeader( + text: 'By Top Users', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television, movies, and music played by the top most active users.', + ].join(), + ), + TautulliGraphsPlayCountByTopUsersGraph(), ], ), ); - - List get _dailyPlayCount => [ - LSHeader( - text: _createTitle('Daily Play $_placeholder'), - subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data} Days', - ), - TautulliGraphsPlayCountByDateGraph(), - ]; - - List get _playsByMonth => [ - LSHeader( - text: 'Plays By Month', - subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_MONTHS.data} Months', - ), - TautulliGraphsPlaysByMonthGraph(), - ]; - - List get _playCountByDayOfWeek => [ - LSHeader( - text: _createTitle('Play $_placeholder By Day Of Week'), - subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', - ), - ]; - - List get _playCountByTopPlatforms => [ - LSHeader( - text: _createTitle('Play $_placeholder By Top Platforms'), - subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', - ), - ]; - - List get _playCountByTopUsers => [ - LSHeader( - text: _createTitle('Play $_placeholder By Top Users'), - subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', - ), - ]; - - String _createTitle(String title) => Provider.of(context).graphYAxis == TautulliGraphYAxis.PLAYS - ? title.replaceFirst(_placeholder, 'Count') - : title.replaceFirst(_placeholder, 'Duration'); -} \ No newline at end of file +} diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets.dart index ce111c5e..d7793726 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets.dart @@ -1,2 +1,5 @@ -export 'widgets/play_count_by_date_graph.dart'; +export 'widgets/daily_play_count_graph.dart'; +export 'widgets/play_count_by_day_of_week_graph.dart'; +export 'widgets/play_count_by_top_platforms_graph.dart'; +export 'widgets/play_count_by_top_users_graph.dart'; export 'widgets/plays_by_month_graph.dart'; diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart new file mode 100644 index 00000000..c0bdb34b --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/daily_play_count_graph.dart @@ -0,0 +1,59 @@ + +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsDailyPlayCountGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.dailyPlayCountGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsDailyPlayCountGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysByDate', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: LineChart( + LineChartData( + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliLineGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + lineBarsData: TautulliLineGraphHelper.lineBarsData(data), + lineTouchData: TautulliLineGraphHelper.lineTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_date_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_date_graph.dart deleted file mode 100644 index 9c701686..00000000 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_date_graph.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:fl_chart/fl_chart.dart'; -import 'package:intl/intl.dart'; -import 'package:lunasea/core.dart'; -import 'package:lunasea/modules/tautulli.dart'; -import 'package:tautulli/tautulli.dart'; - -class TautulliGraphsPlayCountByDateGraph extends StatelessWidget { - static const double _height = 250.0; - - @override - Widget build(BuildContext context) => Selector>( - selector: (_, state) => state.playCountByDateGraph, - builder: (context, future, _) => FutureBuilder( - future: future, - builder: (context, AsyncSnapshot snapshot) { - if(snapshot.hasError) { - if(snapshot.connectionState != ConnectionState.waiting) { - Logger.error( - 'TautulliGraphsPlayCountByDateGraph', - '_body', - 'Unable to fetch Tautulli graph data: getPlaysByDate', - snapshot.error, - StackTrace.current, - uploadToSentry: !(snapshot.error is DioError), - ); - } - return _error; - } - if(snapshot.hasData) return _graph(context, snapshot.data); - return _loading; - }, - ), - ); - - - Widget _graph(BuildContext context, TautulliGraphData data) { - return LSCard( - child: Container( - height: _height, - child: Padding( - child: LineChart( - LineChartData( - gridData: FlGridData(show: false), - titlesData: FlTitlesData( - leftTitles: SideTitles(showTitles: false), - rightTitles: SideTitles(showTitles: false), - topTitles: SideTitles(showTitles: false), - bottomTitles: SideTitles( - showTitles: true, - margin: 26.0, - rotateAngle: -90.0, - getTitles: (value) => DateTime.tryParse((data.categories[value.truncate()])) != null - ? DateFormat('MM/dd').format(DateTime.parse((data.categories[value.truncate()])))?.toString() - : '??/??', - textStyle: TextStyle( - color: Colors.white30, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: Colors.white12), - ), - lineBarsData: List.generate( - data.series.length, - (sIndex) => LineChartBarData( - isCurved: true, - isStrokeCapRound: true, - barWidth: 3.0, - colors: [LSColors.list(sIndex)], - spots: List.generate( - data.series[sIndex].data.length, - (dIndex) => FlSpot(dIndex.toDouble(), data.series[sIndex].data[dIndex].toDouble()), - ), - belowBarData: BarAreaData( - show: true, - colors: [LSColors.list(sIndex).withOpacity(0.20)], - ), - dotData: FlDotData( - show: true, - getDotPainter: (FlSpot spot, double xPercentage, LineChartBarData bar, int index) => FlDotCirclePainter( - radius: 2.50, - strokeColor: bar.colors[0], - color: bar.colors[0], - ), - ), - ), - ), - lineTouchData: LineTouchData( - enabled: true, - touchTooltipData: LineTouchTooltipData( - tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LSColors.primary, - tooltipRoundedRadius: Constants.UI_BORDER_RADIUS, - tooltipPadding: EdgeInsets.all(8.0), - maxContentWidth: MediaQuery.of(context).size.width/1.25, - fitInsideVertically: true, - fitInsideHorizontally: true, - getTooltipItems: (List spots) => List.generate( - spots.length, - (index) => LineTooltipItem( - [ - '${data.series[spots[index].barIndex].name}: ', - Provider.of(context, listen: false).graphYAxis == TautulliGraphYAxis.PLAYS - ? '${spots[index]?.y?.truncate() ?? 0}' - : '${Duration(seconds: spots[index]?.y?.truncate() ?? 0).lsDuration_fullTimestamp()}', - ].join().trim(), - TextStyle( - color: LSColors.list(index), - fontWeight: FontWeight.w600, - ), - ), - ), - ), - getTouchedSpotIndicator: (bar, data) => List.generate( - data.length, - (index) => TouchedSpotIndicatorData( - FlLine( - strokeWidth: 3.0, - color: bar.colors[0].withOpacity(0.50), - ), - FlDotData( - show: true, - getDotPainter: (FlSpot spot, double xPercentage, LineChartBarData bar, int index) => FlDotCirclePainter( - radius: 5.0, - strokeColor: bar.colors[0], - color: bar.colors[0], - ), - ), - ), - ), - ), - ), - ), - padding: EdgeInsets.fromLTRB(14.0, 14.0, 14.0, 2.0), - ), - ), - ); - } - - Widget get _loading => LSCard( - child: Container( - height: _height, - child: LSLoader(), - ), - ); - - Widget get _error => LSCard( - child: Container( - height: _height, - alignment: Alignment.center, - child: LSIconButton( - icon: Icons.error, - iconSize: 60.0, - color: Colors.white12, - ), - ), - ); -} diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart new file mode 100644 index 00000000..826115a0 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_day_of_week_graph.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountByDayOfWeekGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountByDayOfWeekGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountByDayOfWeekGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysByDayOfWeek', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data, maxTitleLength: 3, titleOverFlowShowEllipsis: false), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart new file mode 100644 index 00000000..f1978513 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_platforms_graph.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountByTopPlatformsGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountByTopPlatformsGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountByTopPlatformsGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysByTopTenPlatforms', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart new file mode 100644 index 00000000..fa760b56 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/play_count_by_top_users_graph.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountByTopUsersGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountByTopUsersGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountByTopUsersGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysByTopTenUsers', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart index d162052b..c409b8f0 100644 --- a/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart +++ b/lib/modules/tautulli/modules/graphs_play_by_period/widgets/plays_by_month_graph.dart @@ -5,8 +5,6 @@ import 'package:lunasea/modules/tautulli.dart'; import 'package:tautulli/tautulli.dart'; class TautulliGraphsPlaysByMonthGraph extends StatelessWidget { - static const double _height = 250.0; - @override Widget build(BuildContext context) => Selector>( selector: (_, state) => state.playsByMonthGraph, @@ -24,104 +22,38 @@ class TautulliGraphsPlaysByMonthGraph extends StatelessWidget { uploadToSentry: !(snapshot.error is DioError), ); } - return _error; + return TautulliGraphHelper.errorContainer; } if(snapshot.hasData) return _graph(context, snapshot.data); - return _loading; + return TautulliGraphHelper.loadingContainer; }, ), ); - Widget _graph(BuildContext context, TautulliGraphData data) { return LSCard( - child: Container( - height: _height, - child: Padding( - child: BarChart( - BarChartData( - alignment: BarChartAlignment.spaceEvenly, - gridData: FlGridData(show: false), - titlesData: FlTitlesData( - leftTitles: SideTitles(showTitles: false), - rightTitles: SideTitles(showTitles: false), - topTitles: SideTitles(showTitles: false), - bottomTitles: SideTitles( - showTitles: true, - reservedSize: 8.0, - getTitles: (value) => data.categories[value.truncate()].substring(0, 3).toUpperCase(), - textStyle: TextStyle( - color: Colors.white30, - fontSize: Constants.UI_FONT_SIZE_SUBTITLE, - ), - ), - ), - borderData: FlBorderData( - show: true, - border: Border.all(color: Colors.white12), - ), - barGroups: List.generate( - data.categories.length, - (cIndex) => BarChartGroupData( - x: cIndex, - barRods: List.generate( - data.series.length, - (sIndex) => BarChartRodData( - width: 11.0, - y: data.series[sIndex].data[cIndex].toDouble(), - borderRadius: BorderRadius.circular(Constants.UI_BORDER_RADIUS), - color: LSColors.list(sIndex), - ), - ), - ), - ), - barTouchData: BarTouchData( - enabled: true, - touchTooltipData: BarTouchTooltipData( - tooltipBgColor: LunaSeaDatabaseValue.THEME_AMOLED.data ? Colors.black : LSColors.primary, - tooltipRoundedRadius: Constants.UI_BORDER_RADIUS, - tooltipPadding: EdgeInsets.all(8.0), - maxContentWidth: MediaQuery.of(context).size.width/1.25, - fitInsideVertically: true, - fitInsideHorizontally: true, - getTooltipItem: (group, gIndex, rod, rIndex) => BarTooltipItem( - [ - '${data.series[rIndex].name}: ', - Provider.of(context, listen: false).graphYAxis == TautulliGraphYAxis.PLAYS - ? '${data.series[rIndex].data[gIndex] ?? 0}' - : '${Duration(seconds: data.series[rIndex].data[gIndex] ?? 0).lsDuration_fullTimestamp()}', - ].join().trim(), - TextStyle( - color: LSColors.list(rIndex), - fontWeight: FontWeight.w600, - ), - ), + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data, maxTitleLength: 3, titleOverFlowShowEllipsis: false), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), ), ), + padding: EdgeInsets.all(14.0), ), ), - padding: EdgeInsets.all(14.0), - ), + TautulliGraphHelper.createLegend(data.series), + ], ), ); } - - Widget get _loading => LSCard( - child: Container( - height: _height, - child: LSLoader(), - ), - ); - - Widget get _error => LSCard( - child: Container( - height: _height, - alignment: Alignment.center, - child: LSIconButton( - icon: Icons.error, - iconSize: 60.0, - color: Colors.white12, - ), - ), - ); } diff --git a/lib/modules/tautulli/modules/graphs_stream_information/route.dart b/lib/modules/tautulli/modules/graphs_stream_information/route.dart index 3bab8e25..2eac7d43 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/route.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/route.dart @@ -27,8 +27,14 @@ class _State extends State with AutomaticK Future _refresh() async { TautulliLocalState _state = Provider.of(context, listen: false); - _state.resetAllStreamInfoGraphs(context); - await Future.wait([]); + _state.resetAllStreamInformationGraphs(context); + await Future.wait([ + _state.dailyStreamTypeBreakdownGraph, + _state.playCountBySourceResolutionGraph, + _state.playCountByStreamResolutionGraph, + _state.playCountByPlatformStreamTypeGraph, + _state.playCountByUserStreamTypeGraph, + ]); } @override @@ -36,7 +42,7 @@ class _State extends State with AutomaticK super.build(context); return Scaffold( key: _scaffoldKey, - body: _comingSoon, + body: _body, ); } @@ -44,9 +50,53 @@ class _State extends State with AutomaticK refreshKey: _refreshKey, onRefresh: _refresh, child: LSListView( - children: [], + children: [ + LSHeader( + text: 'Daily Stream Type Breakdown', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data} Days', + '\n\n', + 'The total play count or duration of television, movies, and music by the transcode decision.', + ].join(), + ), + TautulliGraphsDailyStreamTypeBreakdownGraph(), + LSHeader( + text: 'By Source Resolution', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television and movies by their original resolution (pre-transcoding).', + ].join(), + ), + TautulliGraphsPlayCountBySourceResolutionGraph(), + LSHeader( + text: 'By Stream Resolution', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television and movies by their original resolution (pre-transcoding).', + ].join(), + ), + TautulliGraphsPlayCountByStreamResolutionGraph(), + LSHeader( + text: 'By Platform Stream Type', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television and movies by their original resolution (pre-transcoding).', + ].join(), + ), + TautulliGraphsPlayCountByPlatformStreamTypeGraph(), + LSHeader( + text: 'By User Stream Type', + subtitle: [ + 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days', + '\n\n', + 'The combined total of television and movies by their original resolution (pre-transcoding).', + ].join(), + ), + TautulliGraphsPlayCountByUserStreamTypeGraph(), + ], ), ); - - Widget get _comingSoon => LSGenericMessage(text: 'Coming Soon'); } diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets.dart index e69de29b..54423204 100644 --- a/lib/modules/tautulli/modules/graphs_stream_information/widgets.dart +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets.dart @@ -0,0 +1,5 @@ +export 'widgets/daily_stream_type_breakdown_graph.dart'; +export 'widgets/play_count_by_platform_stream_type_graph.dart'; +export 'widgets/play_count_by_source_resolution_graph.dart'; +export 'widgets/play_count_by_stream_resolution_graph.dart'; +export 'widgets/play_count_by_user_stream_type_graph.dart'; diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart new file mode 100644 index 00000000..3131ca23 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/daily_stream_type_breakdown_graph.dart @@ -0,0 +1,59 @@ + +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsDailyStreamTypeBreakdownGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.dailyStreamTypeBreakdownGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsDailyStreamTypeBreakdownGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysByDate', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: LineChart( + LineChartData( + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliLineGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + lineBarsData: TautulliLineGraphHelper.lineBarsData(data), + lineTouchData: TautulliLineGraphHelper.lineTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart new file mode 100644 index 00000000..341b6a15 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_platform_stream_type_graph.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountByPlatformStreamTypeGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountByPlatformStreamTypeGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountByPlatformStreamTypeGraph', + '_body', + 'Unable to fetch Tautulli graph data: getStreamTypeByTopTenPlatforms', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart new file mode 100644 index 00000000..d3d21210 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_source_resolution_graph.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountBySourceResolutionGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountBySourceResolutionGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountBySourceResolutionGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysBySourceResolution', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart new file mode 100644 index 00000000..c5a1dc87 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_stream_resolution_graph.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountByStreamResolutionGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountByStreamResolutionGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountByStreamResolutionGraph', + '_body', + 'Unable to fetch Tautulli graph data: getPlaysByStreamResolution', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } + + +} diff --git a/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart new file mode 100644 index 00000000..f6a4ca21 --- /dev/null +++ b/lib/modules/tautulli/modules/graphs_stream_information/widgets/play_count_by_user_stream_type_graph.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; +import 'package:tautulli/tautulli.dart'; + +class TautulliGraphsPlayCountByUserStreamTypeGraph extends StatelessWidget { + @override + Widget build(BuildContext context) => Selector>( + selector: (_, state) => state.playCountByUserStreamTypeGraph, + builder: (context, future, _) => FutureBuilder( + future: future, + builder: (context, AsyncSnapshot snapshot) { + if(snapshot.hasError) { + if(snapshot.connectionState != ConnectionState.waiting) { + Logger.error( + 'TautulliGraphsPlayCountByUserStreamTypeGraph', + '_body', + 'Unable to fetch Tautulli graph data: getStreamTypeByTopTenUsers', + snapshot.error, + StackTrace.current, + uploadToSentry: !(snapshot.error is DioError), + ); + } + return TautulliGraphHelper.errorContainer; + } + if(snapshot.hasData) return _graph(context, snapshot.data); + return TautulliGraphHelper.loadingContainer; + }, + ), + ); + + Widget _graph(BuildContext context, TautulliGraphData data) { + return LSCard( + child: Column( + children: [ + Container( + height: TautulliGraphHelper.GRAPH_HEIGHT, + width: MediaQuery.of(context).size.width, + child: Padding( + child: BarChart( + BarChartData( + alignment: TautulliGraphHelper.chartAlignment(), + gridData: TautulliGraphHelper.gridData(), + titlesData: TautulliGraphHelper.titlesData(data), + borderData: TautulliGraphHelper.borderData(), + barGroups: TautulliBarGraphHelper.barGroups(context, data), + barTouchData: TautulliBarGraphHelper.barTouchData(context, data), + ), + ), + padding: EdgeInsets.all(14.0), + ), + ), + TautulliGraphHelper.createLegend(data.series), + ], + ), + ); + } +} diff --git a/lib/modules/tautulli/modules/history_details/route.dart b/lib/modules/tautulli/modules/history_details/route.dart index 7a71bea8..71ba81f7 100644 --- a/lib/modules/tautulli/modules/history_details/route.dart +++ b/lib/modules/tautulli/modules/history_details/route.dart @@ -101,7 +101,10 @@ class _State extends State { body: _body, ); - Widget get _appBar => LSAppBar(title: 'History Details'); + Widget get _appBar => LSAppBar( + title: 'History Details', + actions: [], + ); Widget get _body => LSRefreshIndicator( refreshKey: _refreshKey, diff --git a/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart b/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart index adb35786..6b1ed71a 100644 --- a/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/check_for_updates_tile.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lunasea/core.dart'; +import 'package:lunasea/modules/tautulli.dart'; class TautulliMoreCheckForUpdatesTile extends StatelessWidget { @override @@ -13,10 +14,8 @@ class TautulliMoreCheckForUpdatesTile extends StatelessWidget { onTap: () async => _onTap(context), ); - Future _onTap(BuildContext context) async => LSSnackBar( - context: context, - title: 'Coming Soon!', - message: 'Feature has not yet been implemented', - type: SNACKBAR_TYPE.info, + Future _onTap(BuildContext context) async => TautulliRouter.router.navigateTo( + context, + TautulliCheckForUpdatesRoute.route(), ); } diff --git a/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart b/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart index 8883ce8b..2ebfa747 100644 --- a/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart +++ b/lib/modules/tautulli/modules/more/widgets/graphs_tile.dart @@ -14,15 +14,8 @@ class TautulliMoreGraphsTile extends StatelessWidget { onTap: () async => _onTap(context), ); - Future _onTap(BuildContext context) async => LSSnackBar( - context: context, - title: 'Coming Soon!', - message: 'Feature has not yet been implemented', - type: SNACKBAR_TYPE.info, + Future _onTap(BuildContext context) async => TautulliRouter.router.navigateTo( + context, + TautulliGraphsRoute.route(), ); - - // Future _onTap(BuildContext context) async => TautulliRouter.router.navigateTo( - // context, - // TautulliGraphsRoute.route(), - // ); } diff --git a/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart b/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart index 02be2b3c..8ef50d79 100644 --- a/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart +++ b/lib/modules/tautulli/modules/recently_added/widgets/content_tile.dart @@ -128,7 +128,7 @@ class TautulliRecentlyAddedContentTile extends StatelessWidget { Future _onTap(BuildContext context) async => LSSnackBar( context: context, title: 'Coming Soon!', - message: 'This feature has not yet been implemented', + message: 'Library data has not yet been implemented', type: SNACKBAR_TYPE.info, ); } diff --git a/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart index 3fdb8fe6..5488736d 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/media_tile.dart @@ -117,7 +117,7 @@ class TautulliStatisticsMediaTile extends StatelessWidget { Future _onTap(BuildContext context) async => LSSnackBar( context: context, title: 'Coming Soon!', - message: 'This feature has not yet been implemented', + message: 'Library data has not yet been implemented', type: SNACKBAR_TYPE.info, ); } \ No newline at end of file diff --git a/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart b/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart index a8d5c9c0..678ed92d 100644 --- a/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart +++ b/lib/modules/tautulli/modules/statistics/widgets/recently_watched_tile.dart @@ -96,7 +96,7 @@ class TautulliStatisticsRecentlyWatchedTile extends StatelessWidget { Future _onTap(BuildContext context) async => LSSnackBar( context: context, title: 'Coming Soon!', - message: 'This feature has not yet been implemented', + message: 'Library data has not yet been implemented', type: SNACKBAR_TYPE.info, ); } \ No newline at end of file diff --git a/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart b/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart index 3351b8e0..91dc1985 100644 --- a/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart +++ b/lib/modules/tautulli/modules/tautulli/widgets/global_settings.dart @@ -27,7 +27,7 @@ class TautulliGlobalSettings extends StatelessWidget { Provider.of(context, listen: false).api.system.backupConfig() .then((_) => LSSnackBar( context: context, - title: 'Backing Up Configuration...', + title: 'Backing Up Configuration${Constants.TEXT_ELLIPSIS}', message: 'Backing up your configuration in the background', )) .catchError((error, trace) { @@ -51,7 +51,7 @@ class TautulliGlobalSettings extends StatelessWidget { Provider.of(context, listen: false).api.system.backupDB() .then((_) => LSSnackBar( context: context, - title: 'Backing Up Database...', + title: 'Backing Up Database${Constants.TEXT_ELLIPSIS}', message: 'Backing up your database in the background', )) .catchError((error, trace) { @@ -75,7 +75,7 @@ class TautulliGlobalSettings extends StatelessWidget { Provider.of(context, listen: false).api.system.deleteCache() .then((_) => LSSnackBar( context: context, - title: 'Deleting Cache...', + title: 'Deleting Cache${Constants.TEXT_ELLIPSIS}', message: 'Tautulli cache is being deleted', )) .catchError((error, trace) { @@ -99,7 +99,7 @@ class TautulliGlobalSettings extends StatelessWidget { Provider.of(context, listen: false).api.system.deleteImageCache() .then((_) => LSSnackBar( context: context, - title: 'Deleting Image Cache...', + title: 'Deleting Image Cache${Constants.TEXT_ELLIPSIS}', message: 'Tautulli image cache is being deleted', )) .catchError((error, trace) { diff --git a/pubspec.lock b/pubspec.lock index 1dcf0ebe..28fa60a6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -465,7 +465,7 @@ packages: name: in_app_purchase url: "https://pub.dartlang.org" source: hosted - version: "0.3.4+5" + version: "0.3.4+6" intl: dependency: "direct main" description: @@ -827,7 +827,7 @@ packages: name: tautulli url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: @@ -869,7 +869,7 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.5.2" + version: "5.5.3" url_launcher_linux: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a98f731c..9ce677e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: lunasea description: Self Hosted Manager -version: 4.0.0+400009 +version: 4.0.0+400010 publish_to: 'none' environment: sdk: ">=2.7.0 <3.0.0" @@ -26,7 +26,7 @@ dependencies: google_nav_bar: ^3.0.0 hive: ^1.4.4 hive_flutter: ^0.3.1 - in_app_purchase: ^0.3.4+5 + in_app_purchase: ^0.3.4+6 intl: ^0.16.1 package_info: ^0.4.3 path_provider: ^1.6.14 @@ -38,11 +38,11 @@ dependencies: stack_trace: ^1.9.5 table_calendar: ^2.2.3 tuple: ^1.0.3 - url_launcher: ^5.5.2 + url_launcher: ^5.5.3 uuid: ^2.2.2 xml_parser: ^0.1.2 # LunaTools Packages - tautulli: ^1.0.5 + tautulli: ^1.1.0 wake_on_lan: ^1.1.1+1 dev_dependencies: