This commit is contained in:
Christien Rioux 2023-10-09 22:11:39 -04:00
parent 711f82735e
commit 9291dc2b80
8 changed files with 196 additions and 88 deletions

View File

@ -163,7 +163,9 @@
"developer": { "developer": {
"title": "Developer Logs", "title": "Developer Logs",
"command": "Command", "command": "Command",
"copied": "Selection copied" "copied": "Selection copied",
"cleared": "Logs cleared",
"are_you_sure_clear": "Are you sure you want to clear the logs?"
}, },
"log": { "log": {
"error": "Error", "error": "Error",

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:ansicolor/ansicolor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -23,6 +24,9 @@ void main() async {
debugPrint('VeilidChat PID: $pid'); debugPrint('VeilidChat PID: $pid');
} }
// Ansi colors
ansiColorDisabled = false;
// Logs // Logs
initLoggy(); initLoggy();

View File

@ -1,11 +1,16 @@
import 'package:ansicolor/ansicolor.dart';
import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:cool_dropdown/cool_dropdown.dart';
import 'package:cool_dropdown/models/cool_dropdown_item.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:loggy/loggy.dart'; import 'package:loggy/loggy.dart';
import 'package:quickalert/quickalert.dart';
import 'package:xterm/xterm.dart'; import 'package:xterm/xterm.dart';
import '../tools/tools.dart'; import '../tools/tools.dart';
@ -28,60 +33,63 @@ class DeveloperPage extends ConsumerStatefulWidget {
} }
class DeveloperPageState extends ConsumerState<DeveloperPage> { class DeveloperPageState extends ConsumerState<DeveloperPage> {
final terminalController = TerminalController(); final _terminalController = TerminalController();
var logLevelDropDown = log.level.logLevel; final _debugCommandController = TextEditingController();
final TextEditingController _debugCommandController = TextEditingController(); final _logLevelController = DropdownController(duration: 250.ms);
final List<CoolDropdownItem<LogLevel>> _logLevelDropdownItems = [];
var _logLevelDropDown = log.level.logLevel;
var _showEllet = false;
@override @override
void initState() { void initState() {
// _scrollController = ScrollController(
// onAttach: _handlePositionAttach,
// onDetach: _handlePositionDetach,
// );
super.initState(); super.initState();
terminalController.addListener(() { _terminalController.addListener(() {
setState(() {}); setState(() {});
}); });
for (var i = 0; i < logLevels.length; i++) {
_logLevelDropdownItems.add(CoolDropdownItem<LogLevel>(
label: logLevelName(logLevels[i]),
icon: Text(logLevelEmoji(logLevels[i])),
value: logLevels[i]));
}
} }
// void _handleScrollChange() { void _debugOut(String out) {
// if (_isScrolling != _scrollController.position.isScrollingNotifier.value) { final pen = AnsiPen()..cyan(bold: true);
// _isScrolling = _scrollController.position.isScrollingNotifier.value; final colorOut = pen(out);
// _wantsBottom = _scrollController.position.pixels == debugPrint(colorOut);
// _scrollController.position.maxScrollExtent; globalDebugTerminal.write(colorOut.replaceAll('\n', '\r\n'));
// } }
// }
// void _handlePositionAttach(ScrollPosition position) {
// // From here, add a listener to the given ScrollPosition.
// // Here the isScrollingNotifier will be used to inform when scrolling starts
// // and stops and change the AppBar's color in response.
// position.isScrollingNotifier.addListener(_handleScrollChange);
// }
// void _handlePositionDetach(ScrollPosition position) {
// // From here, add a listener to the given ScrollPosition.
// // Here the isScrollingNotifier will be used to inform when scrolling starts
// // and stops and change the AppBar's color in response.
// position.isScrollingNotifier.removeListener(_handleScrollChange);
// }
// void _scrollToBottom() {
// _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
// _wantsBottom = true;
// }
Future<void> _sendDebugCommand(String debugCommand) async { Future<void> _sendDebugCommand(String debugCommand) async {
log.info('DEBUG >>>\n$debugCommand'); if (debugCommand == 'ellet') {
final out = await Veilid.instance.debug(debugCommand); setState(() {
log.info('<<< DEBUG\n$out'); _showEllet = !_showEllet;
});
return;
}
_debugOut('DEBUG >>>\n$debugCommand\n');
try {
final out = await Veilid.instance.debug(debugCommand);
_debugOut('<<< DEBUG\n$out\n');
} on Exception catch (e, st) {
_debugOut('<<< ERROR\n$e\n<<< STACK\n$st');
}
}
Future<void> clear(BuildContext context) async {
globalDebugTerminal.buffer.clear();
if (context.mounted) {
showInfoToast(context, translate('developer.cleared'));
}
} }
Future<void> copySelection(BuildContext context) async { Future<void> copySelection(BuildContext context) async {
final selection = terminalController.selection; final selection = _terminalController.selection;
if (selection != null) { if (selection != null) {
final text = globalDebugTerminal.buffer.getText(selection); final text = globalDebugTerminal.buffer.getText(selection);
terminalController.clearSelection(); _terminalController.clearSelection();
await Clipboard.setData(ClipboardData(text: text)); await Clipboard.setData(ClipboardData(text: text));
if (context.mounted) { if (context.mounted) {
showInfoToast(context, translate('developer.copied')); showInfoToast(context, translate('developer.copied'));
@ -92,7 +100,7 @@ class DeveloperPageState extends ConsumerState<DeveloperPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
//final textTheme = theme.textTheme; final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
// WidgetsBinding.instance.addPostFrameCallback((_) { // WidgetsBinding.instance.addPostFrameCallback((_) {
@ -112,49 +120,99 @@ class DeveloperPageState extends ConsumerState<DeveloperPage> {
icon: const Icon(Icons.copy), icon: const Icon(Icons.copy),
color: scale.primaryScale.text, color: scale.primaryScale.text,
disabledColor: scale.grayScale.subtleText, disabledColor: scale.grayScale.subtleText,
onPressed: terminalController.selection == null onPressed: _terminalController.selection == null
? null ? null
: () async { : () async {
await copySelection(context); await copySelection(context);
}), }),
DropdownMenu<LogLevel>( IconButton(
initialSelection: logLevelDropDown, icon: const Icon(Icons.clear_all),
onSelected: (value) { color: scale.primaryScale.text,
if (value != null) { disabledColor: scale.grayScale.subtleText,
setState(() { onPressed: () async {
logLevelDropDown = value; await QuickAlert.show(
//log. = value; context: context,
setVeilidLogLevel(value); type: QuickAlertType.confirm,
}); title: translate('developer.are_you_sure_clear'),
} textColor: scale.primaryScale.text,
}, confirmBtnColor: scale.primaryScale.elementBackground,
dropdownMenuEntries: [ backgroundColor: scale.primaryScale.subtleBackground,
DropdownMenuEntry<LogLevel>( headerBackgroundColor: scale.primaryScale.background,
value: LogLevel.error, label: translate('log.error')), confirmBtnText: translate('button.ok'),
DropdownMenuEntry<LogLevel>( cancelBtnText: translate('button.cancel'),
value: LogLevel.warning, label: translate('log.warning')), onConfirmBtnTap: () async {
DropdownMenuEntry<LogLevel>( Navigator.pop(context);
value: LogLevel.info, label: translate('log.info')), if (context.mounted) {
DropdownMenuEntry<LogLevel>( await clear(context);
value: LogLevel.debug, label: translate('log.debug')), }
DropdownMenuEntry<LogLevel>( });
value: traceLevel, label: translate('log.trace')), }),
]) CoolDropdown<LogLevel>(
controller: _logLevelController,
defaultItem: _logLevelDropdownItems
.singleWhere((x) => x.value == _logLevelDropDown),
onChange: (value) {
setState(() {
_logLevelDropDown = value;
Loggy('').level = getLogOptions(value);
setVeilidLogLevel(value);
_logLevelController.close();
});
},
resultOptions: ResultOptions(
width: 64,
height: 40,
render: ResultRender.icon,
textStyle: textTheme.labelMedium!
.copyWith(color: scale.primaryScale.text),
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
openBoxDecoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
boxDecoration:
BoxDecoration(color: scale.primaryScale.elementBackground),
),
dropdownOptions: DropdownOptions(
width: 160,
align: DropdownAlign.right,
duration: 150.ms,
color: scale.primaryScale.elementBackground,
borderSide: BorderSide(color: scale.primaryScale.border),
borderRadius: BorderRadius.circular(8),
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
),
dropdownTriangleOptions: const DropdownTriangleOptions(
align: DropdownTriangleAlign.right),
dropdownItemOptions: DropdownItemOptions(
selectedTextStyle: textTheme.labelMedium!
.copyWith(color: scale.primaryScale.text),
textStyle: textTheme.labelMedium!
.copyWith(color: scale.primaryScale.text),
selectedBoxDecoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
selectedPadding: const EdgeInsets.fromLTRB(8, 4, 8, 4)),
dropdownList: _logLevelDropdownItems,
)
], ],
title: Text(translate('developer.title')), title: Text(translate('developer.title'),
style:
textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.bold)),
centerTitle: true, centerTitle: true,
), ),
body: Column(children: [ body: SafeArea(
TerminalView( child: Column(children: [
globalDebugTerminal, Stack(alignment: AlignmentDirectional.center, children: [
textStyle: kDefaultTerminalStyle, Image.asset('assets/images/ellet.png'),
controller: terminalController, TerminalView(globalDebugTerminal,
//autofocus: true, textStyle: kDefaultTerminalStyle,
//backgroundOpacity: 0.9, controller: _terminalController,
onSecondaryTapDown: (details, offset) async { //autofocus: true,
backgroundOpacity: _showEllet ? 0.75 : 1.0,
onSecondaryTapDown: (details, offset) async {
await copySelection(context); await copySelection(context);
}, })
).expanded(), ]).expanded(),
TextField( TextField(
controller: _debugCommandController, controller: _debugCommandController,
decoration: InputDecoration( decoration: InputDecoration(
@ -167,24 +225,32 @@ class DeveloperPageState extends ConsumerState<DeveloperPage> {
hintText: translate('developer.command'), hintText: translate('developer.command'),
suffixIcon: IconButton( suffixIcon: IconButton(
icon: const Icon(Icons.send), icon: const Icon(Icons.send),
onPressed: () async { onPressed: _debugCommandController.text.isEmpty
final debugCommand = _debugCommandController.text; ? null
_debugCommandController.clear(); : () async {
await _sendDebugCommand(debugCommand); final debugCommand = _debugCommandController.text;
}, _debugCommandController.clear();
await _sendDebugCommand(debugCommand);
},
)), )),
onChanged: (_) {
setState(() => {});
},
onSubmitted: (debugCommand) async { onSubmitted: (debugCommand) async {
_debugCommandController.clear(); _debugCommandController.clear();
await _sendDebugCommand(debugCommand); await _sendDebugCommand(debugCommand);
}, },
).paddingAll(4) ).paddingAll(4)
])); ])));
} }
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<TerminalController>( properties
'terminalController', terminalController)); ..add(DiagnosticsProperty<TerminalController>(
'terminalController', _terminalController))
..add(
DiagnosticsProperty<LogLevel>('logLevelDropDown', _logLevelDropDown));
} }
} }

View File

@ -153,7 +153,6 @@ class NewAccountPageState extends ConsumerState<NewAccountPage> {
body: _newAccountForm( body: _newAccountForm(
context, context,
onSubmit: (formKey) async { onSubmit: (formKey) async {
debugPrint(_formKey.currentState?.value.toString());
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
try { try {
await createAccount(); await createAccount();

View File

@ -92,6 +92,8 @@ class RouterNotifier extends _$RouterNotifier implements Listenable {
case '/home/settings': case '/home/settings':
case '/new_account/settings': case '/new_account/settings':
return null; return null;
case '/developer':
return null;
default: default:
return hasAnyAccount ? null : '/new_account'; return hasAnyAccount ? null : '/new_account';
} }

View File

@ -2,6 +2,7 @@ import 'dart:io' show Platform;
import 'package:ansicolor/ansicolor.dart'; import 'package:ansicolor/ansicolor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:loggy/loggy.dart'; import 'package:loggy/loggy.dart';
@ -54,13 +55,37 @@ final DateFormat _dateFormatter = DateFormat('HH:mm:ss.SSS');
extension PrettyPrintLogRecord on LogRecord { extension PrettyPrintLogRecord on LogRecord {
String pretty() { String pretty() {
final tm = _dateFormatter.format(time.toLocal()); final tm = _dateFormatter.format(time.toLocal());
final lev = logEmoji(level); final lev = logLevelEmoji(level);
final lstr = wrapWithLogColor(level, tm); final lstr = wrapWithLogColor(level, tm);
return '$lstr $lev $message'; return '$lstr $lev $message';
} }
} }
String logEmoji(LogLevel logLevel) { List<LogLevel> logLevels = [
LogLevel.error,
LogLevel.warning,
LogLevel.info,
LogLevel.debug,
traceLevel,
];
String logLevelName(LogLevel logLevel) {
switch (logLevel) {
case traceLevel:
return translate('log.trace');
case LogLevel.debug:
return translate('log.debug');
case LogLevel.info:
return translate('log.info');
case LogLevel.warning:
return translate('log.warning');
case LogLevel.error:
return translate('log.error');
}
return '???';
}
String logLevelEmoji(LogLevel logLevel) {
switch (logLevel) { switch (logLevel) {
case traceLevel: case traceLevel:
return '👾'; return '👾';
@ -69,7 +94,7 @@ String logEmoji(LogLevel logLevel) {
case LogLevel.info: case LogLevel.info:
return '💡'; return '💡';
case LogLevel.warning: case LogLevel.warning:
return '⚠️'; return '🍋';
case LogLevel.error: case LogLevel.error:
return '🛑'; return '🛑';
} }

View File

@ -337,6 +337,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.1" version: "3.1.1"
cool_dropdown:
dependency: "direct main"
description:
name: cool_dropdown
sha256: "24400f57740b4779407586121e014bef241699ad2a52c506a7e1e7616cb68653"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:

View File

@ -19,6 +19,7 @@ dependencies:
charcode: ^1.3.1 charcode: ^1.3.1
circular_profile_avatar: ^2.0.5 circular_profile_avatar: ^2.0.5
circular_reveal_animation: ^2.0.1 circular_reveal_animation: ^2.0.1
cool_dropdown: ^2.1.0
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
equatable: ^2.0.5 equatable: ^2.0.5
fast_immutable_collections: ^9.1.5 fast_immutable_collections: ^9.1.5
@ -119,6 +120,7 @@ flutter:
- assets/images/icon.svg - assets/images/icon.svg
- assets/images/title.svg - assets/images/title.svg
- assets/images/vlogo.svg - assets/images/vlogo.svg
- assets/images/ellet.png
# Fonts # Fonts
fonts: fonts:
- family: Source Code Pro - family: Source Code Pro