Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new feature #9

Merged
merged 1 commit into from
Nov 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"/>
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true"/>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts"/>
Expand All @@ -52,6 +49,9 @@
<data android:host="player"/>
</intent-filter>
</activity>
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="true"/>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"buttonMarkNotPlayed": "Mark Not Played",
"buttonMarkPlayed": "Mark Played",
"buttonName": "Name",
"buttonNewFolder": "New Folder",
"buttonPause": "Pause",
"buttonPlay": "Play",
"buttonProperty": "Property",
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_zh.arb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"buttonMarkNotPlayed": "标记为未观看",
"buttonMarkPlayed": "标记为已观看",
"buttonName": "名称",
"buttonNewFolder": "新建文件夾",
"buttonPause": "暂停",
"buttonPlay": "播放",
"buttonProperty": "属性",
Expand Down
2 changes: 1 addition & 1 deletion lib/pages/player/common_player.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class CommonPlayerPage extends StatefulWidget {

class _CommonPlayerPageState extends State<CommonPlayerPage> {
late final userConfig = context.read<UserConfig>();
late final controller = PlayerController<ExPlaylistItem>(widget.playlist, widget.index);
late final controller = PlayerController<ExPlaylistItem>(widget.playlist, widget.index, Api.log);
late final StreamSubscription<bool> _pipSubscription;
final cast = const CastAdaptor();

Expand Down
3 changes: 2 additions & 1 deletion lib/pages/player/live_player.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';

import 'package:animations/animations.dart';
import 'package:api/api.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand All @@ -24,7 +25,7 @@ class LivePlayerPage extends StatefulWidget {

class _LivePlayerPageState extends State<LivePlayerPage> {
final _scaffoldKey = GlobalKey<ScaffoldState>();
late final _controller = PlayerController(widget.playlist, widget.index);
late final _controller = PlayerController(widget.playlist, widget.index, Api.log);
final _isShowControls = ValueNotifier(false);
late final StreamSubscription<bool> _pipSubscription;

Expand Down
3 changes: 2 additions & 1 deletion lib/pages/player/singleton_player.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:api/api.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:player_view/player.dart';
Expand All @@ -18,7 +19,7 @@ class _SingletonPlayerState extends State<SingletonPlayer> {
url: Uri.parse(widget.url),
sourceType: PlaylistItemSourceType.local,
),
]);
], null, Api.log);

@override
void dispose() {
Expand Down
167 changes: 51 additions & 116 deletions lib/pages/settings/settings_log.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import 'dart:io';

import 'package:api/api.dart';
import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';

import '../../utils/utils.dart';
import '../../components/error_message.dart';
import '../../components/no_data.dart';

class SettingsLogPage extends StatefulWidget {
const SettingsLogPage({super.key});
Expand Down Expand Up @@ -72,129 +71,65 @@ class _SettingsLogPageState extends State<SettingsLogPage> {
controller: _scrollController,
child: RefreshIndicator(
onRefresh: () async => _controller.refresh(),
child: PagedListView(
pagingController: _controller,
scrollController: _scrollController,
builderDelegate: PagedChildBuilderDelegate<Log>(
itemBuilder: (context, item, index) => switch (item.type) {
LogType.divider => const Divider(),
LogType.end => const ListTile(
title: Text('END', textAlign: TextAlign.center),
dense: true,
visualDensity: VisualDensity.compact,
child: PagedListView.separated(
pagingController: _controller,
scrollController: _scrollController,
builderDelegate: PagedChildBuilderDelegate<Log>(
itemBuilder: (context, item, index) => ListTile(
dense: true,
visualDensity: VisualDensity.compact,
title: Text(item.message),
subtitle: Text(formatDate(item.time, [yyyy, '-', mm, '-', dd, ' ', HH, ':', nn, ':', ss, '.', SSS])),
leading: Badge(
label: SizedBox(width: 40, child: Text(item.level.name.toUpperCase(), textAlign: TextAlign.center)),
backgroundColor: switch (item.level) {
LogLevel.error => null,
LogLevel.warn => const Color(0xffffab32),
LogLevel.info => Theme.of(context).colorScheme.primary,
LogLevel.debug => Theme.of(context).colorScheme.secondary,
LogLevel.trace => Theme.of(context).colorScheme.secondary,
},
),
onLongPress: () {
Clipboard.setData(ClipboardData(text: item.toString()));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.tipsForCopiedSuccessfully, textAlign: TextAlign.center),
duration: const Duration(seconds: 1),
),
_ => ListTile(
dense: true,
visualDensity: VisualDensity.compact,
title: Text(item.text!),
subtitle: Text(formatDate(item.dateTime!, [yyyy, '-', mm, '-', dd, ' ', HH, ':', nn, ':', ss, '.', SSS, uuu])),
leading: Badge(
label: SizedBox(width: 40, child: Text(item.type.name.toUpperCase(), textAlign: TextAlign.center)),
backgroundColor: switch (item.type) {
LogType.error => null,
LogType.warn => const Color(0xffffab32),
LogType.info => Theme.of(context).colorScheme.primary,
LogType.debug => Theme.of(context).colorScheme.secondary,
_ => throw UnimplementedError(),
},
),
onLongPress: () {
Clipboard.setData(ClipboardData(text: item.toString()));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.tipsForCopiedSuccessfully, textAlign: TextAlign.center),
duration: const Duration(seconds: 1),
),
);
},
)
);
},
)),
),
firstPageErrorIndicatorBuilder: (_) => ErrorMessage(snapshot: AsyncSnapshot.withError(ConnectionState.done, _controller.error)),
newPageErrorIndicatorBuilder: (_) => ErrorMessage(snapshot: AsyncSnapshot.withError(ConnectionState.done, _controller.error)),
noItemsFoundIndicatorBuilder: (_) => const NoData(),
noMoreItemsIndicatorBuilder: (_) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text('END', style: Theme.of(context).textTheme.labelMedium, textAlign: TextAlign.center),
),
),
separatorBuilder: (BuildContext context, int index) => const Divider(indent: 18, endIndent: 12, thickness: 0.5),
),
),
),
);
}

Future<void> query(int index) async {
final logPath = await Api.logPath();
final list = await Directory(logPath!).list().toList();
final filteredList = list.reversed.where((entity) {
if (_dateTimeRange != null) {
final filename = entity.path.split('/').removeLast();
final dataTime = DateTime(
int.parse(filename.substring(0, 4)),
int.parse(filename.substring(5, 7)),
int.parse(filename.substring(8, 10)),
int.parse(filename.substring(11, 13)),
int.parse(filename.substring(14, 16)),
int.parse(filename.substring(17, 19)),
);
return _dateTimeRange!.start <= dataTime && _dateTimeRange!.end.add(const Duration(days: 1)) > dataTime;
try {
final data = await Api.logQueryPage(
30,
index * 30,
_dateTimeRange != null ? (_dateTimeRange!.start.millisecondsSinceEpoch, _dateTimeRange!.end.add(const Duration(days: 1)).millisecondsSinceEpoch) : null,
);

if (data.offset + data.limit >= data.count) {
_controller.appendLastPage(data.data);
} else {
return true;
}
});
if (index >= filteredList.length) {
_controller.appendLastPage([Log.divider, Log.end]);
} else {
final file = File(filteredList.elementAt(index).path);
final data = await file.readAsLines();

List<Log> list = [];
String cache = '';
for (final line in data.reversed) {
try {
final log = Log.fromString(cache + line);
cache = '';
list.add(log);
} catch (e) {
cache = line + cache;
}
}

if (index == filteredList.length - 1) {
_controller.appendLastPage([...list, Log.divider, Log.end]);
} else {
_controller.appendPage([...list, Log.divider], index + 1);
_controller.appendPage(data.data, index + 1);
}
} catch (error) {
_controller.error = error;
}
}
}

enum LogType {
error,
warn,
info,
debug,
divider,
end;

static LogType fromString(String s) => switch (s) {
'ERROR' => LogType.error,
'WARN' => LogType.warn,
'INFO' => LogType.info,
'DEBUG' => LogType.debug,
_ => throw Exception(),
};
}

class Log {
final LogType type;
final DateTime? dateTime;
final String? text;

const Log({required this.type, this.dateTime, this.text});

static const divider = Log(type: LogType.divider);
static const end = Log(type: LogType.end);

Log.fromString(String s)
: type = LogType.fromString(s.substring(28, 33).trimRight()),
dateTime = DateTime.parse(s.substring(0, 26).trimRight()),
text = s.substring(36);

@override
String toString() {
return '${formatDate(dateTime!, [yyyy, '-', mm, '-', dd, ' ', HH, ':', nn, ':', ss, '.', SSS, uuu])} ${type.name.toUpperCase()}: $text';
}
}
2 changes: 2 additions & 0 deletions lib/providers/user_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,15 @@ class UserConfig extends ChangeNotifier {
case AutoUpdateFrequency.everyday:
if (lastCheckUpdateTime == null || lastCheckUpdateTime!.add(const Duration(days: 1)) <= now) {
lastCheckUpdateTime = now;
save();
return true;
} else {
return false;
}
case AutoUpdateFrequency.everyWeek:
if (lastCheckUpdateTime == null || lastCheckUpdateTime!.add(const Duration(days: 7)) <= now) {
lastCheckUpdateTime = now;
save();
return true;
} else {
return false;
Expand Down
36 changes: 28 additions & 8 deletions lib/views/file_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,21 @@ class FileViewer extends StatelessWidget {
offset: const Offset(1, 0),
tooltip: '',
itemBuilder: (context) => [
PopupMenuItem(
autofocus: PlatformApi.isAndroidTV(),
leading: const Icon(Icons.folder_open_rounded),
title: Text(AppLocalizations.of(context)!.buttonNewFolder),
onTap: () async {
final filename =
await showDialog<String>(context: context, builder: (context) => _FileNameDialog(dialogTitle: AppLocalizations.of(context)!.buttonNewFolder));
if (filename != null && context.mounted) {
final resp = await showNotification(context, Api.fileMkdir(driverId, item.parentId, filename));
if (resp?.error == null) {
onRefresh();
}
}
},
),
if (item.viewable())
PopupMenuItem(
leading: const Icon(Icons.play_arrow_rounded),
Expand All @@ -59,11 +74,15 @@ class FileViewer extends StatelessWidget {
},
),
PopupMenuItem(
autofocus: PlatformApi.isAndroidTV(),
leading: const Icon(Icons.drive_file_rename_outline),
title: Text(AppLocalizations.of(context)!.buttonRename),
onTap: () async {
final filename = await showDialog<String>(context: context, builder: (context) => _FileRenameDialog(filename: item.name));
final filename = await showDialog<String>(
context: context,
builder: (context) => _FileNameDialog(
dialogTitle: AppLocalizations.of(context)!.buttonRename,
filename: item.name,
));
if (filename != null && context.mounted) {
final resp = await showNotification(context, Api.fileRename(driverId, item.id, filename));
if (resp?.error == null) {
Expand Down Expand Up @@ -119,16 +138,17 @@ class FileViewer extends StatelessWidget {
}
}

class _FileRenameDialog extends StatefulWidget {
final String filename;
class _FileNameDialog extends StatefulWidget {
final String dialogTitle;
final String? filename;

const _FileRenameDialog({required this.filename});
const _FileNameDialog({required this.dialogTitle, this.filename});

@override
State<_FileRenameDialog> createState() => _FileRenameDialogState();
State<_FileNameDialog> createState() => _FileNameDialogState();
}

class _FileRenameDialogState extends State<_FileRenameDialog> {
class _FileNameDialogState extends State<_FileNameDialog> {
late final _controller = TextEditingController(text: widget.filename);
final _formKey = GlobalKey<FormState>();

Expand All @@ -141,7 +161,7 @@ class _FileRenameDialogState extends State<_FileRenameDialog> {
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(AppLocalizations.of(context)!.buttonRename),
title: Text(widget.dialogTitle),
content: Form(
key: _formKey,
child: TextFormField(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ class ApiPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, ServiceConnec
"getLocalIpAddress" -> result.success(getLocalIpAddress())
"requestStoragePermission" -> requestStoragePermission(result)
"databasePath" -> result.success(apiService?.databasePath?.path)
"logPath" -> result.success(apiService?.logPath)
"initialized" -> {
if (serviceConnected) {
result.success(apiService?.apiInitialized())
Expand Down Expand Up @@ -80,6 +79,9 @@ class ApiPlugin : FlutterPlugin, MethodCallHandler, ActivityAware, ServiceConnec
}
}

"log" -> {
apiService?.log(call.argument<Int>("level")!!, call.argument<String>("message")!!)
}

else -> {
if (apiService == null) {
Expand Down
Loading
Loading