Files
Zakaria d0ca84d8d2
Build / Build for Android (push) Has been cancelled
Build / Build for iOS (push) Has been cancelled
redesign app phase 1
2026-05-18 14:34:41 -04:00

181 lines
5.9 KiB
Dart

import 'package:clipboard/clipboard.dart';
import 'package:finamp/services/censored_log.dart';
import 'package:finamp/services/contains_login.dart';
import 'package:flutter/material.dart';
import 'package:finamp/l10n/app_localizations.dart';
import 'package:logging/logging.dart';
import '../error_snackbar.dart';
class LogTile extends StatefulWidget {
const LogTile({Key? key, required this.logRecord}) : super(key: key);
final LogRecord logRecord;
@override
State<LogTile> createState() => _LogTileState();
}
class _LogTileState extends State<LogTile> {
final _controller = ExpansionTileController();
/// Whether the user has confirmed. This is used to stop onExpansionChanged
/// from infinitely asking the user to confirm.
bool hasConfirmed = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPress: () async {
try {
await FlutterClipboard.copy(widget.logRecord.censoredMessage);
} catch (e) {
errorSnackbar(e, context);
}
},
child: Card(
color: _logColor(widget.logRecord.level, context),
child: ExpansionTile(
controller: _controller,
key: PageStorageKey(widget.logRecord.time),
leading: _LogIcon(level: widget.logRecord.level),
title: RichText(
maxLines: 2,
overflow: TextOverflow.ellipsis,
text: TextSpan(
text:
"[${widget.logRecord.loggerName}]\n${widget.logRecord.time}",
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
),
subtitle: RichText(
maxLines: 2,
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: widget.logRecord.loginCensoredMessage,
style: Theme.of(context).textTheme.bodyMedium,
),
),
expandedAlignment: Alignment.centerLeft,
expandedCrossAxisAlignment: CrossAxisAlignment.start,
textColor: _logTextColor(widget.logRecord.level, context),
collapsedTextColor: _logTextColor(widget.logRecord.level, context),
iconColor: _logTextColor(widget.logRecord.level, context),
collapsedIconColor: _logTextColor(widget.logRecord.level, context),
// Remove the border when expanded
shape: const Border(),
childrenPadding: const EdgeInsets.all(8.0),
children: [
Text(
AppLocalizations.of(context)!.message,
style: Theme.of(context).textTheme.titleLarge,
),
_LogMessageContent(widget.logRecord.message),
const SizedBox(height: 16.0),
if (widget.logRecord.stackTrace != null)
Text(
AppLocalizations.of(context)!.stackTrace,
style: Theme.of(context).textTheme.titleLarge,
),
if (widget.logRecord.stackTrace != null)
_LogMessageContent(widget.logRecord.stackTrace.toString()),
],
onExpansionChanged: (value) async {
if (value && !hasConfirmed && widget.logRecord.containsLogin) {
_controller.collapse();
final confirmed = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text(AppLocalizations.of(context)!.confirm),
content: Text(
AppLocalizations.of(context)!.showUncensoredLogMessage),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(MaterialLocalizations.of(context)
.cancelButtonLabel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(AppLocalizations.of(context)!.confirm),
),
],
);
},
);
hasConfirmed = confirmed!;
if (confirmed) {
_controller.expand();
}
} else if (hasConfirmed) {
hasConfirmed = false;
}
},
),
),
);
}
Color _logColor(Level level, BuildContext context) {
if (level == Level.WARNING) {
return Theme.of(context).colorScheme.tertiaryContainer;
} else if (level == Level.SEVERE) {
return Theme.of(context).colorScheme.errorContainer;
} else {
return Theme.of(context).colorScheme.primaryContainer;
}
}
Color _logTextColor(Level level, BuildContext context) {
if (level == Level.WARNING) {
return Theme.of(context).colorScheme.onTertiaryContainer;
} else if (level == Level.SEVERE) {
return Theme.of(context).colorScheme.onErrorContainer;
} else {
return Theme.of(context).colorScheme.onPrimaryContainer;
}
}
}
class _LogIcon extends StatelessWidget {
const _LogIcon({Key? key, required this.level}) : super(key: key);
final Level level;
@override
Widget build(BuildContext context) {
if (level == Level.INFO) {
return const Icon(Icons.info);
} else if (level == Level.WARNING) {
return const Icon(Icons.warning);
} else if (level == Level.SEVERE) {
return const Icon(Icons.error);
} else {
return const Icon(Icons.info);
}
}
}
class _LogMessageContent extends StatelessWidget {
const _LogMessageContent(this.content, {Key? key}) : super(key: key);
final String content;
@override
Widget build(BuildContext context) {
return Text(
content,
style: const TextStyle(
fontSize: 12.0,
fontFamily: "monospace",
),
);
}
}