Files
Codex Bot b4d32e5ea8
build / Build Linux (lite) (push) Successful in 2m19s
build / Build Linux (mining) (push) Successful in 2m48s
build / Build Windows (lite) (push) Successful in 21m58s
build / Build Windows (mining) (push) Has been cancelled
Split peyawallet into lite and mining variants
2026-04-20 22:42:52 +02:00

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');
}
}
}