Refine peyawallet mining tab layout
build / Build Linux (lite) (push) Successful in 2m29s
build / Build Linux (mining) (push) Successful in 2m26s
build / Build Windows (mining) (push) Has been cancelled
build / Build Windows (lite) (push) Has been cancelled

This commit is contained in:
Codex Bot
2026-04-20 23:06:15 +02:00
parent b4d32e5ea8
commit b07b84c3cb
6 changed files with 294 additions and 308 deletions
+1
View File
@@ -355,6 +355,7 @@
"miningSoloHint": "Solo mode will target the local daemon bundled with the wallet.",
"miningSoloNeedsLocalNode": "Solo mode expects the wallet to use the local node.",
"miningPoolHint": "Default pool is preconfigured for Peya and can be adjusted here.",
"miningPoolFixedHint": "Pool mode is fixed to the default Peya pool in this release. The endpoint is shown for visibility only.",
"miningApiPortLabel": "XMRig API port",
"miningResetDraftAction": "Reset draft",
"miningConfigInvalid": "Enter a valid pool host and ports.",
+6
View File
@@ -1754,6 +1754,12 @@ abstract class AppLocalizations {
/// **'Default pool is preconfigured for Peya and can be adjusted here.'**
String get miningPoolHint;
/// No description provided for @miningPoolFixedHint.
///
/// In en, this message translates to:
/// **'Pool mode is fixed to the default Peya pool in this release. The endpoint is shown for visibility only.'**
String get miningPoolFixedHint;
/// No description provided for @miningApiPortLabel.
///
/// In en, this message translates to:
+4
View File
@@ -884,6 +884,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get miningPoolHint =>
'Default pool is preconfigured for Peya and can be adjusted here.';
@override
String get miningPoolFixedHint =>
'Pool mode is fixed to the default Peya pool in this release. The endpoint is shown for visibility only.';
@override
String get miningApiPortLabel => 'XMRig API port';
+4
View File
@@ -888,6 +888,10 @@ class AppLocalizationsPl extends AppLocalizations {
String get miningPoolHint =>
'Domyślny pool dla Peya jest już wpisany i można go tu zmienić.';
@override
String get miningPoolFixedHint =>
'Tryb pool jest w tym wydaniu na stałe spięty z domyślnym poolem Peya. Endpoint pokazujemy tylko informacyjnie.';
@override
String get miningApiPortLabel => 'Port API XMRig';
+1
View File
@@ -355,6 +355,7 @@
"miningSoloHint": "Tryb solo będzie kierował kopanie do lokalnego daemona bundlowanego z walletem.",
"miningSoloNeedsLocalNode": "Tryb solo zakłada, że portfel korzysta z lokalnego noda.",
"miningPoolHint": "Domyślny pool dla Peya jest już wpisany i można go tu zmienić.",
"miningPoolFixedHint": "Tryb pool jest w tym wydaniu na stałe spięty z domyślnym poolem Peya. Endpoint pokazujemy tylko informacyjnie.",
"miningApiPortLabel": "Port API XMRig",
"miningResetDraftAction": "Przywróć szkic",
"miningConfigInvalid": "Podaj poprawny host poola i porty.",
+278 -308
View File
@@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import '../../app_features.dart';
import '../../domain/models.dart';
import '../../state/providers.dart';
import '../l10n/app_localizations_ext.dart';
@@ -20,8 +19,6 @@ class MiningScreen extends ConsumerStatefulWidget {
class _MiningScreenState extends ConsumerState<MiningScreen> {
late MiningMode _mode;
late int _cpuThreads;
late final TextEditingController _poolHostController;
late final TextEditingController _poolPortController;
late final TextEditingController _apiPortController;
bool _dirty = false;
@@ -31,15 +28,11 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
final config = ref.read(appConfigControllerProvider).miningConfig;
_mode = config.mode;
_cpuThreads = config.cpuThreads;
_poolHostController = TextEditingController(text: config.poolHost);
_poolPortController = TextEditingController(text: config.poolPort.toString());
_apiPortController = TextEditingController(text: config.apiPort.toString());
}
@override
void dispose() {
_poolHostController.dispose();
_poolPortController.dispose();
_apiPortController.dispose();
super.dispose();
}
@@ -51,14 +44,6 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
final config = ref.watch(appConfigControllerProvider);
final walletState = ref.watch(walletControllerProvider);
final maxThreads = math.max(1, math.min(Platform.numberOfProcessors, 64));
final poolHost = _poolHostController.text.trim();
final poolPort = int.tryParse(_poolPortController.text.trim());
final apiPort = int.tryParse(_apiPortController.text.trim());
final canSave = poolHost.isNotEmpty &&
poolPort != null &&
poolPort > 0 &&
apiPort != null &&
apiPort > 0;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
@@ -82,295 +67,138 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
),
),
const SizedBox(height: 24),
LayoutBuilder(
builder: (context, constraints) {
final wide = constraints.maxWidth >= 920;
return Flex(
direction: wide ? Axis.horizontal : Axis.vertical,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: wide ? 3 : 0,
child: Column(
Column(
children: [
_SectionCard(
title: l10n.miningControlsTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 12,
runSpacing: 12,
children: [
_SectionCard(
title: l10n.miningSourceTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<MiningMode>(
value: _mode,
decoration: InputDecoration(
labelText: l10n.miningModeLabel,
),
items: [
DropdownMenuItem(
value: MiningMode.solo,
child: Text(l10n.miningModeSolo),
),
DropdownMenuItem(
value: MiningMode.pool,
child: Text(l10n.miningModePool),
),
],
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_mode = value;
_dirty = true;
});
},
),
const SizedBox(height: 16),
if (_mode == MiningMode.solo) ...[
_InfoTile(
label: l10n.miningTargetLabel,
value: l10n.miningSoloTargetValue,
icon: Symbols.lan,
),
const SizedBox(height: 12),
Text(
config.nodeConfig.mode == NodeMode.local
? l10n.miningSoloHint
: l10n.miningSoloNeedsLocalNode,
style: theme.textTheme.bodyMedium?.copyWith(
color: config.nodeConfig.mode == NodeMode.local
? theme.colorScheme.onSurfaceVariant
: theme.colorScheme.error,
),
),
] else ...[
TextFormField(
controller: _poolHostController,
decoration: InputDecoration(
labelText: l10n.hostLabel,
),
onChanged: (_) => setState(() => _dirty = true),
),
const SizedBox(height: 16),
TextFormField(
controller: _poolPortController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: l10n.portLabel,
),
onChanged: (_) => setState(() => _dirty = true),
),
const SizedBox(height: 12),
Text(
l10n.miningPoolHint,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
const SizedBox(height: 20),
DropdownButtonFormField<int>(
value: _cpuThreads.clamp(1, maxThreads),
decoration: InputDecoration(
labelText: l10n.miningThreadsLabel,
),
items: [
for (var value = 1; value <= maxThreads; value++)
DropdownMenuItem(
value: value,
child: Text(value.toString()),
),
],
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_cpuThreads = value;
_dirty = true;
});
},
),
const SizedBox(height: 16),
TextFormField(
controller: _apiPortController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: l10n.miningApiPortLabel,
),
onChanged: (_) => setState(() => _dirty = true),
),
const SizedBox(height: 16),
Row(
children: [
FilledButton.icon(
onPressed: canSave ? _saveConfig : null,
icon: const Icon(Symbols.save),
label: Text(l10n.saveAction),
),
const SizedBox(width: 12),
OutlinedButton.icon(
onPressed: _dirty ? _resetDraft : null,
icon: const Icon(Symbols.undo),
label: Text(l10n.miningResetDraftAction),
),
],
),
],
),
FilledButton.icon(
onPressed: null,
icon: const Icon(Symbols.play_arrow),
label: Text(l10n.miningStartAction),
),
const SizedBox(height: 16),
_SectionCard(
title: l10n.miningControlsTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Wrap(
spacing: 12,
runSpacing: 12,
children: [
FilledButton.icon(
onPressed: null,
icon: const Icon(Symbols.play_arrow),
label: Text(l10n.miningStartAction),
),
OutlinedButton.icon(
onPressed: null,
icon: const Icon(Symbols.stop),
label: Text(l10n.miningStopAction),
),
OutlinedButton.icon(
onPressed: null,
icon: const Icon(Symbols.refresh),
label: Text(l10n.localNodeRestartAction),
),
],
),
const SizedBox(height: 12),
Text(
l10n.miningBackendPending,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
OutlinedButton.icon(
onPressed: null,
icon: const Icon(Symbols.stop),
label: Text(l10n.miningStopAction),
),
OutlinedButton.icon(
onPressed: null,
icon: const Icon(Symbols.refresh),
label: Text(l10n.localNodeRestartAction),
),
OutlinedButton.icon(
onPressed: () => _showSettingsDialog(maxThreads),
icon: const Icon(Symbols.settings),
label: Text(l10n.miningSettingsAction),
),
],
),
),
SizedBox(width: wide ? 16 : 0, height: wide ? 0 : 16),
Expanded(
flex: wide ? 2 : 0,
child: Column(
children: [
_SectionCard(
title: l10n.miningStatsTitle,
child: Wrap(
spacing: 12,
runSpacing: 12,
children: [
_StatTile(
label: l10n.transactionStatusLabel,
value: l10n.comingSoon,
),
_StatTile(
label: l10n.miningHashrate10sLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningHashrate1mLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningLabelHashrate15m,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningJobsLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningAcceptedSharesLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningRejectedSharesLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningLabelUptime,
value: l10n.miningDataUnknownValue,
),
],
),
),
const SizedBox(height: 16),
_SectionCard(
title: l10n.miningCurrentConfigTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_InfoTile(
label: l10n.miningWalletAddressLabel,
value: walletState.walletInfo?.address.isNotEmpty == true
? walletState.walletInfo!.address
: l10n.miningNoWalletAddress,
icon: Symbols.account_balance_wallet,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningModeLabel,
value: config.miningConfig.mode == MiningMode.solo
? l10n.miningModeSolo
: '${config.miningConfig.poolHost}:${config.miningConfig.poolPort}',
icon: config.miningConfig.mode == MiningMode.solo
? Symbols.lan
: Symbols.hub,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningThreadsLabel,
value: config.miningConfig.cpuThreads.toString(),
icon: Symbols.memory,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningApiPortLabel,
value: config.miningConfig.apiPort.toString(),
icon: Symbols.settings_ethernet,
),
],
),
),
const SizedBox(height: 16),
_SectionCard(
title: l10n.miningBundledTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
AppFeatures.isMiningFlavor
? l10n.miningBundledEnabled
: l10n.miningBundledDisabled,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 12),
Text(
l10n.miningBundledBody,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
),
),
],
const SizedBox(height: 12),
Text(
l10n.miningBackendPending,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
),
],
);
},
],
),
),
const SizedBox(height: 16),
_SectionCard(
title: l10n.miningCurrentConfigTitle,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_InfoTile(
label: l10n.miningWalletAddressLabel,
value: walletState.walletInfo?.address.isNotEmpty == true
? walletState.walletInfo!.address
: l10n.miningNoWalletAddress,
icon: Symbols.account_balance_wallet,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningModeLabel,
value: config.miningConfig.mode == MiningMode.solo
? l10n.miningModeSolo
: '${config.miningConfig.poolHost}:${config.miningConfig.poolPort}',
icon: config.miningConfig.mode == MiningMode.solo
? Symbols.lan
: Symbols.hub,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningTargetLabel,
value: config.miningConfig.mode == MiningMode.solo
? l10n.miningSoloTargetValue
: '${config.miningConfig.poolHost}:${config.miningConfig.poolPort}',
icon: Symbols.route,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningThreadsLabel,
value: config.miningConfig.cpuThreads.toString(),
icon: Symbols.memory,
),
const SizedBox(height: 12),
_InfoTile(
label: l10n.miningApiPortLabel,
value: config.miningConfig.apiPort.toString(),
icon: Symbols.settings_ethernet,
),
],
),
),
const SizedBox(height: 16),
_SectionCard(
title: l10n.miningStatsTitle,
child: Wrap(
spacing: 12,
runSpacing: 12,
children: [
_StatTile(
label: l10n.transactionStatusLabel,
value: l10n.comingSoon,
),
_StatTile(
label: l10n.miningHashrate10sLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningHashrate1mLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningLabelHashrate15m,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningJobsLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningAcceptedSharesLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningRejectedSharesLabel,
value: l10n.miningDataUnknownValue,
),
_StatTile(
label: l10n.miningLabelUptime,
value: l10n.miningDataUnknownValue,
),
],
),
),
],
),
],
),
@@ -384,8 +212,6 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
setState(() {
_mode = config.mode;
_cpuThreads = config.cpuThreads;
_poolHostController.text = config.poolHost;
_poolPortController.text = config.poolPort.toString();
_apiPortController.text = config.apiPort.toString();
_dirty = false;
});
@@ -393,13 +219,8 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
Future<void> _saveConfig() async {
final l10n = context.l10n;
final port = int.tryParse(_poolPortController.text.trim());
final apiPort = int.tryParse(_apiPortController.text.trim());
if (_poolHostController.text.trim().isEmpty ||
port == null ||
port <= 0 ||
apiPort == null ||
apiPort <= 0) {
if (apiPort == null || apiPort <= 0) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.miningConfigInvalid)),
);
@@ -409,8 +230,8 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
final next = MiningConfig(
mode: _mode,
cpuThreads: _cpuThreads,
poolHost: _poolHostController.text.trim(),
poolPort: port,
poolHost: 'peya.cryptohash.top',
poolPort: 3333,
apiPort: apiPort,
);
await ref.read(appConfigControllerProvider.notifier).setMiningConfig(next);
@@ -422,6 +243,155 @@ class _MiningScreenState extends ConsumerState<MiningScreen> {
SnackBar(content: Text(l10n.miningSettingsSaved)),
);
}
Future<void> _showSettingsDialog(int maxThreads) async {
final l10n = context.l10n;
final theme = Theme.of(context);
await showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertDialog(
backgroundColor: theme.colorScheme.surface,
title: Text(l10n.miningSettingsAction),
content: SizedBox(
width: 520,
child: StatefulBuilder(
builder: (context, setDialogState) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DropdownButtonFormField<MiningMode>(
value: _mode,
dropdownColor: theme.colorScheme.surface,
decoration: InputDecoration(
labelText: l10n.miningModeLabel,
),
items: [
DropdownMenuItem(
value: MiningMode.solo,
child: Text(l10n.miningModeSolo),
),
DropdownMenuItem(
value: MiningMode.pool,
child: Text(l10n.miningModePool),
),
],
onChanged: (value) {
if (value == null) {
return;
}
setState(() {
_mode = value;
_dirty = true;
});
setDialogState(() {});
},
),
const SizedBox(height: 16),
if (_mode == MiningMode.solo) ...[
_InfoTile(
label: l10n.miningTargetLabel,
value: l10n.miningSoloTargetValue,
icon: Symbols.lan,
),
const SizedBox(height: 12),
Text(
ref.read(appConfigControllerProvider).nodeConfig.mode ==
NodeMode.local
? l10n.miningSoloHint
: l10n.miningSoloNeedsLocalNode,
style: theme.textTheme.bodyMedium?.copyWith(
color: ref.read(appConfigControllerProvider).nodeConfig.mode ==
NodeMode.local
? theme.colorScheme.onSurfaceVariant
: theme.colorScheme.error,
),
),
] else ...[
_InfoTile(
label: l10n.miningTargetLabel,
value: 'peya.cryptohash.top:3333',
icon: Symbols.hub,
),
const SizedBox(height: 12),
Text(
l10n.miningPoolFixedHint,
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
const SizedBox(height: 20),
Text(
'${l10n.miningThreadsLabel}: $_cpuThreads',
style: theme.textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.w600,
),
),
Slider(
value: _cpuThreads.toDouble(),
min: 1,
max: maxThreads.toDouble(),
divisions: math.max(0, maxThreads - 1),
label: _cpuThreads.toString(),
onChanged: (value) {
final next = value.round();
setState(() {
_cpuThreads = next;
_dirty = true;
});
setDialogState(() {});
},
),
const SizedBox(height: 8),
TextFormField(
controller: _apiPortController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: l10n.miningApiPortLabel,
),
onChanged: (_) {
setState(() => _dirty = true);
setDialogState(() {});
},
),
],
);
},
),
),
actions: [
TextButton(
onPressed: () {
_resetDraft();
Navigator.of(dialogContext).pop();
},
child: Text(l10n.miningResetDraftAction),
),
TextButton(
onPressed: () => Navigator.of(dialogContext).pop(),
child: Text(l10n.cancelAction),
),
FilledButton(
onPressed: () {
final apiPort = int.tryParse(_apiPortController.text.trim());
return apiPort != null && apiPort > 0;
}()
? () async {
await _saveConfig();
if (mounted) {
Navigator.of(dialogContext).pop();
}
}
: null,
child: Text(l10n.saveAction),
),
],
);
},
);
}
}
class _SectionCard extends StatelessWidget {