Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #95 from MXCzkEVM/account_management_feat
Browse files Browse the repository at this point in the history
Account management feature
  • Loading branch information
reasje authored Oct 17, 2023
2 parents 32fe801 + 91bdf2b commit 2b575b0
Show file tree
Hide file tree
Showing 23 changed files with 670 additions and 110 deletions.
14 changes: 12 additions & 2 deletions assets/flutter_i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,13 +280,23 @@
"checking_balance": "Checking balance...",
"no_balance": "You have no balance",
"no_balance_tip": "You currently have no MXC in your wallet.\nYou can either receive tokens or choose to claim a username later.",
"signature_request": "Signature request",
"signature_request": "Signature request",
"sign": "Sign",
"insufficient_balance": "Insufficient balance",
"unregistered_mns_notice": "Oops! Sending tokens to a username as elusive as a unicorn in a forest. Check if they're registered!",
"insufficient_balance_for_fee": "Insufficient balance for fee",
"send_&_receive": "Send & Receive",
"contract": "Contract",
"decimals": "Decimals",
"add_token_success_message" : "Hooray! The token has been successfully added to your AXS wallet! 🎉"
"add_token_success_message": "Hooray! The token has been successfully added to your AXS wallet! 🎉",
"import_account": "Import account",
"add_account": "Add account",
"private_key": "Private key",
"import_notice": "Imported accounts won’t be associated with your AXS wallet Secret Recovery Phrase.",
"imported": "Imported",
"view_private_key": "View private key",
"view_private_key_notice": "Warning: Never disclose this key. Anyone with your private keys can steal any assets held in your account.",
"removing_account": "Removing account",
"removing_account_warning": "Are you sure you want to remove this account?",
"remove": "Remove"
}
32 changes: 21 additions & 11 deletions lib/common/components/list/single_line_info_item.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
import 'package:datadashwallet/features/dapps/subfeatures/open_dapp/open_dapp_presenter.dart';
import 'package:datadashwallet/common/common.dart';
import 'package:datadashwallet/core/src/providers/providers_use_cases.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mxc_ui/mxc_ui.dart';

class SingleLineInfoItem extends HookConsumerWidget {
const SingleLineInfoItem({
super.key,
required this.title,
required this.value,
this.hint,
});
const SingleLineInfoItem(
{super.key,
required this.title,
required this.value,
this.hint,
this.valueActionIcon});

final String title;
final String value;
final String? hint;
final Widget? valueActionIcon;

@override
Widget build(BuildContext context, WidgetRef ref) {
final presenter = ref.read(openDAppPageContainer.actions);
final isAddress = presenter.isAddress(value);
late final _chainConfigurationUseCase =
ref.read(chainConfigurationUseCaseProvider);

final isAddress = Validation.isAddress(value);
return Padding(
padding: const EdgeInsets.symmetric(vertical: Sizes.spaceXSmall),
child: Row(
Expand All @@ -34,7 +40,9 @@ class SingleLineInfoItem extends HookConsumerWidget {
),
Expanded(
child: InkWell(
onTap: isAddress ? () => presenter.launchAddress(value) : null,
onTap: isAddress
? () => _chainConfigurationUseCase.launchAddress(value)
: null,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Expand All @@ -46,7 +54,9 @@ class SingleLineInfoItem extends HookConsumerWidget {
textAlign: TextAlign.end,
),
),
if (isAddress) ...[
if (valueActionIcon != null)
valueActionIcon!
else if (isAddress) ...[
const SizedBox(width: 8),
Icon(
MxcIcons.external_link,
Expand Down
32 changes: 32 additions & 0 deletions lib/common/utils/validation.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import 'package:datadashwallet/common/config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:web3dart/web3dart.dart';


class Validation {
static String? notEmpty(BuildContext context, String? value,
Expand Down Expand Up @@ -53,6 +55,17 @@ class Validation {
return null;
}

static String? checkEthereumPrivateKey(BuildContext context, String value) {
String ethereumPrivateKeyPattern = r'^[0-9a-fA-F]{64}$';

if (!RegExp(ethereumPrivateKeyPattern, caseSensitive: false)
.hasMatch(value)) {
return FlutterI18n.translate(context, 'invalid_format');
}

return null;
}

static String? checkMnsValidation(BuildContext context, String value) {
if (!((value.endsWith('.mxc') || value.endsWith('.MXC')) &&
value.length > 4)) {
Expand Down Expand Up @@ -118,4 +131,23 @@ class Validation {

return regex.hasMatch(value);
}


static bool isAddress(String address) {
try {
EthereumAddress.fromHex(address);
return true;
} catch (e) {
return false;
}
}

static bool isPrivateKey(String privateKey) {
try {
EthPrivateKey.fromHex(privateKey);
return true;
} catch (e) {
return false;
}
}
}
2 changes: 1 addition & 1 deletion lib/features/common/account/account_cache_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AccountCacheRepository extends GlobalCacheRepository {

void addAccount(Account item) => accounts.value = [...accounts.value, item];
void removeAccount(Account item) => accounts.value =
accounts.value.where((e) => e.name != item.name).toList();
accounts.value.where((e) => e.address != item.address).toList();
void updateAccount(Account item) => accounts.value = accounts.value.map((e) {
if (item.address == account.value!.address) {
account.value = item;
Expand Down
23 changes: 23 additions & 0 deletions lib/features/common/account/account_use_case.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,33 @@ class AccountUseCase extends ReactiveUseCase {
getAccountsNames();
}

/// Deletes the given account, If the account is selected will select the index 0 account
/// This is only used to delete the imported accounts.
void removeAccount(Account item) async {
_accountCacheRepository.removeAccount(item);
final items = _accountCacheRepository.accountItems;
update(accounts, items);
}

bool isAccountSelected(Account item) {
return (item.address == account.value!.address);
}

void changeAccount(Account item) {
update(account, item);
}

int findAccountsLastIndex() {
int lastIndex = 0;
for (Account account in accounts.value.reversed) {
if (!account.isCustom) {
lastIndex = int.parse(account.name);
break;
}
}
return lastIndex;
}

void resetXsdConversionRate(double value) {
_accountCacheRepository.setXsdConversionRate(value);
update(xsdConversionRate, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,7 @@ class OpenDAppPresenter extends CompletePresenter<OpenDAppState> {
}

bool isAddress(String address) {
try {
EthereumAddress.fromHex(address);
return true;
} catch (e) {
return false;
}
return Validation.isAddress(address);
}

void addAsset(int id, Map<String, dynamic> data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ class AddAssetInfo extends ConsumerWidget {
title: FlutterI18n.translate(context, 'contract'),
value: contractAddress ?? ''));
infoList.add(SingleLineInfoItem(
title: FlutterI18n.translate(context, 'symbol'), value: symbol ?? ''));
title: FlutterI18n.translate(context, 'symbol'),
value: symbol ?? '',
));
infoList.add(SingleLineInfoItem(
title: FlutterI18n.translate(context, 'decimals'),
value: decimalsString));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import 'package:datadashwallet/features/security/security.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mxc_ui/mxc_ui.dart';

import 'passcode_authenticate_user_presenter.dart';
import 'passcode_authenticate_user_state.dart';

class PasscodeAuthenticateUserPage extends PasscodeBasePage {
const PasscodeAuthenticateUserPage(
{Key? key, this.change = false, this.dismissedDest})
: super(key: key);

final bool change;
final String? dismissedDest;

@override
String title(BuildContext context, WidgetRef ref) =>
FlutterI18n.translate(context, 'view_private_key');

@override
String hint(BuildContext context, WidgetRef ref) =>
FlutterI18n.translate(context, 'enter_your_passcode');

@override
String secondHint(BuildContext context, WidgetRef ref) =>
FlutterI18n.translate(context, 'view_private_key_notice');

@override
String? dismissedPage() => dismissedDest;

@override
ProviderBase<PasscodeBasePagePresenter> get presenter =>
passcodeAuthenticateUserContainer.actions;

@override
ProviderBase<PasscodeAuthenticateUserState> get state =>
passcodeAuthenticateUserContainer.state;

@override
Widget buildErrorMessage(BuildContext context, WidgetRef ref) => SizedBox(
height: 90,
width: 280,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (ref.watch(state).errorText != null) ...[
Text(
ref.watch(state).errorText!,
textAlign: TextAlign.center,
style: FontTheme.of(context).subtitle2.error(),
),
if (ref.watch(state).wrongInputCounter == 2) ...[
const SizedBox(height: 6),
Text(
FlutterI18n.translate(context, 'app_will_be_locked_alert'),
textAlign: TextAlign.center,
style: FontTheme.of(context).subtitle1.error(),
),
],
]
],
),
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:datadashwallet/core/core.dart';
import 'package:datadashwallet/features/security/security.dart';
import 'passcode_authenticate_user_state.dart';

final passcodeAuthenticateUserContainer = PresenterContainer<
PasscodeAuthenticateUserPresenter,
PasscodeAuthenticateUserState>(
() => PasscodeAuthenticateUserPresenter());

class PasscodeAuthenticateUserPresenter
extends PasscodeBasePagePresenter<PasscodeAuthenticateUserState> {
PasscodeAuthenticateUserPresenter()
: super(PasscodeAuthenticateUserState());

late final PasscodeUseCase _passcodeUseCase =
ref.read(passcodeUseCaseProvider);

@override
void onAllNumbersEntered(String? dismissedPage) async {
if (state.enteredNumbers.join('') != _passcodeUseCase.passcode.value) {
if (state.wrongInputCounter < 2) {
state.errorText = translate('incorrect_passcode')!;
state.wrongInputCounter++;
} else {
state.errorText = null;
state.wrongInputCounter = 0;
ref.read(passcodeUseCaseProvider).penaltyLock();
}
state.enteredNumbers = [];
notify();
return;
}

Future.delayed(
passcodeTransitionDuration,
() => notify(() => state.enteredNumbers = []),
);

navigator?.pop(true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:datadashwallet/features/security/security.dart';

class PasscodeAuthenticateUserState extends PasscodeBasePageState {
int wrongInputCounter = 0;

@override
List<Object?> get props => [
...super.props,
wrongInputCounter,
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ abstract class PasscodeBasePage extends HookConsumerWidget {

String hint(BuildContext context, WidgetRef ref);

String? secondHint(BuildContext context, WidgetRef ref) => null;

String description(BuildContext context, WidgetRef ref) => '';

String? dismissedPage() => null;
Expand Down Expand Up @@ -178,6 +180,13 @@ abstract class PasscodeBasePage extends HookConsumerWidget {
hint(context, ref),
style: FontTheme.of(context).body1.white(),
),
if (secondHint(context, ref) != null) ...[
const SizedBox(height: 16),
Text(
secondHint(context, ref)!,
style: FontTheme.of(context).body1.white(),
),
],
const SizedBox(height: 64),
SizedBox(
height: 57.5,
Expand Down
Loading

0 comments on commit 2b575b0

Please sign in to comment.