Skip to content

Commit

Permalink
Merge pull request #9 from GhostenEditor/release-1.2.0
Browse files Browse the repository at this point in the history
feat: new feature
  • Loading branch information
GhostenEditor authored Nov 23, 2024
2 parents c1d4702 + 8c59352 commit e63d373
Show file tree
Hide file tree
Showing 18 changed files with 687 additions and 152 deletions.
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

0 comments on commit e63d373

Please sign in to comment.