Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-438-add-nano

This commit is contained in:
fosse 2023-08-09 21:07:04 -04:00
commit bdbf360b73
7 changed files with 223 additions and 183 deletions

View file

@ -100,6 +100,7 @@ jobs:
run: |
cd /opt/android/cake_wallet
touch lib/.secrets.g.dart
touch cw_ethereum/lib/.secrets.g.dart
echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart
echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart
echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart

View file

@ -57,7 +57,8 @@ class Erc20Token extends CryptoCurrency with HiveObjectMixin {
static const boxName = 'Erc20Tokens';
@override
bool operator ==(other) => other is Erc20Token && other.contractAddress == contractAddress;
bool operator ==(other) => (other is Erc20Token && other.contractAddress == contractAddress) ||
(other is CryptoCurrency && other.title == title);
@override
int get hashCode => contractAddress.hashCode;

View file

@ -156,15 +156,19 @@ abstract class EthereumWalletBase
final _credentials = credentials as EthereumTransactionCredentials;
final outputs = _credentials.outputs;
final hasMultiDestination = outputs.length > 1;
final _erc20Balance = balance[_credentials.currency]!;
final CryptoCurrency transactionCurrency =
balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
final _erc20Balance = balance[transactionCurrency]!;
BigInt totalAmount = BigInt.zero;
int exponent =
_credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18;
int exponent = transactionCurrency is Erc20Token ? transactionCurrency.decimal : 18;
num amountToEthereumMultiplier = pow(10, exponent);
// so far this can not be made with Ethereum as Ethereum does not support multiple recipients
if (hasMultiDestination) {
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
throw EthereumTransactionCreationException(_credentials.currency);
throw EthereumTransactionCreationException(transactionCurrency);
}
final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble(
@ -172,7 +176,7 @@ abstract class EthereumWalletBase
totalAmount = BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw EthereumTransactionCreationException(_credentials.currency);
throw EthereumTransactionCreationException(transactionCurrency);
}
} else {
final output = outputs.first;
@ -185,7 +189,7 @@ abstract class EthereumWalletBase
: BigInt.from(totalOriginalAmount * amountToEthereumMultiplier);
if (_erc20Balance.balance < totalAmount) {
throw EthereumTransactionCreationException(_credentials.currency);
throw EthereumTransactionCreationException(transactionCurrency);
}
}
@ -195,11 +199,10 @@ abstract class EthereumWalletBase
amount: totalAmount.toString(),
gas: _estimatedGas!,
priority: _credentials.priority!,
currency: _credentials.currency,
currency: transactionCurrency,
exponent: exponent,
contractAddress: _credentials.currency is Erc20Token
? (_credentials.currency as Erc20Token).contractAddress
: null,
contractAddress:
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
);
return pendingEthereumTransaction;

View file

@ -49,6 +49,7 @@ class MainActions {
case WalletType.ethereum:
case WalletType.nano:
case WalletType.banano:
case WalletType.monero:
if (viewModel.isEnabledBuyAction) {
final uri = getIt.get<OnRamperBuyProvider>().requestUrl();
if (DeviceInfo.instance.isMobile) {
@ -59,13 +60,6 @@ class MainActions {
}
}
break;
case WalletType.monero:
if (viewModel.isEnabledBuyAction) {
// final uri = getIt.get<PayfuraBuyProvider>().requestUrl();
final uri = Uri.parse("https://monero.com/trade");
await launchUrl(uri);
}
break;
default:
await showPopUp<void>(
context: context,

View file

@ -43,30 +43,31 @@ class DashboardPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(body: LayoutBuilder(
builder: (context, constraints) {
if (DeviceInfo.instance.isDesktop) {
if (constraints.maxWidth > ResponsiveLayoutUtil.kDesktopMaxDashBoardWidthConstraint) {
return getIt.get<DesktopSidebarWrapper>();
} else {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) {
if (DeviceInfo.instance.isDesktop) {
if (constraints.maxWidth > ResponsiveLayoutUtil.kDesktopMaxDashBoardWidthConstraint) {
return getIt.get<DesktopSidebarWrapper>();
} else {
return _DashboardPageView(
balancePage: balancePage,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
);
}
} else if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
return _DashboardPageView(
balancePage: balancePage,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
);
} else {
return getIt.get<DesktopSidebarWrapper>();
}
} else if (ResponsiveLayoutUtil.instance.shouldRenderMobileUI()) {
return _DashboardPageView(
balancePage: balancePage,
dashboardViewModel: dashboardViewModel,
addressListViewModel: addressListViewModel,
);
} else {
return getIt.get<DesktopSidebarWrapper>();
}
},
));
},
),
);
}
}
@ -89,13 +90,19 @@ class _DashboardPageView extends BasePage {
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Theme.of(context).colorScheme.secondary,
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor,
], begin: Alignment.topRight, end: Alignment.bottomLeft)),
child: scaffold);
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Theme.of(context).colorScheme.secondary,
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor,
],
begin: Alignment.topRight,
end: Alignment.bottomLeft,
),
),
child: scaffold,
);
@override
bool get resizeToAvoidBottomInset => false;
@ -106,29 +113,30 @@ class _DashboardPageView extends BasePage {
@override
Widget middle(BuildContext context) {
return SyncIndicator(
dashboardViewModel: dashboardViewModel,
onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync));
dashboardViewModel: dashboardViewModel,
onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync),
);
}
@override
Widget trailing(BuildContext context) {
final menuButton = Image.asset('assets/images/menu.png',
color: Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor);
final menuButton = Image.asset(
'assets/images/menu.png',
color: Theme.of(context).accentTextTheme.displayMedium!.backgroundColor,
);
return Container(
alignment: Alignment.centerRight,
width: 40,
child: TextButton(
// FIX-ME: Style
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () => onOpenEndDrawer(),
child: Semantics(
label: S.of(context).wallet_menu, child: menuButton)));
alignment: Alignment.centerRight,
width: 40,
child: TextButton(
// FIX-ME: Style
//highlightColor: Colors.transparent,
//splashColor: Colors.transparent,
//padding: EdgeInsets.all(0),
onPressed: () => onOpenEndDrawer(),
child: Semantics(label: S.of(context).wallet_menu, child: menuButton),
),
);
}
final DashboardViewModel dashboardViewModel;
@ -142,56 +150,65 @@ class _DashboardPageView extends BasePage {
Widget body(BuildContext context) {
final controller = PageController(initialPage: initialPage);
reaction((_) => dashboardViewModel.shouldShowMarketPlaceInDashboard, (bool value) {
if (!dashboardViewModel.shouldShowMarketPlaceInDashboard) {
controller.jumpToPage(0);
}
pages.clear();
_isEffectsInstalled = false;
_setEffects(context);
reaction(
(_) => dashboardViewModel.shouldShowMarketPlaceInDashboard,
(bool value) {
if (!dashboardViewModel.shouldShowMarketPlaceInDashboard) {
controller.jumpToPage(0);
}
pages.clear();
_isEffectsInstalled = false;
_setEffects(context);
if (value) {
controller.jumpToPage(1);
} else {
controller.jumpToPage(0);
}
});
if (value) {
controller.jumpToPage(1);
} else {
controller.jumpToPage(0);
}
},
);
_setEffects(context);
return SafeArea(
minimum: EdgeInsets.only(bottom: 24),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Observer(builder: (context) {
return PageView.builder(
minimum: EdgeInsets.only(bottom: 24),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Observer(
builder: (context) {
return PageView.builder(
controller: controller,
itemCount: pages.length,
itemBuilder: (context, index) => pages[index]);
})),
Padding(
padding: EdgeInsets.only(bottom: 24, top: 10),
child: Observer(builder: (context) {
return ExcludeSemantics(
child: SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor,
activeDotColor: Theme.of(context)
.accentTextTheme!
.headlineMedium!
.backgroundColor!),
itemBuilder: (context, index) => pages[index],
);
},
),
),
Padding(
padding: EdgeInsets.only(bottom: 24, top: 10),
child: Observer(
builder: (context) {
return ExcludeSemantics(
child: SmoothPageIndicator(
controller: controller,
count: pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor,
activeDotColor:
Theme.of(context).accentTextTheme.headlineMedium!.backgroundColor!,
),
);
}
)),
Observer(builder: (_) {
),
);
},
),
),
Observer(
builder: (_) {
return ClipRect(
child: Container(
margin: const EdgeInsets.only(left: 16, right: 16),
@ -204,10 +221,7 @@ class _DashboardPageView extends BasePage {
: Colors.transparent,
width: 1,
),
color: Theme.of(context)
.textTheme!
.titleLarge!
.backgroundColor!,
color: Theme.of(context).textTheme.titleLarge!.backgroundColor!,
),
child: Container(
padding: EdgeInsets.only(left: 32, right: 32),
@ -215,48 +229,48 @@ class _DashboardPageView extends BasePage {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: MainActions.all
.where((element) => element.canShow?.call(dashboardViewModel) ?? true)
.map((action) => Semantics(
button: true,
enabled: (action.isEnabled
?.call(dashboardViewModel) ??
true),
child: ActionButton(
image: Image.asset(action.image,
height: 24,
width: 24,
color: action.isEnabled?.call(
dashboardViewModel) ??
true
? Theme.of(context)
.accentTextTheme!
.displayMedium!
.backgroundColor!
: Theme.of(context)
.accentTextTheme!
.displaySmall!
.backgroundColor!),
title: action.name(context),
onClick: () async => await action.onTap(
context, dashboardViewModel),
textColor: action.isEnabled
?.call(dashboardViewModel) ??
true
? null
.map(
(action) => Semantics(
button: true,
enabled: (action.isEnabled?.call(dashboardViewModel) ?? true),
child: ActionButton(
image: Image.asset(
action.image,
height: 24,
width: 24,
color: action.isEnabled?.call(dashboardViewModel) ?? true
? Theme.of(context)
.accentTextTheme
.displayMedium!
.backgroundColor!
: Theme.of(context)
.accentTextTheme!
.accentTextTheme
.displaySmall!
.backgroundColor!,
),
))
title: action.name(context),
onClick: () async =>
await action.onTap(context, dashboardViewModel),
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
? null
: Theme.of(context)
.accentTextTheme
.displaySmall!
.backgroundColor!,
),
),
)
.toList(),
),
),
),
),
);
}),
],
));
},
),
],
),
);
}
void _setEffects(BuildContext context) async {
@ -264,7 +278,8 @@ class _DashboardPageView extends BasePage {
return;
}
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
pages.add(Semantics(
pages.add(
Semantics(
label: S.of(context).market_place,
child: MarketPlacePage(
dashboardViewModel: dashboardViewModel,
@ -274,73 +289,92 @@ class _DashboardPageView extends BasePage {
);
}
pages.add(Semantics(label: S.of(context).balance_page, child: balancePage));
pages.add(Semantics(
pages.add(
Semantics(
label: S.of(context).settings_transactions,
child: TransactionsPage(dashboardViewModel: dashboardViewModel)));
child: TransactionsPage(dashboardViewModel: dashboardViewModel),
),
);
_isEffectsInstalled = true;
autorun((_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
autorun(
(_) async {
if (!dashboardViewModel.isOutdatedElectrumWallet) {
return;
}
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
await Future<void>.delayed(Duration(seconds: 1));
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).pre_seed_title,
alertContent: S.of(context).outdated_electrum_wallet_description,
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop());
});
}
});
buttonAction: () => Navigator.of(context).pop(),
);
},
);
}
},
);
_showReleaseNotesPopup(context);
var needToPresentYat = false;
var isInactive = false;
_onInactiveSub = rootKey.currentState!.isInactive.listen((inactive) {
isInactive = inactive;
_onInactiveSub = rootKey.currentState!.isInactive.listen(
(inactive) {
isInactive = inactive;
if (needToPresentYat) {
Future<void>.delayed(Duration(milliseconds: 500)).then((_) {
showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (_) => YatEmojiId(dashboardViewModel.yatStore.emoji));
needToPresentYat = false;
});
}
});
if (needToPresentYat) {
Future<void>.delayed(Duration(milliseconds: 500)).then(
(_) {
showPopUp<void>(
context: navigatorKey.currentContext!,
builder: (_) => YatEmojiId(dashboardViewModel.yatStore.emoji),
);
needToPresentYat = false;
},
);
}
},
);
dashboardViewModel.yatStore.emojiIncommingStream.listen((String emoji) {
if (!_isEffectsInstalled || emoji.isEmpty) {
return;
}
dashboardViewModel.yatStore.emojiIncommingStream.listen(
(String emoji) {
if (!_isEffectsInstalled || emoji.isEmpty) {
return;
}
needToPresentYat = true;
});
needToPresentYat = true;
},
);
}
void _showReleaseNotesPopup(BuildContext context) async {
final sharedPrefs = await SharedPreferences.getInstance();
final currentAppVersion =
VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion);
VersionComparator.getExtendedVersionNumber(dashboardViewModel.settingsStore.appVersion);
final lastSeenAppVersion = sharedPrefs.getInt(PreferencesKey.lastSeenAppVersion);
final isNewInstall = sharedPrefs.getBool(PreferencesKey.isNewInstall);
if (currentAppVersion != lastSeenAppVersion && !isNewInstall!) {
Future<void>.delayed(Duration(seconds: 1), () {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ReleaseNotesScreen(
title: 'Version ${dashboardViewModel.settingsStore.appVersion}');
});
});
Future<void>.delayed(
Duration(seconds: 1),
() {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ReleaseNotesScreen(
title: 'Version ${dashboardViewModel.settingsStore.appVersion}',
);
},
);
},
);
sharedPrefs.setInt(PreferencesKey.lastSeenAppVersion, currentAppVersion);
} else if (isNewInstall!) {

View file

@ -29,7 +29,9 @@ abstract class ExchangeTradeViewModelBase with Store {
required this.sendViewModel})
: trade = tradesStore.trade!,
isSendable = tradesStore.trade!.from == wallet.currency ||
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto,
tradesStore.trade!.provider == ExchangeProviderDescription.xmrto ||
(wallet.currency == CryptoCurrency.eth &&
tradesStore.trade!.from.tag == CryptoCurrency.eth.title),
items = ObservableList<ExchangeTradeItem>() {
switch (trade.provider) {
case ExchangeProviderDescription.xmrto:
@ -103,6 +105,7 @@ abstract class ExchangeTradeViewModelBase with Store {
final output = sendViewModel.outputs.first;
output.address = trade.inputAddress ?? '';
output.setCryptoAmount(trade.amount);
sendViewModel.selectedCryptoCurrency = trade.from;
await sendViewModel.createTransaction();
}

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:hive/hive.dart';
@ -80,6 +81,9 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
case WalletType.litecoin:
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
case WalletType.ethereum:
return ethereum!.createEthereumRestoreWalletFromSeedCredentials(
name: name, mnemonic: restoreWallet.mnemonicSeed ?? '', password: password);
default:
throw Exception('Unexpected type: ${type.toString()}');
}