developer menu

This commit is contained in:
Christien Rioux 2023-10-09 16:52:37 -04:00
parent 8075b81742
commit ee94f6622c
41 changed files with 294 additions and 35 deletions

View File

@ -62,6 +62,7 @@ android {
signingConfig signingConfigs.debug signingConfig signingConfigs.debug
} }
} }
namespace 'com.veilid.veilidchat'
} }
flutter { flutter {

View File

@ -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.

View File

@ -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">
<application <application
android:label="VeilidChat" android:label="VeilidChat"
android:name="${applicationName}" android:name="${applicationName}"

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFFFF</color>
</resources>

View File

@ -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.

View File

@ -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"
} }
} }

View File

@ -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

Binary file not shown.

Binary file not shown.

View File

@ -159,5 +159,10 @@
"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"
} }
} }

Binary file not shown.

View 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(

View File

@ -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,

View File

@ -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(
onLongPress: () async {
await context.push('/developer');
},
child: SignalStrengthIndicator.bars(
value: value, value: value,
activeColor: color, activeColor: color,
inactiveColor: inactiveColor, inactiveColor: inactiveColor,
size: iconSize, size: iconSize,
barCount: 4, barCount: 4,
spacing: 1, spacing: 1,
); ));
} }
} }

View File

@ -5,6 +5,7 @@ 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';
@ -36,13 +37,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
runZonedGuarded(() {
runApp(ProviderScope( runApp(ProviderScope(
observers: const [StateLogger()], observers: const [StateLogger()],
child: LocalizedApp(delegate, VeilidChatApp(theme: initTheme)))); child: LocalizedApp(delegate, VeilidChatApp(theme: initTheme))));
}, (error, stackTrace) {
log.error('Dart Runtime: {$error}\n{$stackTrace}');
});
} }

166
lib/pages/developer.dart Normal file
View File

@ -0,0 +1,166 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.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 TextEditingController _debugCommandController = TextEditingController();
@override
void initState() {
// _scrollController = ScrollController(
// onAttach: _handlePositionAttach,
// onDetach: _handlePositionDetach,
// );
super.initState();
terminalController.addListener(() {
setState(() {});
});
}
// void _handleScrollChange() {
// if (_isScrolling != _scrollController.position.isScrollingNotifier.value) {
// _isScrolling = _scrollController.position.isScrollingNotifier.value;
// _wantsBottom = _scrollController.position.pixels ==
// _scrollController.position.maxScrollExtent;
// }
// }
// 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 {
log.info('DEBUG >>>\n$debugCommand');
final out = await Veilid.instance.debug(debugCommand);
log.info('<<< DEBUG\n$out');
}
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);
})
],
title: Text(translate('developer.title')),
centerTitle: true,
),
body: Column(children: [
TerminalView(
globalDebugTerminal,
textStyle: kDefaultTerminalStyle,
controller: terminalController,
//autofocus: true,
//backgroundOpacity: 0.9,
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: () async {
final debugCommand = _debugCommandController.text;
_debugCommandController.clear();
await _sendDebugCommand(debugCommand);
},
)),
onSubmitted: (debugCommand) async {
_debugCommandController.clear();
await _sendDebugCommand(debugCommand);
},
).paddingAll(4)
]));
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<TerminalController>(
'terminalController', terminalController));
}
}

View File

@ -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(

View File

@ -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(

View File

@ -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';
@ -126,6 +127,10 @@ class RouterNotifier extends _$RouterNotifier implements Listenable {
), ),
], ],
), ),
GoRoute(
path: '/developer',
builder: (context, state) => const DeveloperPage(),
)
]; ];
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////

View File

@ -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)

View File

@ -2,8 +2,10 @@ 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: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 +49,33 @@ 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 = logEmoji(level);
return '$lstr $message'; final lstr = wrapWithLogColor(level, tm);
return '$lstr $lev $message';
} }
} }
String logEmoji(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 +83,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);
} }

View File

@ -36,7 +36,7 @@ void _initVeilid() {
const platformConfig = VeilidFFIConfig( const platformConfig = VeilidFFIConfig(
logging: VeilidFFIConfigLogging( logging: VeilidFFIConfigLogging(
terminal: VeilidFFIConfigLoggingTerminal( terminal: VeilidFFIConfigLoggingTerminal(
enabled: true, enabled: false,
level: VeilidConfigLogLevel.debug, level: VeilidConfigLogLevel.debug,
), ),
otlp: VeilidFFIConfigLoggingOtlp( otlp: VeilidFFIConfigLoggingOtlp(

View File

@ -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,

View File

@ -973,6 +973,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 +1609,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:

View File

@ -71,6 +71,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:
@ -120,9 +121,11 @@ flutter:
- assets/images/vlogo.svg - assets/images/vlogo.svg
# 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