first commit
Build / Build for Android (push) Has been cancelled
Build / Build for iOS (push) Has been cancelled

This commit is contained in:
Zakaria
2026-05-18 14:15:38 -04:00
commit 1563409cb1
382 changed files with 45347 additions and 0 deletions
@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:get_it/get_it.dart';
import '../../services/finamp_logs_helper.dart';
class CopyLogsButton extends StatefulWidget {
const CopyLogsButton({Key? key}) : super(key: key);
@override
State<CopyLogsButton> createState() => _CopyLogsButtonState();
}
class _CopyLogsButtonState extends State<CopyLogsButton> {
@override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.copy),
onPressed: () async {
final finampLogsHelper = GetIt.instance<FinampLogsHelper>();
await finampLogsHelper.copyLogs();
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(AppLocalizations.of(context)!.logsCopied),
));
},
);
}
}
+181
View File
@@ -0,0 +1,181 @@
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:flutter_gen/gen_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",
),
);
}
}
+25
View File
@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'log_tile.dart';
import '../../services/finamp_logs_helper.dart';
class LogsView extends StatelessWidget {
const LogsView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
FinampLogsHelper finampLogsHelper = GetIt.instance<FinampLogsHelper>();
return Scrollbar(
child: ListView.builder(
itemCount: finampLogsHelper.logs.length,
reverse: true,
itemBuilder: (context, index) {
return LogTile(
logRecord: finampLogsHelper.logs.reversed.elementAt(index));
},
),
);
}
}
@@ -0,0 +1,22 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:get_it/get_it.dart';
import '../../services/finamp_logs_helper.dart';
class ShareLogsButton extends StatelessWidget {
const ShareLogsButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.adaptive.share),
tooltip: AppLocalizations.of(context)!.shareLogs,
onPressed: () async {
final finampLogsHelper = GetIt.instance<FinampLogsHelper>();
await finampLogsHelper.shareLogs();
},
);
}
}