mirror of
https://github.com/jagandeepbrar/lunasea.git
synced 2026-08-31 12:42:34 +00:00
[TestFlight] v4.0.0+400010 (#254)
NEW - [Tautulli/Graphs] Implemented graphs page - [Tautulli/Updates] Implemented check for updates page TWEAKS - None FIXES - None
This commit is contained in:
@@ -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';
|
||||
|
||||
3
lib/modules/tautulli/core/graphs.dart
Normal file
3
lib/modules/tautulli/core/graphs.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
export 'graphs/bar_graph.dart';
|
||||
export 'graphs/graph.dart';
|
||||
export 'graphs/line_graph.dart';
|
||||
80
lib/modules/tautulli/core/graphs/bar_graph.dart
Normal file
80
lib/modules/tautulli/core/graphs/bar_graph.dart
Normal file
@@ -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<BarChartGroupData> barGroups(BuildContext context, TautulliGraphData data) => List<BarChartGroupData>.generate(
|
||||
data.categories.take(BAR_COUNT).length,
|
||||
(cIndex) => BarChartGroupData(
|
||||
x: cIndex,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
y: data.series.fold<double>(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<BarChartRodStackItem>.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<rod.rodStackItems.length; i++) {
|
||||
double _number = (rod?.rodStackItems[i]?.toY ?? 0)-(rod?.rodStackItems[i]?.fromY ?? 0);
|
||||
String _value = data?.series[i]?.name ?? 'Unknown';
|
||||
String _text = Provider.of<TautulliState>(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<TautulliSeriesData> series,
|
||||
) => series.take(sIndex).fold<double>(0, (value, data) => value+data.data[cIndex]);
|
||||
|
||||
static double _toY(
|
||||
int cIndex,
|
||||
int sIndex,
|
||||
List<TautulliSeriesData> series,
|
||||
) => series.take(sIndex+1).fold<double>(0, (value, data) => value+data.data[cIndex]);
|
||||
}
|
||||
102
lib/modules/tautulli/core/graphs/graph.dart
Normal file
102
lib/modules/tautulli/core/graphs/graph.dart
Normal file
@@ -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<TautulliSeriesData> 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
98
lib/modules/tautulli/core/graphs/line_graph.dart
Normal file
98
lib/modules/tautulli/core/graphs/line_graph.dart
Normal file
@@ -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<LineChartBarData> lineBarsData(TautulliGraphData data) => List<LineChartBarData>.generate(
|
||||
data.series.length,
|
||||
(sIndex) => LineChartBarData(
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 3.0,
|
||||
colors: [LSColors.graph(sIndex)],
|
||||
spots: List<FlSpot>.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<LineBarSpot> spots) => List<LineTooltipItem>.generate(
|
||||
spots.length,
|
||||
(index) => LineTooltipItem(
|
||||
[
|
||||
'${data.series[spots[index].barIndex].name}: ',
|
||||
Provider.of<TautulliState>(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<TouchedSpotIndicatorData>.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],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -251,17 +251,17 @@ class TautulliLocalState extends ChangeNotifier {
|
||||
/// GRAPHS ///
|
||||
//////////////
|
||||
|
||||
Future<TautulliGraphData> _playCountByDateGraph;
|
||||
Future<TautulliGraphData> get playCountByDateGraph => _playCountByDateGraph;
|
||||
set playCountByDateGraph(Future<TautulliGraphData> playCountByDateGraph) {
|
||||
assert(playCountByDateGraph != null);
|
||||
_playCountByDateGraph = playCountByDateGraph;
|
||||
Future<TautulliGraphData> _dailyPlayCountGraph;
|
||||
Future<TautulliGraphData> get dailyPlayCountGraph => _dailyPlayCountGraph;
|
||||
set dailyPlayCountGraph(Future<TautulliGraphData> dailyPlayCountGraph) {
|
||||
assert(dailyPlayCountGraph != null);
|
||||
_dailyPlayCountGraph = dailyPlayCountGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _resetPlayCountByDateGraph(BuildContext context) {
|
||||
void resetDailyPlayCountGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(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<TautulliState>(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<TautulliGraphData> _playCountByDayOfWeekGraph;
|
||||
Future<TautulliGraphData> get playCountByDayOfWeekGraph => _playCountByDayOfWeekGraph;
|
||||
set playCountByDayOfWeekGraph(Future<TautulliGraphData> playCountByDayOfWeekGraph) {
|
||||
assert(playCountByDayOfWeekGraph != null);
|
||||
_playCountByDayOfWeekGraph = playCountByDayOfWeekGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetAllStreamInfoGraphs(BuildContext context) {}
|
||||
void resetPlayCountByDayOfWeekGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _playCountByDayOfWeekGraph = _state.api.history.getPlaysByDayOfWeek(
|
||||
timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data,
|
||||
yAxis: _state.graphYAxis,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliGraphData> _playCountByTopPlatformsGraph;
|
||||
Future<TautulliGraphData> get playCountByTopPlatformsGraph => _playCountByTopPlatformsGraph;
|
||||
set playCountByTopPlatformsGraph(Future<TautulliGraphData> playCountByTopPlatformsGraph) {
|
||||
assert(playCountByTopPlatformsGraph != null);
|
||||
_playCountByTopPlatformsGraph = playCountByTopPlatformsGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetPlayCountByTopPlatformsGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _playCountByTopPlatformsGraph = _state.api.history.getPlaysByTopTenPlatforms(
|
||||
timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data,
|
||||
yAxis: _state.graphYAxis,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliGraphData> _playCountByTopUsersGraph;
|
||||
Future<TautulliGraphData> get playCountByTopUsersGraph => _playCountByTopUsersGraph;
|
||||
set playCountByTopUsersGraph(Future<TautulliGraphData> playCountByTopUsersGraph) {
|
||||
assert(playCountByTopUsersGraph != null);
|
||||
_playCountByTopUsersGraph = playCountByTopUsersGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetPlayCountByTopUsersGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(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<TautulliGraphData> _dailyStreamTypeBreakdownGraph;
|
||||
Future<TautulliGraphData> get dailyStreamTypeBreakdownGraph => _dailyStreamTypeBreakdownGraph;
|
||||
set dailyStreamTypeBreakdownGraph(Future<TautulliGraphData> dailyStreamTypeBreakdownGraph) {
|
||||
assert(dailyStreamTypeBreakdownGraph != null);
|
||||
_dailyStreamTypeBreakdownGraph = dailyStreamTypeBreakdownGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetDailyStreamTypeBreakdownGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _dailyStreamTypeBreakdownGraph = _state.api.history.getPlaysByStreamType(
|
||||
timeRange: TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data,
|
||||
yAxis: _state.graphYAxis,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliGraphData> _playCountBySourceResolutionGraph;
|
||||
Future<TautulliGraphData> get playCountBySourceResolutionGraph => _playCountBySourceResolutionGraph;
|
||||
set playCountBySourceResolutionGraph(Future<TautulliGraphData> playCountBySourceResolutionGraph) {
|
||||
assert(playCountBySourceResolutionGraph != null);
|
||||
_playCountBySourceResolutionGraph = playCountBySourceResolutionGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetPlayCountBySourceResolutionGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _playCountBySourceResolutionGraph = _state.api.history.getPlaysBySourceResolution(
|
||||
timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data,
|
||||
yAxis: _state.graphYAxis,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliGraphData> _playCountByStreamResolutionGraph;
|
||||
Future<TautulliGraphData> get playCountByStreamResolutionGraph => _playCountByStreamResolutionGraph;
|
||||
set playCountByStreamResolutionGraph(Future<TautulliGraphData> playCountByStreamResolutionGraph) {
|
||||
assert(playCountByStreamResolutionGraph != null);
|
||||
_playCountByStreamResolutionGraph = playCountByStreamResolutionGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetPlayCountByStreamResolutionGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _playCountByStreamResolutionGraph = _state.api.history.getPlaysByStreamResolution(
|
||||
timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data,
|
||||
yAxis: _state.graphYAxis,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliGraphData> _playCountByPlatformStreamTypeGraph;
|
||||
Future<TautulliGraphData> get playCountByPlatformStreamTypeGraph => _playCountByPlatformStreamTypeGraph;
|
||||
set playCountByPlatformStreamTypeGraph(Future<TautulliGraphData> playCountByPlatformStreamTypeGraph) {
|
||||
assert(playCountByPlatformStreamTypeGraph != null);
|
||||
_playCountByPlatformStreamTypeGraph = playCountByPlatformStreamTypeGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetPlayCountByPlatformStreamTypeGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _playCountByPlatformStreamTypeGraph = _state.api.history.getStreamTypeByTopTenPlatforms(
|
||||
timeRange: TautulliDatabaseValue.GRAPHS_DAYS.data,
|
||||
yAxis: _state.graphYAxis,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliGraphData> _playCountByUserStreamTypeGraph;
|
||||
Future<TautulliGraphData> get playCountByUserStreamTypeGraph => _playCountByUserStreamTypeGraph;
|
||||
set playCountByUserStreamTypeGraph(Future<TautulliGraphData> playCountByUserStreamTypeGraph) {
|
||||
assert(playCountByUserStreamTypeGraph != null);
|
||||
_playCountByUserStreamTypeGraph = playCountByUserStreamTypeGraph;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetPlayCountByUserStreamTypeGraph(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(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<TautulliPMSUpdate> _updatePlexMediaServer;
|
||||
Future<TautulliPMSUpdate> get updatePlexMediaServer => _updatePlexMediaServer;
|
||||
set updatePlexMediaServer(Future<TautulliPMSUpdate> updatePlexMediaServer) {
|
||||
assert(updatePlexMediaServer != null);
|
||||
_updatePlexMediaServer = updatePlexMediaServer;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetUpdatePlexMediaServer(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _updatePlexMediaServer = _state.api.system.getPMSUpdate();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<TautulliUpdateCheck> _updateTautulli;
|
||||
Future<TautulliUpdateCheck> get updateTautulli => _updateTautulli;
|
||||
set updateTautulli(Future<TautulliUpdateCheck> updateTautulli) {
|
||||
assert(updateTautulli != null);
|
||||
_updateTautulli = updateTautulli;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetUpdateTautulli(BuildContext context) {
|
||||
TautulliState _state = Provider.of<TautulliState>(context, listen: false);
|
||||
if(_state.api != null) _updateTautulli = _state.api.system.updateCheck();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetAllUpdates(BuildContext context) {
|
||||
resetUpdatePlexMediaServer(context);
|
||||
resetUpdateTautulli(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,12 @@ class _State extends State<TautulliActivityDetailsRoute> {
|
||||
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,
|
||||
|
||||
@@ -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<TautulliState, Future<TautulliActivity>>(
|
||||
selector: (_, state) => state.activity,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliActivity> 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<void> _onPressed(BuildContext context) => LSSnackBar(
|
||||
|
||||
@@ -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<StatefulWidget> createState() => _State();
|
||||
}
|
||||
|
||||
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 {
|
||||
TautulliLocalState _local = Provider.of<TautulliLocalState>(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<TautulliLocalState>(context).updatePlexMediaServer,
|
||||
Provider.of<TautulliLocalState>(context).updateTautulli,
|
||||
]),
|
||||
builder: (context, AsyncSnapshot<List<Object>> 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),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'widgets/pms_tile.dart';
|
||||
export 'widgets/tautulli_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: <TextSpan>[
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -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: <TextSpan>[
|
||||
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,
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ class TautulliGraphsTypeButton extends StatelessWidget {
|
||||
onSelected: (value) {
|
||||
Provider.of<TautulliState>(context, listen: false).graphYAxis = value;
|
||||
Provider.of<TautulliLocalState>(context, listen: false).resetAllPlayPeriodGraphs(context);
|
||||
Provider.of<TautulliLocalState>(context, listen: false).resetAllStreamInfoGraphs(context);
|
||||
Provider.of<TautulliLocalState>(context, listen: false).resetAllStreamInformationGraphs(context);
|
||||
},
|
||||
itemBuilder: (context) => List<PopupMenuEntry<TautulliGraphYAxis>>.generate(
|
||||
TautulliStatsType.values.length,
|
||||
|
||||
@@ -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<TautulliGraphsPlayByPeriodRoute> with AutomaticKeepAliveClientMixin {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey<RefreshIndicatorState>();
|
||||
final String _placeholder = '<<!title!>>';
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@@ -31,7 +29,11 @@ class _State extends State<TautulliGraphsPlayByPeriodRoute> with AutomaticKeepAl
|
||||
TautulliLocalState _state = Provider.of<TautulliLocalState>(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<TautulliGraphsPlayByPeriodRoute> 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<Widget> get _dailyPlayCount => [
|
||||
LSHeader(
|
||||
text: _createTitle('Daily Play $_placeholder'),
|
||||
subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_LINECHART_DAYS.data} Days',
|
||||
),
|
||||
TautulliGraphsPlayCountByDateGraph(),
|
||||
];
|
||||
|
||||
List<Widget> get _playsByMonth => [
|
||||
LSHeader(
|
||||
text: 'Plays By Month',
|
||||
subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_MONTHS.data} Months',
|
||||
),
|
||||
TautulliGraphsPlaysByMonthGraph(),
|
||||
];
|
||||
|
||||
List<Widget> get _playCountByDayOfWeek => [
|
||||
LSHeader(
|
||||
text: _createTitle('Play $_placeholder By Day Of Week'),
|
||||
subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days',
|
||||
),
|
||||
];
|
||||
|
||||
List<Widget> get _playCountByTopPlatforms => [
|
||||
LSHeader(
|
||||
text: _createTitle('Play $_placeholder By Top Platforms'),
|
||||
subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days',
|
||||
),
|
||||
];
|
||||
|
||||
List<Widget> get _playCountByTopUsers => [
|
||||
LSHeader(
|
||||
text: _createTitle('Play $_placeholder By Top Users'),
|
||||
subtitle: 'Last ${TautulliDatabaseValue.GRAPHS_DAYS.data} Days',
|
||||
),
|
||||
];
|
||||
|
||||
String _createTitle(String title) => Provider.of<TautulliState>(context).graphYAxis == TautulliGraphYAxis.PLAYS
|
||||
? title.replaceFirst(_placeholder, 'Count')
|
||||
: title.replaceFirst(_placeholder, 'Duration');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.dailyPlayCountGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByDateGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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<LineChartBarData>.generate(
|
||||
data.series.length,
|
||||
(sIndex) => LineChartBarData(
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 3.0,
|
||||
colors: [LSColors.list(sIndex)],
|
||||
spots: List<FlSpot>.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<LineBarSpot> spots) => List<LineTooltipItem>.generate(
|
||||
spots.length,
|
||||
(index) => LineTooltipItem(
|
||||
[
|
||||
'${data.series[spots[index].barIndex].name}: ',
|
||||
Provider.of<TautulliState>(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<TouchedSpotIndicatorData>.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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByDayOfWeekGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByTopPlatformsGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByTopUsersGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
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<BarChartGroupData>.generate(
|
||||
data.categories.length,
|
||||
(cIndex) => BarChartGroupData(
|
||||
x: cIndex,
|
||||
barRods: List<BarChartRodData>.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<TautulliState>(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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,14 @@ class _State extends State<TautulliGraphsStreamInformationRoute> with AutomaticK
|
||||
|
||||
Future<void> _refresh() async {
|
||||
TautulliLocalState _state = Provider.of<TautulliLocalState>(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<TautulliGraphsStreamInformationRoute> with AutomaticK
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
key: _scaffoldKey,
|
||||
body: _comingSoon,
|
||||
body: _body,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,9 +50,53 @@ class _State extends State<TautulliGraphsStreamInformationRoute> 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');
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.dailyStreamTypeBreakdownGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByPlatformStreamTypeGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountBySourceResolutionGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByStreamResolutionGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<TautulliLocalState, Future<TautulliGraphData>>(
|
||||
selector: (_, state) => state.playCountByUserStreamTypeGraph,
|
||||
builder: (context, future, _) => FutureBuilder(
|
||||
future: future,
|
||||
builder: (context, AsyncSnapshot<TautulliGraphData> 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,10 @@ class _State extends State<TautulliHistoryDetailsRoute> {
|
||||
body: _body,
|
||||
);
|
||||
|
||||
Widget get _appBar => LSAppBar(title: 'History Details');
|
||||
Widget get _appBar => LSAppBar(
|
||||
title: 'History Details',
|
||||
actions: [],
|
||||
);
|
||||
|
||||
Widget get _body => LSRefreshIndicator(
|
||||
refreshKey: _refreshKey,
|
||||
|
||||
@@ -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<void> _onTap(BuildContext context) async => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Coming Soon!',
|
||||
message: 'Feature has not yet been implemented',
|
||||
type: SNACKBAR_TYPE.info,
|
||||
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
|
||||
context,
|
||||
TautulliCheckForUpdatesRoute.route(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,15 +14,8 @@ class TautulliMoreGraphsTile extends StatelessWidget {
|
||||
onTap: () async => _onTap(context),
|
||||
);
|
||||
|
||||
Future<void> _onTap(BuildContext context) async => LSSnackBar(
|
||||
context: context,
|
||||
title: 'Coming Soon!',
|
||||
message: 'Feature has not yet been implemented',
|
||||
type: SNACKBAR_TYPE.info,
|
||||
Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
|
||||
context,
|
||||
TautulliGraphsRoute.route(),
|
||||
);
|
||||
|
||||
// Future<void> _onTap(BuildContext context) async => TautulliRouter.router.navigateTo(
|
||||
// context,
|
||||
// TautulliGraphsRoute.route(),
|
||||
// );
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class TautulliRecentlyAddedContentTile extends StatelessWidget {
|
||||
Future<void> _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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ class TautulliStatisticsMediaTile extends StatelessWidget {
|
||||
Future<void> _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,
|
||||
);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class TautulliStatisticsRecentlyWatchedTile extends StatelessWidget {
|
||||
Future<void> _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,
|
||||
);
|
||||
}
|
||||
@@ -27,7 +27,7 @@ class TautulliGlobalSettings extends StatelessWidget {
|
||||
Provider.of<TautulliState>(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<TautulliState>(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<TautulliState>(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<TautulliState>(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) {
|
||||
|
||||
Reference in New Issue
Block a user