471 lines
13 KiB
Dart
471 lines
13 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
enum NodeMode { local, remote }
|
|
|
|
enum LanguagePreference { system, english, polish }
|
|
|
|
enum MiningMode { solo, pool }
|
|
|
|
class RemoteNode {
|
|
const RemoteNode({
|
|
required this.host,
|
|
required this.port,
|
|
required this.trusted,
|
|
});
|
|
|
|
final String host;
|
|
final int port;
|
|
final bool trusted;
|
|
|
|
RemoteNode copyWith({String? host, int? port, bool? trusted}) {
|
|
return RemoteNode(
|
|
host: host ?? this.host,
|
|
port: port ?? this.port,
|
|
trusted: trusted ?? this.trusted,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'host': host,
|
|
'port': port,
|
|
'trusted': trusted,
|
|
};
|
|
}
|
|
|
|
factory RemoteNode.fromJson(Map<String, dynamic> json) {
|
|
return RemoteNode(
|
|
host: json['host'] as String? ?? '',
|
|
port: json['port'] as int? ?? 0,
|
|
trusted: json['trusted'] as bool? ?? false,
|
|
);
|
|
}
|
|
|
|
@override
|
|
String toString() => '$host:$port';
|
|
}
|
|
|
|
class NodeConfig {
|
|
const NodeConfig({
|
|
required this.mode,
|
|
required this.remoteNodes,
|
|
required this.activeRemoteIndex,
|
|
});
|
|
|
|
final NodeMode mode;
|
|
final List<RemoteNode> remoteNodes;
|
|
final int? activeRemoteIndex;
|
|
|
|
NodeConfig copyWith({
|
|
NodeMode? mode,
|
|
List<RemoteNode>? remoteNodes,
|
|
int? activeRemoteIndex,
|
|
}) {
|
|
return NodeConfig(
|
|
mode: mode ?? this.mode,
|
|
remoteNodes: remoteNodes ?? this.remoteNodes,
|
|
activeRemoteIndex: activeRemoteIndex ?? this.activeRemoteIndex,
|
|
);
|
|
}
|
|
|
|
RemoteNode? get activeRemote {
|
|
if (activeRemoteIndex == null) {
|
|
return null;
|
|
}
|
|
if (activeRemoteIndex! < 0 || activeRemoteIndex! >= remoteNodes.length) {
|
|
return null;
|
|
}
|
|
return remoteNodes[activeRemoteIndex!];
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'mode': mode.name,
|
|
'remoteNodes': remoteNodes.map((node) => node.toJson()).toList(),
|
|
'activeRemoteIndex': activeRemoteIndex,
|
|
};
|
|
}
|
|
|
|
factory NodeConfig.fromJson(Map<String, dynamic> json) {
|
|
final nodesJson = json['remoteNodes'] as List<dynamic>? ?? const [];
|
|
final remoteNodes = nodesJson
|
|
.map((node) => RemoteNode.fromJson(node as Map<String, dynamic>))
|
|
.toList();
|
|
final mode = NodeMode.values.firstWhere(
|
|
(mode) => mode.name == (json['mode'] as String? ?? 'local'),
|
|
orElse: () => NodeMode.local,
|
|
);
|
|
final activeRemoteIndex = json['activeRemoteIndex'] as int?;
|
|
return NodeConfig(
|
|
mode: mode,
|
|
remoteNodes: remoteNodes,
|
|
activeRemoteIndex: activeRemoteIndex ??
|
|
(mode == NodeMode.remote && remoteNodes.isNotEmpty ? 0 : null),
|
|
);
|
|
}
|
|
}
|
|
|
|
class WalletInfo {
|
|
const WalletInfo({
|
|
required this.name,
|
|
required this.path,
|
|
required this.address,
|
|
});
|
|
|
|
final String name;
|
|
final String path;
|
|
final String address;
|
|
|
|
WalletInfo copyWith({String? name, String? path, String? address}) {
|
|
return WalletInfo(
|
|
name: name ?? this.name,
|
|
path: path ?? this.path,
|
|
address: address ?? this.address,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'name': name,
|
|
'path': path,
|
|
'address': address,
|
|
};
|
|
}
|
|
|
|
factory WalletInfo.fromJson(Map<String, dynamic> json) {
|
|
return WalletInfo(
|
|
name: json['name'] as String? ?? '',
|
|
path: json['path'] as String? ?? '',
|
|
address: json['address'] as String? ?? '',
|
|
);
|
|
}
|
|
}
|
|
|
|
class SubaddressInfo {
|
|
const SubaddressInfo({
|
|
required this.index,
|
|
required this.address,
|
|
required this.label,
|
|
required this.balanceAtomic,
|
|
required this.unlockedAtomic,
|
|
});
|
|
|
|
final int index;
|
|
final String address;
|
|
final String label;
|
|
final int balanceAtomic;
|
|
final int unlockedAtomic;
|
|
|
|
bool get isPrimary => index == 0;
|
|
}
|
|
|
|
class SyncStatus {
|
|
const SyncStatus({
|
|
required this.synced,
|
|
required this.progress,
|
|
required this.nodeHeight,
|
|
required this.walletHeight,
|
|
required this.lastSync,
|
|
});
|
|
|
|
final bool synced;
|
|
final double progress;
|
|
final int nodeHeight;
|
|
final int walletHeight;
|
|
final DateTime? lastSync;
|
|
|
|
SyncStatus copyWith({
|
|
bool? synced,
|
|
double? progress,
|
|
int? nodeHeight,
|
|
int? walletHeight,
|
|
DateTime? lastSync,
|
|
}) {
|
|
return SyncStatus(
|
|
synced: synced ?? this.synced,
|
|
progress: progress ?? this.progress,
|
|
nodeHeight: nodeHeight ?? this.nodeHeight,
|
|
walletHeight: walletHeight ?? this.walletHeight,
|
|
lastSync: lastSync ?? this.lastSync,
|
|
);
|
|
}
|
|
|
|
factory SyncStatus.initial() {
|
|
return const SyncStatus(
|
|
synced: false,
|
|
progress: 0.0,
|
|
nodeHeight: 0,
|
|
walletHeight: 0,
|
|
lastSync: null,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'synced': synced,
|
|
'progress': progress,
|
|
'nodeHeight': nodeHeight,
|
|
'walletHeight': walletHeight,
|
|
'lastSync': lastSync?.toIso8601String(),
|
|
};
|
|
}
|
|
|
|
factory SyncStatus.fromJson(Map<String, dynamic> json) {
|
|
return SyncStatus(
|
|
synced: json['synced'] as bool? ?? false,
|
|
progress: (json['progress'] as num?)?.toDouble() ?? 0.0,
|
|
nodeHeight: json['nodeHeight'] as int? ?? 0,
|
|
walletHeight: json['walletHeight'] as int? ?? 0,
|
|
lastSync: json['lastSync'] == null
|
|
? null
|
|
: DateTime.tryParse(json['lastSync'] as String),
|
|
);
|
|
}
|
|
}
|
|
|
|
class MiningConfig {
|
|
const MiningConfig({
|
|
required this.mode,
|
|
required this.cpuThreads,
|
|
required this.poolHost,
|
|
required this.poolPort,
|
|
required this.apiPort,
|
|
});
|
|
|
|
final MiningMode mode;
|
|
final int cpuThreads;
|
|
final String poolHost;
|
|
final int poolPort;
|
|
final int apiPort;
|
|
|
|
MiningConfig copyWith({
|
|
MiningMode? mode,
|
|
int? cpuThreads,
|
|
String? poolHost,
|
|
int? poolPort,
|
|
int? apiPort,
|
|
}) {
|
|
return MiningConfig(
|
|
mode: mode ?? this.mode,
|
|
cpuThreads: cpuThreads ?? this.cpuThreads,
|
|
poolHost: poolHost ?? this.poolHost,
|
|
poolPort: poolPort ?? this.poolPort,
|
|
apiPort: apiPort ?? this.apiPort,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'mode': mode.name,
|
|
'cpuThreads': cpuThreads,
|
|
'poolHost': poolHost,
|
|
'poolPort': poolPort,
|
|
'apiPort': apiPort,
|
|
};
|
|
}
|
|
|
|
factory MiningConfig.fromJson(Map<String, dynamic> json) {
|
|
return MiningConfig(
|
|
mode: MiningMode.values.firstWhere(
|
|
(value) => value.name == (json['mode'] as String? ?? 'solo'),
|
|
orElse: () => MiningMode.solo,
|
|
),
|
|
cpuThreads: (json['cpuThreads'] as num?)?.toInt() ?? 1,
|
|
poolHost: json['poolHost'] as String? ?? 'peya.cryptohash.top',
|
|
poolPort: (json['poolPort'] as num?)?.toInt() ?? 3333,
|
|
apiPort: (json['apiPort'] as num?)?.toInt() ?? 18091,
|
|
);
|
|
}
|
|
}
|
|
|
|
class AppConfig {
|
|
const AppConfig({
|
|
required this.lastWallet,
|
|
required this.nodeConfig,
|
|
required this.walletBasePath,
|
|
required this.minimizeToTray,
|
|
required this.startMinimized,
|
|
required this.autoSync,
|
|
required this.autoSyncInterval,
|
|
required this.languagePreference,
|
|
required this.hiddenSubaddresses,
|
|
required this.localNodeArgs,
|
|
required this.miningConfig,
|
|
required this.p2poolStratum,
|
|
required this.p2poolP2p,
|
|
required this.p2poolStartMining,
|
|
required this.p2poolMiningThreads,
|
|
});
|
|
|
|
final WalletInfo? lastWallet;
|
|
final NodeConfig nodeConfig;
|
|
final String? walletBasePath;
|
|
final bool minimizeToTray;
|
|
final bool startMinimized;
|
|
final bool autoSync;
|
|
final Duration autoSyncInterval;
|
|
final LanguagePreference languagePreference;
|
|
final Map<String, List<String>> hiddenSubaddresses;
|
|
final List<String> localNodeArgs;
|
|
final MiningConfig miningConfig;
|
|
final String p2poolStratum;
|
|
final String p2poolP2p;
|
|
final bool p2poolStartMining;
|
|
final int p2poolMiningThreads;
|
|
|
|
AppConfig copyWith({
|
|
WalletInfo? lastWallet,
|
|
NodeConfig? nodeConfig,
|
|
String? walletBasePath,
|
|
bool? minimizeToTray,
|
|
bool? startMinimized,
|
|
bool? autoSync,
|
|
Duration? autoSyncInterval,
|
|
LanguagePreference? languagePreference,
|
|
Map<String, List<String>>? hiddenSubaddresses,
|
|
List<String>? localNodeArgs,
|
|
MiningConfig? miningConfig,
|
|
String? p2poolStratum,
|
|
String? p2poolP2p,
|
|
bool? p2poolStartMining,
|
|
int? p2poolMiningThreads,
|
|
}) {
|
|
return AppConfig(
|
|
lastWallet: lastWallet ?? this.lastWallet,
|
|
nodeConfig: nodeConfig ?? this.nodeConfig,
|
|
walletBasePath: walletBasePath ?? this.walletBasePath,
|
|
minimizeToTray: minimizeToTray ?? this.minimizeToTray,
|
|
startMinimized: startMinimized ?? this.startMinimized,
|
|
autoSync: autoSync ?? this.autoSync,
|
|
autoSyncInterval: autoSyncInterval ?? this.autoSyncInterval,
|
|
languagePreference: languagePreference ?? this.languagePreference,
|
|
hiddenSubaddresses: hiddenSubaddresses ?? this.hiddenSubaddresses,
|
|
localNodeArgs: localNodeArgs ?? this.localNodeArgs,
|
|
miningConfig: miningConfig ?? this.miningConfig,
|
|
p2poolStratum: p2poolStratum ?? this.p2poolStratum,
|
|
p2poolP2p: p2poolP2p ?? this.p2poolP2p,
|
|
p2poolStartMining: p2poolStartMining ?? this.p2poolStartMining,
|
|
p2poolMiningThreads: p2poolMiningThreads ?? this.p2poolMiningThreads,
|
|
);
|
|
}
|
|
|
|
static AppConfig defaults() {
|
|
return AppConfig(
|
|
lastWallet: null,
|
|
nodeConfig: const NodeConfig(
|
|
mode: NodeMode.local,
|
|
remoteNodes: [],
|
|
activeRemoteIndex: null,
|
|
),
|
|
walletBasePath: null,
|
|
minimizeToTray: false,
|
|
startMinimized: false,
|
|
autoSync: true,
|
|
autoSyncInterval: const Duration(seconds: 10),
|
|
languagePreference: LanguagePreference.system,
|
|
hiddenSubaddresses: const {},
|
|
localNodeArgs: const [],
|
|
miningConfig: const MiningConfig(
|
|
mode: MiningMode.solo,
|
|
cpuThreads: 1,
|
|
poolHost: 'peya.cryptohash.top',
|
|
poolPort: 3333,
|
|
apiPort: 18091,
|
|
),
|
|
p2poolStratum: '0.0.0.0:3333',
|
|
p2poolP2p: '0.0.0.0:38889',
|
|
p2poolStartMining: false,
|
|
p2poolMiningThreads: 1,
|
|
);
|
|
}
|
|
|
|
Map<String, dynamic> toJson() {
|
|
return {
|
|
'lastWallet': lastWallet?.toJson(),
|
|
'nodeConfig': nodeConfig.toJson(),
|
|
'walletBasePath': walletBasePath,
|
|
'minimizeToTray': minimizeToTray,
|
|
'startMinimized': startMinimized,
|
|
'autoSync': autoSync,
|
|
'autoSyncIntervalSeconds': autoSyncInterval.inSeconds,
|
|
'languagePreference': languagePreference.name,
|
|
'hiddenSubaddresses': hiddenSubaddresses,
|
|
'localNodeArgs': localNodeArgs,
|
|
'miningConfig': miningConfig.toJson(),
|
|
'p2poolStratum': p2poolStratum,
|
|
'p2poolP2p': p2poolP2p,
|
|
'p2poolStartMining': p2poolStartMining,
|
|
'p2poolMiningThreads': p2poolMiningThreads,
|
|
};
|
|
}
|
|
|
|
factory AppConfig.fromJson(Map<String, dynamic> json) {
|
|
final hiddenRaw =
|
|
json['hiddenSubaddresses'] as Map<String, dynamic>? ?? const {};
|
|
final hidden = <String, List<String>>{};
|
|
for (final entry in hiddenRaw.entries) {
|
|
final rawList = entry.value;
|
|
if (rawList is List) {
|
|
hidden[entry.key] = rawList.whereType<String>().toList();
|
|
}
|
|
}
|
|
final argsRaw = json['localNodeArgs'] as List<dynamic>? ?? const [];
|
|
final localNodeArgs = argsRaw.whereType<String>().toList();
|
|
final p2poolStratum = json['p2poolStratum'] as String? ?? '0.0.0.0:3333';
|
|
final p2poolP2p = json['p2poolP2p'] as String? ?? '0.0.0.0:38889';
|
|
final p2poolStartMining = json['p2poolStartMining'] as bool? ?? false;
|
|
final p2poolMiningThreads =
|
|
(json['p2poolMiningThreads'] as num?)?.toInt() ?? 1;
|
|
final miningConfigJson =
|
|
json['miningConfig'] as Map<String, dynamic>? ?? const {};
|
|
return AppConfig(
|
|
lastWallet: json['lastWallet'] == null
|
|
? null
|
|
: WalletInfo.fromJson(json['lastWallet'] as Map<String, dynamic>),
|
|
nodeConfig: json['nodeConfig'] == null
|
|
? const NodeConfig(
|
|
mode: NodeMode.local,
|
|
remoteNodes: [],
|
|
activeRemoteIndex: null,
|
|
)
|
|
: NodeConfig.fromJson(json['nodeConfig'] as Map<String, dynamic>),
|
|
walletBasePath: json['walletBasePath'] as String?,
|
|
minimizeToTray: json['minimizeToTray'] as bool? ?? false,
|
|
startMinimized: json['startMinimized'] as bool? ?? false,
|
|
autoSync: json['autoSync'] as bool? ?? true,
|
|
autoSyncInterval: Duration(
|
|
seconds: json['autoSyncIntervalSeconds'] as int? ?? 10,
|
|
),
|
|
languagePreference: LanguagePreference.values.firstWhere(
|
|
(value) =>
|
|
value.name == (json['languagePreference'] as String? ?? 'system'),
|
|
orElse: () => LanguagePreference.system,
|
|
),
|
|
hiddenSubaddresses: hidden,
|
|
localNodeArgs: localNodeArgs,
|
|
miningConfig: MiningConfig.fromJson(miningConfigJson),
|
|
p2poolStratum: p2poolStratum,
|
|
p2poolP2p: p2poolP2p,
|
|
p2poolStartMining: p2poolStartMining,
|
|
p2poolMiningThreads: p2poolMiningThreads,
|
|
);
|
|
}
|
|
|
|
static String prettyPrint(AppConfig config) {
|
|
return const JsonEncoder.withIndent(' ').convert(config.toJson());
|
|
}
|
|
|
|
Locale? resolveLocale() {
|
|
switch (languagePreference) {
|
|
case LanguagePreference.system:
|
|
return null;
|
|
case LanguagePreference.english:
|
|
return const Locale('en');
|
|
case LanguagePreference.polish:
|
|
return const Locale('pl');
|
|
}
|
|
}
|
|
}
|