Merge branch 'debugging' into 'main'
Developer Console Support See merge request veilid/veilidchat!14
@ -62,6 +62,7 @@ android {
|
|||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.debug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
namespace 'com.veilid.veilidchat'
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="com.veilid.veilidchat">
|
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
the Flutter tool needs it to communicate with the running application
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="com.veilid.veilidchat">
|
|
||||||
<application
|
<application
|
||||||
android:label="VeilidChat"
|
android:label="VeilidChat"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_rounded">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_round"/>
|
||||||
|
</adaptive-icon>
|
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 33 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 48 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 18 KiB |
4
android/app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFFFF</color>
|
||||||
|
</resources>
|
@ -1,5 +1,4 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
package="com.veilid.veilidchat">
|
|
||||||
<!-- The INTERNET permission is required for development. Specifically,
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
the Flutter tool needs it to communicate with the running application
|
the Flutter tool needs it to communicate with the running application
|
||||||
to allow setting breakpoints, to provide hot reload, etc.
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.0'
|
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||||
|
BIN
assets/fonts/SourceCodePro-Bold.ttf
Normal file
BIN
assets/fonts/SourceCodePro-Regular.ttf
Normal file
@ -159,5 +159,19 @@
|
|||||||
"titlebar": "Settings",
|
"titlebar": "Settings",
|
||||||
"color_theme": "Color Theme",
|
"color_theme": "Color Theme",
|
||||||
"brightness_mode": "Brightness Mode"
|
"brightness_mode": "Brightness Mode"
|
||||||
|
},
|
||||||
|
"developer": {
|
||||||
|
"title": "Developer Logs",
|
||||||
|
"command": "Command",
|
||||||
|
"copied": "Selection copied",
|
||||||
|
"cleared": "Logs cleared",
|
||||||
|
"are_you_sure_clear": "Are you sure you want to clear the logs?"
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"error": "Error",
|
||||||
|
"warning": "Warning",
|
||||||
|
"info": "Info",
|
||||||
|
"debug": "Debug",
|
||||||
|
"trace": "Trace"
|
||||||
}
|
}
|
||||||
}
|
}
|
BIN
assets/sources/round_icon.afdesign
Normal file
@ -20,7 +20,6 @@ class VeilidChatApp extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
final localizationDelegate = LocalizedApp.of(context).delegate;
|
final localizationDelegate = LocalizedApp.of(context).delegate;
|
||||||
|
|
||||||
return ThemeProvider(
|
return ThemeProvider(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -83,10 +84,16 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||||||
Future<void> Function({required Uint8List inviteData})
|
Future<void> Function({required Uint8List inviteData})
|
||||||
validateInviteData) {
|
validateInviteData) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
//final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
final textTheme = theme.textTheme;
|
//final textTheme = theme.textTheme;
|
||||||
//final height = MediaQuery.of(context).size.height;
|
//final height = MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
|
final monoStyle = TextStyle(
|
||||||
|
fontFamily: 'Source Code Pro',
|
||||||
|
fontSize: 11,
|
||||||
|
color: scale.primaryScale.text,
|
||||||
|
);
|
||||||
|
|
||||||
return Column(mainAxisSize: MainAxisSize.min, children: [
|
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
Text(
|
Text(
|
||||||
translate('paste_invite_dialog.paste_invite_here'),
|
translate('paste_invite_dialog.paste_invite_here'),
|
||||||
@ -97,8 +104,7 @@ class PasteInviteDialogState extends ConsumerState<PasteInviteDialog> {
|
|||||||
enabled: !dialogState.isValidating,
|
enabled: !dialogState.isValidating,
|
||||||
onChanged: (text) async =>
|
onChanged: (text) async =>
|
||||||
_onPasteChanged(text, validateInviteData),
|
_onPasteChanged(text, validateInviteData),
|
||||||
style: textTheme.labelSmall!
|
style: monoStyle,
|
||||||
.copyWith(fontFamily: 'Victor Mono', fontSize: 11),
|
|
||||||
keyboardType: TextInputType.multiline,
|
keyboardType: TextInputType.multiline,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
controller: _pasteTextController,
|
controller: _pasteTextController,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
import 'package:signal_strength_indicator/signal_strength_indicator.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../providers/connection_state.dart';
|
import '../providers/connection_state.dart';
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
@ -48,13 +49,17 @@ class SignalStrengthMeterWidget extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
inactiveColor = scale.grayScale.subtleText;
|
inactiveColor = scale.grayScale.subtleText;
|
||||||
|
|
||||||
return SignalStrengthIndicator.bars(
|
return GestureDetector(
|
||||||
value: value,
|
onLongPress: () async {
|
||||||
activeColor: color,
|
await context.push('/developer');
|
||||||
inactiveColor: inactiveColor,
|
},
|
||||||
size: iconSize,
|
child: SignalStrengthIndicator.bars(
|
||||||
barCount: 4,
|
value: value,
|
||||||
spacing: 1,
|
activeColor: color,
|
||||||
);
|
inactiveColor: inactiveColor,
|
||||||
|
size: iconSize,
|
||||||
|
barCount: 4,
|
||||||
|
spacing: 1,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
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';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'providers/window_control.dart';
|
import 'providers/window_control.dart';
|
||||||
@ -22,6 +24,9 @@ void main() async {
|
|||||||
debugPrint('VeilidChat PID: $pid');
|
debugPrint('VeilidChat PID: $pid');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ansi colors
|
||||||
|
ansiColorDisabled = false;
|
||||||
|
|
||||||
// Logs
|
// Logs
|
||||||
initLoggy();
|
initLoggy();
|
||||||
|
|
||||||
@ -36,13 +41,18 @@ void main() async {
|
|||||||
// Make localization delegate
|
// Make localization delegate
|
||||||
final delegate = await LocalizationDelegate.create(
|
final delegate = await LocalizationDelegate.create(
|
||||||
fallbackLocale: 'en_US', supportedLocales: ['en_US']);
|
fallbackLocale: 'en_US', supportedLocales: ['en_US']);
|
||||||
|
await initializeDateFormatting();
|
||||||
|
|
||||||
// Start up Veilid and Veilid processor in the background
|
// Start up Veilid and Veilid processor in the background
|
||||||
unawaited(initializeVeilid());
|
unawaited(initializeVeilid());
|
||||||
|
|
||||||
// Run the app
|
// Run the app
|
||||||
// Hot reloads will only restart this part, not Veilid
|
// Hot reloads will only restart this part, not Veilid
|
||||||
runApp(ProviderScope(
|
runZonedGuarded(() {
|
||||||
observers: const [StateLogger()],
|
runApp(ProviderScope(
|
||||||
child: LocalizedApp(delegate, VeilidChatApp(theme: initTheme))));
|
observers: const [StateLogger()],
|
||||||
|
child: LocalizedApp(delegate, VeilidChatApp(theme: initTheme))));
|
||||||
|
}, (error, stackTrace) {
|
||||||
|
log.error('Dart Runtime: {$error}\n{$stackTrace}');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
256
lib/pages/developer.dart
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
import 'package:ansicolor/ansicolor.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/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:loggy/loggy.dart';
|
||||||
|
import 'package:quickalert/quickalert.dart';
|
||||||
|
import 'package:xterm/xterm.dart';
|
||||||
|
|
||||||
|
import '../tools/tools.dart';
|
||||||
|
import '../veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
|
final globalDebugTerminal = Terminal(
|
||||||
|
maxLines: 50000,
|
||||||
|
);
|
||||||
|
|
||||||
|
const kDefaultTerminalStyle = TerminalStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
// height: 1.2,
|
||||||
|
fontFamily: 'Source Code Pro');
|
||||||
|
|
||||||
|
class DeveloperPage extends ConsumerStatefulWidget {
|
||||||
|
const DeveloperPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
DeveloperPageState createState() => DeveloperPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeveloperPageState extends ConsumerState<DeveloperPage> {
|
||||||
|
final _terminalController = TerminalController();
|
||||||
|
final _debugCommandController = TextEditingController();
|
||||||
|
final _logLevelController = DropdownController(duration: 250.ms);
|
||||||
|
final List<CoolDropdownItem<LogLevel>> _logLevelDropdownItems = [];
|
||||||
|
var _logLevelDropDown = log.level.logLevel;
|
||||||
|
var _showEllet = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_terminalController.addListener(() {
|
||||||
|
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 _debugOut(String out) {
|
||||||
|
final pen = AnsiPen()..cyan(bold: true);
|
||||||
|
final colorOut = pen(out);
|
||||||
|
debugPrint(colorOut);
|
||||||
|
globalDebugTerminal.write(colorOut.replaceAll('\n', '\r\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sendDebugCommand(String debugCommand) async {
|
||||||
|
if (debugCommand == 'ellet') {
|
||||||
|
setState(() {
|
||||||
|
_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 {
|
||||||
|
final selection = _terminalController.selection;
|
||||||
|
if (selection != null) {
|
||||||
|
final text = globalDebugTerminal.buffer.getText(selection);
|
||||||
|
_terminalController.clearSelection();
|
||||||
|
await Clipboard.setData(ClipboardData(text: text));
|
||||||
|
if (context.mounted) {
|
||||||
|
showInfoToast(context, translate('developer.copied'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final textTheme = theme.textTheme;
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
// if (!_isScrolling && _wantsBottom) {
|
||||||
|
// _scrollToBottom();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(Icons.arrow_back, color: scale.primaryScale.text),
|
||||||
|
onPressed: () => GoRouterHelper(context).pop(),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.copy),
|
||||||
|
color: scale.primaryScale.text,
|
||||||
|
disabledColor: scale.grayScale.subtleText,
|
||||||
|
onPressed: _terminalController.selection == null
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
await copySelection(context);
|
||||||
|
}),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.clear_all),
|
||||||
|
color: scale.primaryScale.text,
|
||||||
|
disabledColor: scale.grayScale.subtleText,
|
||||||
|
onPressed: () async {
|
||||||
|
await QuickAlert.show(
|
||||||
|
context: context,
|
||||||
|
type: QuickAlertType.confirm,
|
||||||
|
title: translate('developer.are_you_sure_clear'),
|
||||||
|
textColor: scale.primaryScale.text,
|
||||||
|
confirmBtnColor: scale.primaryScale.elementBackground,
|
||||||
|
backgroundColor: scale.primaryScale.subtleBackground,
|
||||||
|
headerBackgroundColor: scale.primaryScale.background,
|
||||||
|
confirmBtnText: translate('button.ok'),
|
||||||
|
cancelBtnText: translate('button.cancel'),
|
||||||
|
onConfirmBtnTap: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (context.mounted) {
|
||||||
|
await clear(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
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'),
|
||||||
|
style:
|
||||||
|
textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.bold)),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(children: [
|
||||||
|
Stack(alignment: AlignmentDirectional.center, children: [
|
||||||
|
Image.asset('assets/images/ellet.png'),
|
||||||
|
TerminalView(globalDebugTerminal,
|
||||||
|
textStyle: kDefaultTerminalStyle,
|
||||||
|
controller: _terminalController,
|
||||||
|
//autofocus: true,
|
||||||
|
backgroundOpacity: _showEllet ? 0.75 : 1.0,
|
||||||
|
onSecondaryTapDown: (details, offset) async {
|
||||||
|
await copySelection(context);
|
||||||
|
})
|
||||||
|
]).expanded(),
|
||||||
|
TextField(
|
||||||
|
controller: _debugCommandController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(8, 2, 8, 2),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
borderSide: BorderSide(color: scale.primaryScale.border)),
|
||||||
|
fillColor: scale.primaryScale.subtleBackground,
|
||||||
|
hintText: translate('developer.command'),
|
||||||
|
suffixIcon: IconButton(
|
||||||
|
icon: const Icon(Icons.send),
|
||||||
|
onPressed: _debugCommandController.text.isEmpty
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final debugCommand = _debugCommandController.text;
|
||||||
|
_debugCommandController.clear();
|
||||||
|
await _sendDebugCommand(debugCommand);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
onChanged: (_) {
|
||||||
|
setState(() => {});
|
||||||
|
},
|
||||||
|
onSubmitted: (debugCommand) async {
|
||||||
|
_debugCommandController.clear();
|
||||||
|
await _sendDebugCommand(debugCommand);
|
||||||
|
},
|
||||||
|
).paddingAll(4)
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties
|
||||||
|
..add(DiagnosticsProperty<TerminalController>(
|
||||||
|
'terminalController', _terminalController))
|
||||||
|
..add(
|
||||||
|
DiagnosticsProperty<LogLevel>('logLevelDropDown', _logLevelDropDown));
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,13 @@ class IndexPage extends ConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
ref.watch(windowControlProvider);
|
ref.watch(windowControlProvider);
|
||||||
|
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final textTheme = theme.textTheme;
|
||||||
|
final monoTextStyle = textTheme.labelSmall!
|
||||||
|
.copyWith(fontFamily: 'Source Code Pro', fontSize: 11);
|
||||||
|
final emojiTextStyle = textTheme.labelSmall!
|
||||||
|
.copyWith(fontFamily: 'Noto Color Emoji', fontSize: 11);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: DecoratedBox(
|
body: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -28,6 +35,11 @@ class IndexPage extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
// Hack to preload fonts
|
||||||
|
Offstage(child: Text('🧱', style: emojiTextStyle)),
|
||||||
|
// Hack to preload fonts
|
||||||
|
Offstage(child: Text('A', style: monoTextStyle)),
|
||||||
|
// Splash Screen
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: SvgPicture.asset(
|
child: SvgPicture.asset(
|
||||||
|
@ -21,8 +21,8 @@ import '../../entities/local_account.dart';
|
|||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../../veilid_support/veilid_support.dart';
|
import '../../veilid_support/veilid_support.dart';
|
||||||
import 'account_page.dart';
|
import 'account.dart';
|
||||||
import 'chats_page.dart';
|
import 'chats.dart';
|
||||||
|
|
||||||
class MainPager extends ConsumerStatefulWidget {
|
class MainPager extends ConsumerStatefulWidget {
|
||||||
const MainPager(
|
const MainPager(
|
||||||
|
@ -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();
|
||||||
|
@ -2,7 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import '../pages/chat_only_page.dart';
|
import '../pages/chat_only.dart';
|
||||||
|
import '../pages/developer.dart';
|
||||||
import '../pages/home.dart';
|
import '../pages/home.dart';
|
||||||
import '../pages/index.dart';
|
import '../pages/index.dart';
|
||||||
import '../pages/new_account.dart';
|
import '../pages/new_account.dart';
|
||||||
@ -91,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';
|
||||||
}
|
}
|
||||||
@ -126,6 +129,10 @@ class RouterNotifier extends _$RouterNotifier implements Listenable {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/developer',
|
||||||
|
builder: (context, state) => const DeveloperPage(),
|
||||||
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -6,7 +6,7 @@ part of 'router_notifier.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$routerNotifierHash() => r'b7c49cdc6f940c5a5032faaf19af08d9f478dae6';
|
String _$routerNotifierHash() => r'5a3527e3890f0746db4cbe051d453b89e5809989';
|
||||||
|
|
||||||
/// See also [RouterNotifier].
|
/// See also [RouterNotifier].
|
||||||
@ProviderFor(RouterNotifier)
|
@ProviderFor(RouterNotifier)
|
||||||
|
@ -2,8 +2,11 @@ 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:loggy/loggy.dart';
|
import 'package:loggy/loggy.dart';
|
||||||
|
|
||||||
|
import '../pages/developer.dart';
|
||||||
import '../veilid_support/veilid_support.dart';
|
import '../veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
String wrapWithLogColor(LogLevel? level, String text) {
|
String wrapWithLogColor(LogLevel? level, String text) {
|
||||||
@ -47,14 +50,57 @@ String wrapWithLogColor(LogLevel? level, String text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final DateFormat _dateFormatter = DateFormat('HH:mm:ss.SSS');
|
||||||
|
|
||||||
extension PrettyPrintLogRecord on LogRecord {
|
extension PrettyPrintLogRecord on LogRecord {
|
||||||
String pretty() {
|
String pretty() {
|
||||||
final lstr =
|
final tm = _dateFormatter.format(time.toLocal());
|
||||||
wrapWithLogColor(level, '[${level.toString().substring(0, 1)}]');
|
final lev = logLevelEmoji(level);
|
||||||
return '$lstr $message';
|
final lstr = wrapWithLogColor(level, tm);
|
||||||
|
return '$lstr $lev $message';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
case traceLevel:
|
||||||
|
return '👾';
|
||||||
|
case LogLevel.debug:
|
||||||
|
return '🐛';
|
||||||
|
case LogLevel.info:
|
||||||
|
return '💡';
|
||||||
|
case LogLevel.warning:
|
||||||
|
return '🍋';
|
||||||
|
case LogLevel.error:
|
||||||
|
return '🛑';
|
||||||
|
}
|
||||||
|
return '❓';
|
||||||
|
}
|
||||||
|
|
||||||
class CallbackPrinter extends LoggyPrinter {
|
class CallbackPrinter extends LoggyPrinter {
|
||||||
CallbackPrinter() : super();
|
CallbackPrinter() : super();
|
||||||
|
|
||||||
@ -62,7 +108,9 @@ class CallbackPrinter extends LoggyPrinter {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void onLog(LogRecord record) {
|
void onLog(LogRecord record) {
|
||||||
debugPrint(record.pretty());
|
final out = record.pretty();
|
||||||
|
debugPrint(out);
|
||||||
|
globalDebugTerminal.write('$out\n'.replaceAll('\n', '\r\n'));
|
||||||
callback?.call(record);
|
callback?.call(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ Future<VeilidConfig> getVeilidChatConfig() async {
|
|||||||
|
|
||||||
return config.copyWith(
|
return config.copyWith(
|
||||||
capabilities: const VeilidConfigCapabilities(disable: ['DHTV', 'TUNL']),
|
capabilities: const VeilidConfigCapabilities(disable: ['DHTV', 'TUNL']),
|
||||||
|
protectedStore: config.protectedStore.copyWith(allowInsecureFallback: true),
|
||||||
// network: config.network.copyWith(
|
// network: config.network.copyWith(
|
||||||
// dht: config.network.dht.copyWith(
|
// dht: config.network.dht.copyWith(
|
||||||
// getValueCount: 3,
|
// getValueCount: 3,
|
||||||
|
24
pubspec.lock
@ -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:
|
||||||
@ -973,6 +981,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
|
platform_info:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform_info
|
||||||
|
sha256: "012e73712166cf0b56d3eb95c0d33491f56b428c169eca385f036448474147e4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
plugin_platform_interface:
|
plugin_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1601,6 +1617,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.3.0"
|
||||||
|
xterm:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: xterm
|
||||||
|
sha256: "6a02b15d03152b8186e12790902ff28c8a932fc441e89fa7255a7491661a8e69"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.5.0"
|
||||||
yaml:
|
yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -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
|
||||||
@ -71,6 +72,7 @@ dependencies:
|
|||||||
# veilid: ^0.0.1
|
# veilid: ^0.0.1
|
||||||
path: ../veilid/veilid-flutter
|
path: ../veilid/veilid-flutter
|
||||||
window_manager: ^0.3.5
|
window_manager: ^0.3.5
|
||||||
|
xterm: ^3.5.0
|
||||||
zxing2: ^0.2.0
|
zxing2: ^0.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
@ -118,11 +120,14 @@ 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: Victor Mono
|
- family: Source Code Pro
|
||||||
fonts:
|
fonts:
|
||||||
- asset: assets/fonts/VictorMono-VariableFont_wght.ttf
|
- asset: assets/fonts/SourceCodePro-Regular.ttf
|
||||||
|
- asset: assets/fonts/SourceCodePro-Bold.ttf
|
||||||
|
weight: 700
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||||
|