mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
refactor: Optimize logs page with LRU caching and improved pagination handling
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
import 'package:advanced_datatable/advanced_datatable_source.dart';
|
import 'package:advanced_datatable/advanced_datatable_source.dart';
|
||||||
import 'package:advanced_datatable/datatable.dart';
|
import 'package:advanced_datatable/datatable.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:quiver/collection.dart';
|
||||||
import '../api/log.dart';
|
import '../api/log.dart';
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
@@ -34,39 +34,54 @@ class LogsPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _LogDataSource extends AdvancedDataTableSource<LogEntry> {
|
class _LogDataSource extends AdvancedDataTableSource<LogEntry> {
|
||||||
_LogDataSource(this.logs,
|
_LogDataSource(
|
||||||
{this.type,
|
{this.type,
|
||||||
this.minLevel,
|
this.minLevel,
|
||||||
this.allowedLevel,
|
this.allowedLevel,
|
||||||
this.size = 10,
|
this.size = 10,
|
||||||
this.count,
|
|
||||||
this.page = 1,
|
this.page = 1,
|
||||||
this.locale,
|
this.locale});
|
||||||
this.start = 0});
|
|
||||||
final String? type;
|
final String? type;
|
||||||
final LogLevel? minLevel;
|
final LogLevel? minLevel;
|
||||||
final List<LogLevel>? allowedLevel;
|
final List<LogLevel>? allowedLevel;
|
||||||
final int size;
|
int size;
|
||||||
final String? locale;
|
final String? locale;
|
||||||
int? count;
|
int? count;
|
||||||
int start;
|
|
||||||
int page;
|
int page;
|
||||||
List<LogEntry> logs;
|
LruMap<int, List<LogEntry>> logs = LruMap(maximumSize: 20);
|
||||||
|
bool offsetMode = false;
|
||||||
|
List<LogEntry> offsetData = [];
|
||||||
@override
|
@override
|
||||||
bool get isRowCountApproximate => count == null;
|
bool get isRowCountApproximate => count != null;
|
||||||
@override
|
@override
|
||||||
int get rowCount => count ?? 10;
|
int get rowCount => count ?? 0;
|
||||||
@override
|
@override
|
||||||
int get selectedRowCount => 0;
|
int get selectedRowCount => 0;
|
||||||
@override
|
@override
|
||||||
Future<RemoteDataSourceDetails<LogEntry>> getNextPage(
|
Future<RemoteDataSourceDetails<LogEntry>> getNextPage(
|
||||||
NextPageRequest pageRequest) async {
|
NextPageRequest pageRequest) async {
|
||||||
|
if (size != pageRequest.pageSize) {
|
||||||
|
size = pageRequest.pageSize;
|
||||||
|
logs.clear();
|
||||||
|
}
|
||||||
|
if (pageRequest.offset % size != 0) {
|
||||||
|
offsetMode = true;
|
||||||
|
offsetData = (await api.queryLog(
|
||||||
|
offset: pageRequest.offset,
|
||||||
|
limit: size,
|
||||||
|
minLevel: minLevel?.toInt(),
|
||||||
|
allowedLevel: allowedLevel?.map((e) => e.toInt()).join(","),
|
||||||
|
type: type))
|
||||||
|
.unwrap()
|
||||||
|
.datas;
|
||||||
|
return RemoteDataSourceDetails(count ?? offsetData.length, offsetData);
|
||||||
|
}
|
||||||
|
offsetMode = false;
|
||||||
int npage = pageRequest.offset ~/ pageRequest.pageSize + 1;
|
int npage = pageRequest.offset ~/ pageRequest.pageSize + 1;
|
||||||
page = npage;
|
page = npage;
|
||||||
if ((logs.length >= pageRequest.offset + pageRequest.pageSize - start &&
|
var log = logs[page];
|
||||||
pageRequest.offset >= start) ||
|
if (log != null) {
|
||||||
logs.length == count) {
|
return RemoteDataSourceDetails(count ?? log.length, log);
|
||||||
return RemoteDataSourceDetails(count ?? logs.length, logs);
|
|
||||||
}
|
}
|
||||||
var data = (await api.queryLog(
|
var data = (await api.queryLog(
|
||||||
page: page,
|
page: page,
|
||||||
@@ -75,30 +90,36 @@ class _LogDataSource extends AdvancedDataTableSource<LogEntry> {
|
|||||||
allowedLevel: allowedLevel?.map((e) => e.toInt()).join(","),
|
allowedLevel: allowedLevel?.map((e) => e.toInt()).join(","),
|
||||||
limit: size))
|
limit: size))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
count = data.count;
|
if (count != data.count) {
|
||||||
if (pageRequest.offset < start) {
|
logs.clear();
|
||||||
logs.insertAll(0, data.datas);
|
|
||||||
start -= data.datas.length;
|
|
||||||
} else {
|
|
||||||
logs.addAll(data.datas);
|
|
||||||
}
|
}
|
||||||
return RemoteDataSourceDetails(count ?? logs.length, logs);
|
count = data.count;
|
||||||
|
logs[page] = data.datas;
|
||||||
|
return RemoteDataSourceDetails(count ?? data.datas.length, data.datas);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
DataRow? getDataRow(LogEntry? log) {
|
||||||
DataRow? getRow(int index) {
|
|
||||||
index += (page - 1) * size;
|
|
||||||
index -= start;
|
|
||||||
var log = logs.elementAtOrNull(index);
|
|
||||||
if (log == null) return null;
|
if (log == null) return null;
|
||||||
return DataRow(cells: [
|
return DataRow(cells: [
|
||||||
DataCell(
|
DataCell(
|
||||||
Text(DateFormat.yMd(locale).add_jms().format(log.time.toLocal()))),
|
Text(DateFormat.yMd(locale).add_jms().format(log.time.toLocal()))),
|
||||||
DataCell(Text(log.message)),
|
DataCell(Text(log.message, maxLines: 2)),
|
||||||
DataCell(Text(log.level.name)),
|
DataCell(Text(log.level.name)),
|
||||||
DataCell(Text(log.type)),
|
DataCell(Text(log.type)),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DataRow? getRow(int index) {
|
||||||
|
if (offsetMode) {
|
||||||
|
var log = offsetData.elementAtOrNull(index);
|
||||||
|
return getDataRow(log);
|
||||||
|
}
|
||||||
|
var vlog = logs[page];
|
||||||
|
if (vlog == null) return null;
|
||||||
|
var log = vlog.elementAtOrNull(index);
|
||||||
|
return getDataRow(log);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LogsPage extends State<LogsPage> with ThemeModeWidget, IsTopWidget2 {
|
class _LogsPage extends State<LogsPage> with ThemeModeWidget, IsTopWidget2 {
|
||||||
@@ -106,39 +127,10 @@ class _LogsPage extends State<LogsPage> with ThemeModeWidget, IsTopWidget2 {
|
|||||||
String? _type;
|
String? _type;
|
||||||
LogLevel? _minLevel;
|
LogLevel? _minLevel;
|
||||||
List<LogLevel>? _allowedLevel;
|
List<LogLevel>? _allowedLevel;
|
||||||
int _size = 50;
|
int _size = 10;
|
||||||
|
int _offset = 0;
|
||||||
bool _pageMode = false;
|
bool _pageMode = false;
|
||||||
_LogDataSource? _dataSource;
|
_LogDataSource? _dataSource;
|
||||||
CancelToken? _cancel;
|
|
||||||
bool _isLoading = false;
|
|
||||||
LogEntries? _firstPage;
|
|
||||||
|
|
||||||
Future<void> _fetchFirstPage() async {
|
|
||||||
try {
|
|
||||||
_cancel = CancelToken();
|
|
||||||
_isLoading = true;
|
|
||||||
_firstPage = (await api.queryLog(
|
|
||||||
page: _page ?? 1,
|
|
||||||
type: _type,
|
|
||||||
minLevel: _minLevel?.toInt(),
|
|
||||||
allowedLevel: _allowedLevel?.map((e) => e.toInt()).join(","),
|
|
||||||
limit: _size,
|
|
||||||
cancel: _cancel))
|
|
||||||
.unwrap();
|
|
||||||
if (!_cancel!.isCancelled) {
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (!_cancel!.isCancelled) {
|
|
||||||
_log.severe("Failed to load first page:", e);
|
|
||||||
setState(() {
|
|
||||||
_isLoading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -152,10 +144,16 @@ class _LogsPage extends State<LogsPage> with ThemeModeWidget, IsTopWidget2 {
|
|||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void updateRoute(BuildContext context) {
|
||||||
void dispose() {
|
var params = {
|
||||||
_cancel?.cancel();
|
"page": _page?.toString(),
|
||||||
super.dispose();
|
"type": _type,
|
||||||
|
"min_level": _minLevel?.name,
|
||||||
|
"allowed_level": _allowedLevel?.map((e) => e.name).join(","),
|
||||||
|
"size": _size.toString(),
|
||||||
|
};
|
||||||
|
params.removeWhere((key, value) => value == null || value!.isEmpty);
|
||||||
|
context.replaceNamed("/logs", queryParameters: params);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -166,21 +164,15 @@ class _LogsPage extends State<LogsPage> with ThemeModeWidget, IsTopWidget2 {
|
|||||||
setCurrentTitle(i18n.serverLogs);
|
setCurrentTitle(i18n.serverLogs);
|
||||||
}
|
}
|
||||||
final locale = MainApp.of(context).lang.toLocale().toString();
|
final locale = MainApp.of(context).lang.toLocale().toString();
|
||||||
bool isLoading = false;
|
|
||||||
if (_pageMode) {
|
if (_pageMode) {
|
||||||
isLoading = _firstPage == null;
|
_dataSource ??= _LogDataSource(
|
||||||
if (isLoading && !_isLoading) _fetchFirstPage();
|
page: _page ?? 1,
|
||||||
if (_dataSource == null && _firstPage != null) {
|
type: _type,
|
||||||
_dataSource = _LogDataSource(_firstPage!.datas,
|
minLevel: _minLevel,
|
||||||
page: _page ?? 1,
|
allowedLevel: _allowedLevel,
|
||||||
type: _type,
|
size: _size,
|
||||||
minLevel: _minLevel,
|
locale: locale);
|
||||||
allowedLevel: _allowedLevel,
|
_offset = ((_page ?? 1) - 1) * _size;
|
||||||
size: _size,
|
|
||||||
count: _firstPage!.count,
|
|
||||||
locale: locale,
|
|
||||||
start: (_page ?? 1 - 1) * _size);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -198,29 +190,30 @@ class _LogsPage extends State<LogsPage> with ThemeModeWidget, IsTopWidget2 {
|
|||||||
),
|
),
|
||||||
body: _pageMode
|
body: _pageMode
|
||||||
? _dataSource != null
|
? _dataSource != null
|
||||||
? AdvancedPaginatedDataTable(
|
? SingleChildScrollView(
|
||||||
columns: <DataColumn>[
|
child: AdvancedPaginatedDataTable(
|
||||||
|
columns: <DataColumn>[
|
||||||
DataColumn(label: Text(i18n.time)),
|
DataColumn(label: Text(i18n.time)),
|
||||||
DataColumn(label: Text(i18n.message)),
|
DataColumn(label: Text(i18n.message)),
|
||||||
DataColumn(label: Text(i18n.level)),
|
DataColumn(label: Text(i18n.level)),
|
||||||
DataColumn(label: Text(i18n.type)),
|
DataColumn(label: Text(i18n.type)),
|
||||||
],
|
],
|
||||||
source: _dataSource!,
|
source: _dataSource!,
|
||||||
rowsPerPage: _size,
|
rowsPerPage: _size,
|
||||||
onPageChanged: (page) {
|
onPageChanged: (page) {
|
||||||
_page = page ~/ _size + 1;
|
_page = page ~/ _size + 1;
|
||||||
var params = {
|
updateRoute(context);
|
||||||
"page": _page?.toString(),
|
},
|
||||||
"type": _type,
|
showHorizontalScrollbarAlways: true,
|
||||||
"min_level": _minLevel?.name,
|
showFirstLastButtons: true,
|
||||||
"allowed_level":
|
initialFirstRowIndex: _offset,
|
||||||
_allowedLevel?.map((e) => e.name).join(","),
|
onRowsPerPageChanged: (size) {
|
||||||
"size": _size.toString(),
|
setState(() {
|
||||||
};
|
_size = size ?? 10;
|
||||||
params.removeWhere(
|
_page = _offset ~/ _size + 1;
|
||||||
(key, value) => value == null || value!.isEmpty);
|
updateRoute(context);
|
||||||
context.replaceNamed("/logs", queryParameters: params);
|
});
|
||||||
})
|
}))
|
||||||
: const Center(child: CircularProgressIndicator())
|
: const Center(child: CircularProgressIndicator())
|
||||||
: Container());
|
: Container());
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user