Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions miner-app/lib/features/withdrawal/claim_rewards_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
import 'package:quantus_miner/src/services/binary_manager.dart';
import 'package:quantus_miner/src/services/miner_settings_service.dart';
import 'package:quantus_miner/src/services/miner_wallet_service.dart';
import 'package:quantus_miner/src/services/wormhole_claim_service.dart';
import 'package:quantus_miner/src/utils/app_logger.dart';
import 'package:quantus_sdk/quantus_sdk.dart';

Expand All @@ -30,7 +29,7 @@ enum _Screen { input, confirm, progress }

class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> {
final _addressController = TextEditingController();
final _claimService = WormholeClaimService();
WormholeClaimService? _claimService;
final _walletService = MinerWalletService();
final _settingsService = MinerSettingsService();

Expand Down Expand Up @@ -105,14 +104,13 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> {
final binsDir = '${await BinaryManager.getQuantusHomeDirectoryPath()}/generated-bins';
await Directory(binsDir).create(recursive: true);

final rpcUrl = chainConfig.rpcUrl;
_claimService = WormholeClaimService(rpcUrl: chainConfig.rpcUrl);
_log.i('Starting claim for ${keyPair.address} to ${_addressController.text.trim()}');

final result = await _claimService.claimRewards(
final result = await _claimService!.claimRewards(
wormholeAddress: keyPair.address,
secretHex: keyPair.secretHex,
destinationAddress: _addressController.text.trim(),
rpcUrl: rpcUrl,
circuitBinsDir: binsDir,
onProgress: (progress) {
if (!mounted) return;
Expand Down Expand Up @@ -147,7 +145,7 @@ class _ClaimRewardsDialogState extends State<_ClaimRewardsDialog> {
}

void _cancelClaim() {
_claimService.cancel();
_claimService?.cancel();
}

@override
Expand Down
2 changes: 2 additions & 0 deletions mobile-app/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ analyzer:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "lib/generated/**"
- "build/**"

errors:
avoid_print: ignore # print is the most reliable way to debug the app
missing_required_param: error
Expand Down
Empty file modified mobile-app/fix_ios_build.sh
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions mobile-app/ios/Runner/Runner.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
<array>
<string>applinks:www.quantus.com</string>
</array>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
</dict>
</plist>
2 changes: 1 addition & 1 deletion mobile-app/lib/generated/version.g.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
const appVersion = '1.5.0';
const appBuildNumber = '106';
const appBuildNumber = '107';
111 changes: 111 additions & 0 deletions mobile-app/lib/v2/components/address_input_field.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import 'package:flutter/material.dart';
import 'package:quantus_sdk/quantus_sdk.dart';
import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';

/// Dual-state recipient input. When the controller holds a valid SS58 address
/// (`hasValid`), shows a pill with the truncated address and optional human
/// checksum. Otherwise shows a regular search-style input.
class AddressInputField extends StatelessWidget {
final TextEditingController controller;
final FocusNode focusNode;
final bool hasValid;
final String? recipientChecksum;
final String hintText;
final bool showSearchIcon;

const AddressInputField({
super.key,
required this.controller,
required this.focusNode,
required this.hasValid,
required this.recipientChecksum,
this.hintText = 'Search ${AppConstants.tokenSymbol} Address',
this.showSearchIcon = true,
});

@override
Widget build(BuildContext context) {
final colors = context.colors;
final text = context.themeText;

return SizedBox(
height: 48,
child: Stack(
children: [
Positioned.fill(
child: IgnorePointer(
ignoring: hasValid,
child: Opacity(
opacity: hasValid ? 0 : 1,
child: Container(
padding: const EdgeInsets.only(left: 12, right: 8),
decoration: BoxDecoration(color: colors.sheetBackground, borderRadius: BorderRadius.circular(8)),
child: Row(
children: [
if (showSearchIcon) ...[
Icon(Icons.search, size: 14, color: colors.textLabel),
const SizedBox(width: 12),
],
Expanded(
child: TextField(
controller: controller,
focusNode: focusNode,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.done,
autocorrect: false,
enableSuggestions: false,
textCapitalization: TextCapitalization.none,
scrollPadding: const EdgeInsets.only(bottom: 120),
style: text.smallParagraph?.copyWith(color: colors.textPrimary),
decoration: InputDecoration(hintText: hintText),
),
),
],
),
),
),
),
),
if (hasValid)
Positioned.fill(
child: GestureDetector(
onTap: () {
controller.clear();
focusNode.requestFocus();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
decoration: BoxDecoration(color: colors.toasterBackground, borderRadius: BorderRadius.circular(8)),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
AddressFormattingService.formatAddress(controller.text.trim()),
style: text.smallParagraph?.copyWith(
color: colors.textPrimary,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (recipientChecksum != null)
Text(recipientChecksum!, style: text.detail?.copyWith(color: colors.checksum)),
],
),
),
Icon(Icons.edit_outlined, size: 16, color: colors.textLabel),
],
),
),
),
),
],
),
);
}
}
113 changes: 113 additions & 0 deletions mobile-app/lib/v2/components/recent_addresses_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:resonance_network_wallet/features/components/skeleton.dart';
import 'package:resonance_network_wallet/providers/wallet_providers.dart';
import 'package:resonance_network_wallet/v2/components/address_checkphrase_with_initial.dart';
import 'package:resonance_network_wallet/v2/components/loader.dart';
import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';

/// Recents list backed by [recentAddressesServiceProvider]. Filters out any
/// addresses in [excludeAddresses] (typically the user's own active account).
/// Renders as slivers so it can live inside the screen's main CustomScrollView.
class RecentAddressesSlivers extends ConsumerStatefulWidget {
final Set<String> excludeAddresses;
final ValueChanged<String> onTap;
final String title;

const RecentAddressesSlivers({
super.key,
required this.excludeAddresses,
required this.onTap,
this.title = 'Recents',
});

@override
ConsumerState<RecentAddressesSlivers> createState() => _RecentAddressesSliversState();
}

class _RecentAddressesSliversState extends ConsumerState<RecentAddressesSlivers> {
final Map<String, String> _checksums = {};
List<String> _recents = [];
bool _loading = true;

@override
void initState() {
super.initState();
_load();
}

Future<void> _load() async {
final recentAddressesService = ref.read(recentAddressesServiceProvider);
final checksumService = ref.read(humanReadableChecksumServiceProvider);
try {
final all = await recentAddressesService.getAddresses();
final addresses = all.where((a) => !widget.excludeAddresses.contains(a)).toList();
if (!mounted) return;
setState(() {
_recents = addresses;
_loading = false;
});
for (final addr in addresses) {
checksumService.getHumanReadableName(addr).then((name) {
if (mounted) setState(() => _checksums[addr] = name);
});
}
} catch (e) {
debugPrint('RecentAddressesSlivers load failed: $e');
if (mounted) setState(() => _loading = false);
}
}

@override
Widget build(BuildContext context) {
if (_loading) {
return const SliverFillRemaining(hasScrollBody: false, child: Center(child: Loader()));
}
if (_recents.isEmpty) {
return const SliverFillRemaining(hasScrollBody: false, child: SizedBox.shrink());
}

final colors = context.colors;
final text = context.themeText;

return SliverMainAxisGroup(
slivers: [
SliverToBoxAdapter(
child: Text(widget.title, style: text.smallTitle?.copyWith(color: colors.textPrimary)),
),
const SliverToBoxAdapter(child: SizedBox(height: 32)),
SliverList(
delegate: SliverChildBuilderDelegate((context, i) {
final isFirst = i == 0;
final isLast = i == _recents.length - 1;
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
if (!isFirst) const SizedBox(height: 14),
_recentRow(_recents[i], colors),
if (!isLast) ...[const SizedBox(height: 14), Divider(height: 1, color: colors.txItemSeparator)],
],
);
}, childCount: _recents.length),
),
],
);
}

Widget _recentRow(String address, AppColorsV2 colors) {
final checksum = _checksums[address];

return Material(
color: Colors.transparent,
child: InkWell(
onTap: () => widget.onTap(address),
borderRadius: BorderRadius.circular(8),
child: checksum != null
? AddressCheckphraseWithInitial(recipientChecksum: checksum, recipientAddress: address)
: const Skeleton(height: 36),
),
);
}
}
19 changes: 16 additions & 3 deletions mobile-app/lib/v2/screens/settings/mining_rewards_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:resonance_network_wallet/v2/components/scaffold_base.dart';
import 'package:resonance_network_wallet/v2/components/scaffold_base_bottom_content.dart';
import 'package:resonance_network_wallet/v2/components/split_card.dart';
import 'package:resonance_network_wallet/v2/components/v2_app_bar.dart';
import 'package:resonance_network_wallet/v2/screens/settings/redeem_address_screen.dart';
import 'package:resonance_network_wallet/v2/theme/app_colors.dart';
import 'package:resonance_network_wallet/v2/theme/app_text_styles.dart';

Expand All @@ -35,9 +36,21 @@ class MiningRewardsScreen extends ConsumerWidget {
),
],
bottomContent: miningAsync.when(
data: (data) => data.totalBlocks > 0
? const ScaffoldBaseBottomContent(child: QuantusButton.simple(label: 'Redeem', onTap: null))
: null,
data: (data) {
if (data.totalBlocks == 0) return null;
final canRedeem = data.redeemableRewards > BigInt.zero;
return ScaffoldBaseBottomContent(
child: QuantusButton.simple(
label: 'Redeem',
isDisabled: !canRedeem,
onTap: canRedeem
? () => Navigator.of(context).push(
MaterialPageRoute(builder: (_) => RedeemAddressScreen(redeemableRewards: data.redeemableRewards)),
)
: null,
),
);
},
loading: () => null,
error: (err, _) => null,
),
Expand Down
Loading