refactor state

lolnode
fuwa 4 years ago
parent 2aa380a143
commit 965bd60d91

@ -38,3 +38,9 @@ const stdoutLineBufferSize = 200;
const bannerShownKey = 'banner-shown';
const int maxPoolTxSize = 5000;
const int forcedUpdateInSeconds = 3;
const int processTimeoutInSeconds = 4;
const int processKillWaitingInSeconds = 2;
const int logLines = 200;
const int defaultPageIndex = 1;

@ -1,87 +0,0 @@
/*
Copyright 2019 fuwa
This file is part of CyberWOW.
CyberWOW is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CyberWOW is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
*/
import 'package:flutter/material.dart';
import 'prototype.dart';
// const crtGreen = Color.fromRGBO(0, 255, 102, 1);
const crtGreen = Color.fromRGBO(51, 255, 51, 0.9);
final _theme = ThemeData(
brightness: Brightness.dark,
primaryColor: crtGreen,
hintColor: Colors.yellow,
accentColor: crtGreen,
cursorColor: crtGreen,
backgroundColor: Colors.black,
scaffoldBackgroundColor: Colors.black,
textTheme: TextTheme(
headline4: TextStyle(
fontFamily: 'RobotoMono',
fontSize: 35,
fontWeight: FontWeight.bold,
),
headline3: TextStyle(
fontFamily: 'RobotoMono',
fontSize: 22,
),
headline6: TextStyle(
fontFamily: 'VT323',
fontSize: 22,
),
subtitle1: TextStyle(
fontFamily: 'RobotoMono',
fontSize: 17,
fontWeight: FontWeight.bold,
),
bodyText2: TextStyle(
fontFamily: 'VT323',
fontSize: 17,
height: 1,
),
bodyText1: TextStyle(
fontFamily: 'RobotoMono',
fontSize: 12.5,
),
).apply(
bodyColor: crtGreen,
displayColor: crtGreen,
),
);
final config = CryptoConfig(
'liblolnerod.so',
'wownerod',
'Is this a test, sir?',
70,
_theme,
45679,
[
'--log-file=/dev/null',
'--max-log-file-size=0',
'--p2p-use-ipv6',
'--hide-my-port',
'--add-exclusive-node=192.168.10.100',
],
'[1337@lol]: ',
6,
);

@ -24,7 +24,7 @@ import 'package:flutter/material.dart';
import 'prototype.dart';
// const crtGreen = Color.fromRGBO(0, 255, 102, 1);
const crtGreen = Color.fromRGBO(51, 255, 51, 0.9);
const crtGreen = Color.fromRGBO(51, 255, 51, 1);
final _theme = ThemeData(
brightness: Brightness.dark,

@ -31,8 +31,7 @@ class CryptoConfig {
final List<String> extraArgs;
final String promptString;
final int hashViewBlockLength;
const CryptoConfig
(
const CryptoConfig(
this.outputBin,
this.appPath,
this.splash,

@ -32,12 +32,10 @@ import '../../sensor/helper.dart' as helper;
typedef ShouldExit = bool Function();
Stream<String> runBinary(
Future<Process> runBinary(
final String name, {
final Stream<String> input,
final ShouldExit shouldExit,
final List<String> userArgs = const [],
}) async* {
}) async {
final binPath = await helper.getBinaryPath(name);
final appDocDir = await getApplicationDocumentsDirectory();
@ -61,37 +59,5 @@ Stream<String> runBinary(
log.info('args: ' + args.toString());
final outputProcess = await Process.start(binPath, args);
Future<void> printInput() async {
await for (final line in input) {
log.finest('process input: ' + line);
outputProcess.stdin.writeln(line);
outputProcess.stdin.flush();
}
}
if (input != null) {
printInput();
}
final _stdout = outputProcess.stdout
.transform(utf8.decoder)
.transform(const LineSplitter());
await for (final line in _stdout) {
log.finest('process output: ' + line);
yield line;
}
if (config.isEmu) return;
if (shouldExit != null) {
if (!shouldExit()) {
log.warning('process is ded');
exit(1);
}
}
log.info('Daemon exited gracefully.');
return Process.start(binPath, args);
}

@ -1,44 +0,0 @@
/*
Copyright 2019 fuwa
This file is part of CyberWOW.
CyberWOW is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CyberWOW is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:ui';
import 'dart:async';
import '../../logging.dart';
import '../../helper.dart';
typedef GetNotificationFunc = AppLifecycleState Function();
Stream<Null> pull(
GetNotificationFunc getNotification, final String puller) async* {
while (true) {
final _appState = getNotification();
log.finer('refresh pull by $puller: app state: $_appState');
if (_appState == AppLifecycleState.resumed) {
yield null;
await tick();
} else {
await tick();
await tick();
}
}
}

@ -26,9 +26,9 @@ import 'package:logging/logging.dart';
import 'dart:io';
import 'dart:async';
import 'dart:collection';
import 'config.dart' as config;
import 'logic/controller/process/run.dart' as process;
import 'logging.dart';
import 'state.dart' as state;
import 'widget.dart' as widget;
@ -109,31 +109,7 @@ class _LNodePageState extends State<LNodePage> with WidgetsBindingObserver {
}
Future<void> buildStateMachine(final state.BlankState _blankState) async {
final loadingText = config.c.splash;
state.LoadingState _loadingState = await _blankState.next(loadingText);
state.SyncingState _syncingState = await _loadingState.next();
final _initialIntent = await getInitialIntent();
final _userArgs = _initialIntent
.trim()
.split(RegExp(r"\s+"))
.where((x) => x.isNotEmpty)
.toList();
if (_userArgs.isNotEmpty) {
log.info('user args: $_userArgs');
}
final syncing = process
.runBinary(
config.c.outputBin,
input: _inputStreamController.stream,
shouldExit: _isExiting,
userArgs: _userArgs,
)
.asBroadcastStream();
await _syncingState.next(_inputStreamController.sink, syncing);
_setState(_blankState);
bool exited = false;
bool validState = true;
@ -148,14 +124,13 @@ class _LNodePageState extends State<LNodePage> with WidgetsBindingObserver {
}
break;
case state.BlankState:
case state.LoadingState:
case state.SyncingState:
case state.SyncedState:
await (_state as state.SyncedState).next();
break;
case state.ReSyncingState:
await (_state as state.ReSyncingState).next();
_setState(await (_state as state.AppStateAutomata).next());
break;
default:
validState = false;
}
@ -165,7 +140,6 @@ class _LNodePageState extends State<LNodePage> with WidgetsBindingObserver {
if (exited) {
log.finer('popping navigator');
// SystemNavigator.pop();
exit(0);
} else {
log.severe('Reached invalid state!');
@ -180,8 +154,14 @@ class _LNodePageState extends State<LNodePage> with WidgetsBindingObserver {
WidgetsBinding.instance.addObserver(this);
final state.AppHook _appHook =
state.AppHook(_setState, _getNotification, _isExiting);
final state.AppHook _appHook = state.AppHook(
_setState,
_getNotification,
_isExiting,
getInitialIntent,
null,
Queue(),
);
final state.BlankState _blankState = state.BlankState(_appHook);
_state = _blankState;

@ -21,14 +21,18 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import '../logging.dart';
import 'prototype.dart';
import 'loading.dart';
class BlankState extends AppState {
BlankState(appHook) : super (appHook);
class BlankState extends AppStateAutomata {
BlankState(appHook) : super(appHook);
Future<LoadingState> next(String status) async {
LoadingState _next = LoadingState(appHook, status);
return moveState(_next);
Future<AppStateAutomata> next() async {
if (await shouldExit()) return exitState();
log.fine('blank state next');
return LoadingState(appHook);
}
}

@ -19,41 +19,33 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:collection';
import 'dart:io';
import '../config.dart' as config;
import '../logging.dart';
import 'prototype.dart';
class ExitingState extends AppState {
final Queue<String> stdout;
final Stream<String> processOutput;
ExitingState(appHook, this.stdout, this.processOutput) : super (appHook);
void append(final String msg) {
stdout.addLast(msg);
while (stdout.length > config.stdoutLineBufferSize) {
stdout.removeFirst();
}
syncState();
}
class ExitingState extends AppStateAutomata {
ExitingState(appHook) : super(appHook);
Future<void> wait() async {
log.finer("Exiting wait");
Future<void> printStdout() async {
await for (final line in processOutput) {
log.finer('exiting: print stdout loop');
append(line);
log.info(line);
}
if (appHook.process != null) {
log.fine('exiting state: killing process');
appHook.process.kill();
await appHook.process.exitCode.timeout(
Duration(seconds: config.processKillWaitingInSeconds),
onTimeout: () {
log.warning('process exitCode timeout');
appHook.process.kill(ProcessSignal.sigkill);
return -1;
},
);
}
await printStdout();
log.finer('exiting state done');
}
Future<AppStateAutomata> next() async {
return null;
}
}

@ -20,18 +20,25 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
*/
import 'package:shared_preferences/shared_preferences.dart';
import 'package:async/async.dart';
import 'dart:async';
import 'dart:core';
import 'dart:convert';
import 'dart:collection';
import '../config.dart' as config;
import '../helper.dart';
import '../logic/controller/process/run.dart' as process;
import '../logging.dart';
import 'prototype.dart';
import 'syncing.dart';
class LoadingState extends AppState {
final String banner;
class LoadingState extends AppStateAutomata {
String status = '';
LoadingState(appHook, this.banner) : super(appHook);
LoadingState(appHook) : super(appHook);
void append(final String msg) {
this.status += msg;
@ -41,11 +48,12 @@ class LoadingState extends AppState {
Future<SyncingState> next() async {
Future<void> showBanner() async {
final Iterable<String> chars =
banner.runes.map((x) => String.fromCharCode(x));
config.c.splash.runes.map((x) => String.fromCharCode(x));
for (final String char in chars) {
append(char);
await Future.delayed(Duration(milliseconds: config.c.splashDelay));
final int _delay = config.c.splashDelay;
await Future.delayed(Duration(milliseconds: _delay));
}
await tick();
@ -60,7 +68,50 @@ class LoadingState extends AppState {
await _prefs.setBool(config.bannerShownKey, true);
}
SyncingState _next = SyncingState(appHook);
return moveState(_next);
final _initialIntent = await appHook.getInitialIntent();
final _userArgs = _initialIntent
.trim()
.split(RegExp(r"\s+"))
.where((x) => x.isNotEmpty)
.toList();
if (_userArgs.isNotEmpty) {
log.info('user args: $_userArgs');
}
final _process = await process.runBinary(
config.c.outputBin,
userArgs: _userArgs,
);
log.fine('process created');
final _stdout = StreamGroup.merge([_process.stdout, _process.stderr]
.map((x) => x.transform(utf8.decoder).transform(LineSplitter())))
.asBroadcastStream();
final _stdoutQueue = Queue();
AppHook _appHook = AppHook(
appHook.setState,
appHook.getNotification,
appHook.isExiting,
appHook.getInitialIntent,
_process,
_stdoutQueue,
);
_stdout.listen((x) {
log.fine(x);
_stdoutQueue.addLast(x);
while (_stdoutQueue.length > config.logLines) {
_stdoutQueue.removeFirst();
}
_appHook.stdoutCache = _stdoutQueue.join('\n');
});
_process.exitCode.whenComplete(() => _appHook.processCompleted = true);
return SyncingState(_appHook);
}
}

@ -21,27 +21,88 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
import 'package:flutter/material.dart';
import 'dart:collection';
import 'dart:io';
import '../config.dart' as config;
import 'exiting.dart';
typedef SetStateFunc = void Function(AppState);
typedef GetNotificationFunc = AppLifecycleState Function();
typedef IsExitingFunc = bool Function();
typedef GetInitialIntentFunc = Future<String> Function();
class AppHook {
final SetStateFunc setState;
final GetNotificationFunc getNotification;
final IsExitingFunc isExiting;
AppHook(this.setState, this.getNotification, this.isExiting);
final GetInitialIntentFunc getInitialIntent;
final Process process;
final Queue stdout;
String stdoutCache;
bool processCompleted;
AppHook(
this.setState,
this.getNotification,
this.isExiting,
this.getInitialIntent,
this.process,
this.stdout, {
this.stdoutCache = '',
this.processCompleted = false,
});
}
class AppState {
final AppHook appHook;
int skipped = 0;
AppState(this.appHook);
syncState() {
appHook.setState(this);
Future<bool> shouldExit() async {
final _isExiting = appHook.isExiting();
if (_isExiting) return true;
if (appHook.process != null && appHook.processCompleted) {
final _exitCode = await appHook.process.exitCode;
return _exitCode != 0;
}
return false;
}
AppState moveState(AppState _next) {
appHook.setState(_next);
return _next;
ExitingState exitState() => ExitingState(appHook);
bool isActive() {
final _appState = appHook.getNotification();
return _appState == AppLifecycleState.resumed;
}
bool shouldPop() => true;
bool shouldSkip() {
if (!isActive()) {
if (skipped < config.forcedUpdateInSeconds) {
skipped++;
return true;
} else {
skipped = 0;
return false;
}
} else {
skipped = 0;
return false;
}
}
void syncState() {
appHook.setState(this);
}
}
abstract class AppStateAutomata extends AppState {
AppStateAutomata(appHook) : super(appHook);
Future<AppStateAutomata> next();
}

@ -22,76 +22,38 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
import 'dart:async';
import 'dart:collection';
import '../logic/controller/refresh.dart' as refresh;
import '../logic/sensor/rpc/rpc.dart' as rpc;
import '../logic/sensor/daemon.dart' as daemon;
import '../config.dart' as config;
import '../logging.dart';
import '../helper.dart';
import 'prototype.dart';
import 'synced.dart';
import 'exiting.dart';
class ReSyncingState extends AppState {
final Queue<String> stdout;
final StreamSink<String> processInput;
final Stream<String> processOutput;
class ReSyncingState extends AppStateAutomata {
final int pageIndex;
bool synced = false;
ReSyncingState(appHook, this.stdout, this.processInput, this.processOutput,
this.pageIndex)
: super(appHook);
void append(final String msg) {
stdout.addLast(msg);
while (stdout.length > config.stdoutLineBufferSize) {
stdout.removeFirst();
}
syncState();
}
ReSyncingState(appHook, this.pageIndex) : super(appHook);
Future<AppState> next() async {
Future<AppStateAutomata> next() async {
log.fine("ReSyncing next");
if (await shouldExit()) return exitState();
Future<void> printStdout() async {
await for (final line in processOutput) {
if (synced) break;
// print('re-syncing: print stdout loop');
append(line);
log.info(line);
}
if (shouldSkip()) {
log.finest('skipping state update');
await tick();
return this;
}
Future<void> checkSync() async {
await for (final _
in refresh.pull(appHook.getNotification, 'ReSyncingState')) {
if (appHook.isExiting()) {
log.fine('ReSyncing state detected exiting');
break;
}
if (await daemon.isSynced()) {
synced = true;
break;
}
// print('re-syncing: checkSync loop');
}
if (await daemon.isSynced()) {
final int _height = await rpc.height();
return SyncedState(appHook, _height, pageIndex);
} else {
return this;
}
printStdout();
await checkSync();
if (appHook.isExiting()) {
ExitingState _next = ExitingState(appHook, stdout, processOutput);
return moveState(_next);
}
log.fine('resync: await exit');
SyncedState _next =
SyncedState(appHook, stdout, processInput, processOutput, pageIndex);
_next.height = await rpc.height();
return moveState(_next);
}
}

@ -20,13 +20,11 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
import '../config.dart' as config;
import '../logic/sensor/daemon.dart' as daemon;
import '../logic/controller/refresh.dart' as refresh;
import '../logic/sensor/rpc/rpc.dart' as rpc;
import '../logic/sensor/rpc/rpc2.dart' as rpc;
import '../logic/view/rpc/rpc2.dart' as rpc2View;
@ -36,119 +34,98 @@ import '../logging.dart';
import 'prototype.dart';
import 'resyncing.dart';
import 'exiting.dart';
class SyncedState extends AppState {
final Queue<String> stdout;
final StreamSink<String> processInput;
final Stream<String> processOutput;
class SyncedState extends AppStateAutomata {
final TextEditingController textController = TextEditingController();
int height;
bool synced = true;
bool userExit = false;
bool connected = true;
Map<String, dynamic> getInfo = {};
List<Map<String, dynamic>> getConnections = [];
List<Map<String, dynamic>> getTransactionPool = [];
int height;
int pageIndex;
String syncInfo = 'syncInfo';
PageController pageController;
String getInfoCache = '';
String getConnectionsCache = '';
String getTransactionPoolCache = '';
SyncedState(appHook, this.stdout, this.processInput, this.processOutput,
this.pageIndex)
: super(appHook) {
SyncedState(appHook, this.height, this.pageIndex) : super(appHook) {
pageController = PageController(initialPage: pageIndex);
// textController.addListener(this.appendInput);
}
void appendInput(final String line) {
stdout.addLast(config.c.promptString + line);
void appendInput(final String x) {
final _input = config.c.promptString + x;
log.fine(_input);
final _stdoutQueue = appHook.stdout;
_stdoutQueue.addLast('\n' + _input);
while (_stdoutQueue.length > config.logLines) {
_stdoutQueue.removeFirst();
}
appHook.stdoutCache = _stdoutQueue.join();
final _stdin = appHook.process.stdin;
_stdin.writeln(x);
_stdin.flush();
syncState();
processInput.add(line);
if (line == 'exit') {
if (x == 'exit') {
userExit = true;
}
}
void append(final String msg) {
stdout.addLast(msg);
while (stdout.length > config.stdoutLineBufferSize) {
stdout.removeFirst();
}
syncState();
}
void onPageChanged(int value) {
this.pageIndex = value;
}
Future<AppState> next() async {
Future<AppStateAutomata> next() async {
log.fine("Synced next");
Future<void> logStdout() async {
await for (final line in processOutput) {
if (!synced) break;
// print('synced: print stdout loop');
append(line);
log.info(line);
}
if (userExit) {
return exitState();
}
logStdout();
Future<void> checkSync() async {
await for (final _
in refresh.pull(appHook.getNotification, 'syncedState')) {
if (appHook.isExiting() || userExit) {
log.fine('Synced state detected exiting');
break;
}
if (await daemon.isNotSynced()) {
synced = false;
break;
}
// log.finer('SyncedState: checkSync loop');
height = await rpc.height();
connected = await daemon.isConnected();
getInfo = await rpc.getInfoSimple();
final _getInfoView = cleanKey(rpcView.getInfoView(getInfo));
getInfoCache = pretty(_getInfoView);
getConnections = await rpc.getConnectionsSimple();
final List<Map<String, dynamic>> _getConnectionsView = getConnections
.map(rpcView.getConnectionView)
.map((x) => rpcView.simpleHeight(height, x))
.map(cleanKey)
.toList();
getConnectionsCache = pretty(_getConnectionsView);
getTransactionPool = await rpc.getTransactionPoolSimple();
final List<Map<String, dynamic>> _getTransactionPoolView =
getTransactionPool.map(rpc2View.txView).map(cleanKey).toList();
getTransactionPoolCache = pretty(_getTransactionPoolView);
syncState();
}
if (await shouldExit()) {
return exitState();
}
await checkSync();
if (shouldSkip()) {
log.finest('skipping state update');
await tick();
return this;
}
if (appHook.isExiting() || userExit) {
ExitingState _next = ExitingState(appHook, stdout, processOutput);
return moveState(_next);
if (await daemon.isNotSynced()) {
return ReSyncingState(appHook, pageIndex);
}
// log.finer('SyncedState: checkSync loop');
height = await rpc.height();
connected = await daemon.isConnected();
getInfo = await rpc.getInfoSimple();
final _getInfoView = cleanKey(rpcView.getInfoView(getInfo));
getInfoCache = pretty(_getInfoView);
getConnections = await rpc.getConnectionsSimple();
final List<Map<String, dynamic>> _getConnectionsView = getConnections
.map(rpcView.getConnectionView)
.map((x) => rpcView.simpleHeight(height, x))
.map(cleanKey)
.toList();
getConnectionsCache = pretty(_getConnectionsView);
getTransactionPool = await rpc.getTransactionPoolSimple();
final List<Map<String, dynamic>> _getTransactionPoolView =
getTransactionPool.map(rpc2View.txView).map(cleanKey).toList();
getTransactionPoolCache = pretty(_getTransactionPoolView);
log.fine('synced: loop exit');
ReSyncingState _next =
ReSyncingState(appHook, stdout, processInput, processOutput, pageIndex);
return moveState(_next);
return this;
}
}

@ -20,90 +20,34 @@ along with CyberWOW. If not, see <https://www.gnu.org/licenses/>.
*/
import 'dart:async';
import 'dart:collection';
import '../logic/sensor/rpc/rpc.dart' as rpc;
import '../logic/sensor/daemon.dart' as daemon;
import '../logic/controller/refresh.dart' as refresh;
import '../config.dart' as config;
import '../logging.dart';
import '../helper.dart';
import '../config.dart' as config;
import 'prototype.dart';
import 'synced.dart';
import 'exiting.dart';
class SyncingState extends AppState {
final Queue<String> stdout = Queue();
bool synced = false;
class SyncingState extends AppStateAutomata {
SyncingState(appHook) : super(appHook);
void append(final String msg) {
stdout.addLast(msg);
while (stdout.length > config.stdoutLineBufferSize) {
stdout.removeFirst();
}
syncState();
}
Future<AppState> next(
StreamSink<String> processInput, Stream<String> processOutput) async {
Future<AppStateAutomata> next() async {
log.fine("Syncing next");
if (await shouldExit()) return exitState();
Future<void> printStdout() async {
await for (final line in processOutput) {
if (synced) break;
log.finest('syncing: print stdout loop');
append(line);
log.info(line);
}
if (shouldSkip()) {
log.fine('skipping state update');
await tick();
return this;
}
Future<void> checkSync() async {
await for (final _
in refresh.pull(appHook.getNotification, 'syncingState')) {
log.finer('SyncingState: checkSync loop');
if (appHook.isExiting()) {
log.fine('Syncing state detected exiting');
break;
}
// here doc is wrong, targetHeight could match height when synced
// potential bug, targetHeight could be smaller then height
final _isConnected = await daemon.isConnected();
final _isSynced = await daemon.isSynced();
if (_isConnected && _isSynced) {
synced = true;
break;
}
}
}
printStdout();
await checkSync();
if (appHook.isExiting()) {
ExitingState _next = ExitingState(appHook, stdout, processOutput);
return moveState(_next);
if (await daemon.isSynced()) {
final int _height = await rpc.height();
return SyncedState(appHook, _height, config.defaultPageIndex);
} else {
return this;
}
log.fine('syncing: loop exit');
// processInput.add('exit');
final _height = await rpc.height();
SyncedState _next = SyncedState(
appHook,
stdout,
processInput,
processOutput,
1,
);
_next.height = _height;
return moveState(_next);
}
}

@ -25,27 +25,23 @@ import '../state.dart';
Widget build(BuildContext context, ExitingState state) {
return Scaffold(
// appBar: AppBar
// (
// // headline6: Text(widget.headline6),
// headline6: Text('CyberWOW'),
// ),
body: Container(
// padding: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(40.0),
child: Align(
alignment: Alignment.topLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
flex: 1,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse: true,
child: Text(
state.stdout.join(),
style: Theme.of(context).textTheme.bodyText2,
)))
Spacer(
flex: 1,
),
Text(
'',
style: Theme.of(context).textTheme.headline4,
),
Spacer(
flex: 1,
),
],
),
),

@ -35,7 +35,7 @@ Widget build(BuildContext context, ReSyncingState state) {
Expanded(
flex: 5,
child: Text(
state.stdout.last,
state.appHook.stdout.last,
style: Theme.of(context).textTheme.bodyText2,
)),
];

@ -146,8 +146,6 @@ Widget rpcView(BuildContext context, String headline6, String body) {
Widget getInfo(BuildContext context, SyncedState state) =>
rpcView(context, 'info', state.getInfoCache);
Widget syncInfo(BuildContext context, SyncedState state) =>
rpcView(context, 'sync info', pretty(state.syncInfo));
Widget getTransactionPool(BuildContext context, SyncedState state) {
final pool = state.getTransactionPool;
@ -166,7 +164,7 @@ Widget getConnections(BuildContext context, SyncedState state) {
Widget terminalView(BuildContext context, String headline6, SyncedState state) {
final input = TextFormField(
controller: state.textController,
textInputAction: TextInputAction.next,
// textInputAction: TextInputAction.next,
autofocus: true,
autocorrect: false,
enableSuggestions: false,
@ -201,6 +199,8 @@ Widget terminalView(BuildContext context, String headline6, SyncedState state) {
final _text = state.textController.text.trim();
final line = autoReplace(_text);
log.info('textController: $state.controller');
if (line.isNotEmpty) {
log.finer('terminal input: $line');
state.appendInput(line);
@ -227,7 +227,7 @@ Widget terminalView(BuildContext context, String headline6, SyncedState state) {
child: Column(
children: <Widget>[
Text(
state.stdout.join('\n'),
state.appHook.stdoutCache,
style: Theme.of(context).textTheme.bodyText1,
)
],
@ -262,7 +262,6 @@ Widget pageView(BuildContext context, SyncedState state) {
getTransactionPool(context, state),
getConnections(context, state),
getInfo(context, state),
// syncInfo(state),
],
);
}

@ -43,7 +43,7 @@ Widget build(BuildContext context, SyncingState state) {
scrollDirection: Axis.vertical,
reverse: true,
child: Text(
state.stdout.join('\n'),
state.appHook.stdoutCache,
style: Theme.of(context).textTheme.bodyText2,
))),
],

@ -2,7 +2,7 @@
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
dependency: "direct main"
description:
name: async
url: "https://pub.dartlang.org"

@ -15,6 +15,7 @@ dependencies:
logging: ^0.11.3
intl: ^0.16.0
shared_preferences: ^0.5.6
async: ^2.4.2
dev_dependencies:
flutter_test: