Merge remote-tracking branch 'remotes/upstream/main'

This commit is contained in:
holecekp
2023-02-28 16:44:44 +01:00
45 changed files with 2635 additions and 1095 deletions
+1 -1
View File
@@ -57,7 +57,7 @@ class AnyPayPayment {
List<String> get outAddresses {
return instructions
.map((instuction) => instuction.outputs.map((out) => out.address))
.map((instruction) => instruction.outputs.map((out) => out.address))
.expand((e) => e)
.toList();
}
+4 -4
View File
@@ -22,13 +22,13 @@ class WalletLoadingService {
final wallet = await walletService.openWallet(name, password);
if (type == WalletType.monero) {
await upateMoneroWalletPassword(wallet);
await updateMoneroWalletPassword(wallet);
}
return wallet;
}
Future<void> upateMoneroWalletPassword(WalletBase wallet) async {
Future<void> updateMoneroWalletPassword(WalletBase wallet) async {
final key = PreferencesKey.moneroWalletUpdateV1Key(wallet.name);
var isPasswordUpdated = sharedPreferences.getBool(key) ?? false;
@@ -37,8 +37,8 @@ class WalletLoadingService {
}
final password = generateWalletPassword();
// Save new generated password with backup key for case
// if wallet will change password, but it will faild to updated in secure storage
// Save new generated password with backup key for case where
// wallet will change password, but it will fail to update in secure storage
final bakWalletName = '#__${wallet.name}_bak__#';
await keyService.saveWalletPassword(walletName: bakWalletName, password: password);
await wallet.changePassword(password);
+4
View File
@@ -23,7 +23,9 @@ class LanguageService {
'ar': 'العربية (Arabic)',
'tr': 'Türkçe (Turkish)',
'my': 'မြန်မာ (Burmese)',
'bg': 'Български (Bulgarian)',
'cs': 'čeština (Czech)',
'ur': 'اردو (Urdu)'
};
static const Map<String, String> localeCountryCode = {
@@ -46,7 +48,9 @@ class LanguageService {
'ar': 'sau',
'tr': 'tur',
'my': 'mmr',
'bg': 'bgr',
'cs': 'czk',
'ur': 'pak'
};
static final list = <String, String> {};
+18 -4
View File
@@ -44,10 +44,24 @@ class AddressResolver {
if (text.startsWith('@') && !text.substring(1).contains('@')) {
final formattedName = text.substring(1);
final twitterUser = await TwitterApi.lookupUserByName(userName: formattedName);
final address = extractAddressByType(
raw: twitterUser.description ?? '', type: CryptoCurrency.fromString(ticker));
if (address != null) {
return ParsedAddress.fetchTwitterAddress(address: address, name: text);
final addressFromBio = extractAddressByType(
raw: twitterUser.description, type: CryptoCurrency.fromString(ticker));
if (addressFromBio != null) {
return ParsedAddress.fetchTwitterAddress(address: addressFromBio, name: text);
}
final tweets = twitterUser.tweets;
if (tweets != null) {
var subString = StringBuffer();
tweets.forEach((item) {
subString.writeln(item.text);
});
final userTweetsText = subString.toString();
final addressFromPinnedTweet =
extractAddressByType(raw: userTweetsText, type: CryptoCurrency.fromString(ticker));
if (addressFromPinnedTweet != null) {
return ParsedAddress.fetchTwitterAddress(address: addressFromPinnedTweet, name: text);
}
}
}
if (!text.startsWith('@') && text.contains('@') && !text.contains('.')) {
+1
View File
@@ -29,6 +29,7 @@ class PreferencesKey {
static const moneroWalletPasswordUpdateV1Base = 'monero_wallet_update_v1';
static const pinTimeOutDuration = 'pin_timeout_duration';
static const lastAuthTimeMilliseconds = 'last_auth_time_milliseconds';
static const lastPopupDate = 'last_popup_date';
static String moneroWalletUpdateV1Key(String name)
+1 -1
View File
@@ -330,7 +330,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
getIt.get<ContactListPage>());
case Routes.pickerAddressBook:
final selectedCurrency = settings.arguments as CryptoCurrency;
final selectedCurrency = settings.arguments as CryptoCurrency?;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ContactListPage>(param1: selectedCurrency));
+5 -10
View File
@@ -1,10 +1,9 @@
import 'dart:io';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:share_plus/share_plus.dart';
import 'package:cross_file/cross_file.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
@@ -110,7 +109,7 @@ class BackupPage extends BasePage {
if (Platform.isAndroid) {
onExportAndroid(context, backup);
} else {
await share(backup);
await share(backup, context);
}
},
actionLeftButton: () => Navigator.of(dialogContext).pop());
@@ -140,18 +139,14 @@ class BackupPage extends BasePage {
},
actionLeftButton: () async {
Navigator.of(dialogContext).pop();
await share(backup);
await share(backup, context);
});
});
}
Future<void> share(BackupExportFile backup) async {
const mimeType = 'application/*';
Future<void> share(BackupExportFile backup, BuildContext context) async {
final path = await backupViewModelBase.saveBackupFileLocally(backup);
await Share.shareXFiles(<XFile>[XFile(
path,
name: backup.name,
mimeType: mimeType)]);
await ShareUtil.shareFile(filePath: path, fileName: backup.name, context: context);
await backupViewModelBase.removeBackupFileLocally(backup);
}
}
@@ -8,7 +8,6 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
@@ -229,7 +228,8 @@ class DashboardPage extends BasePage {
}
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
@@ -239,6 +239,7 @@ class DashboardPage extends BasePage {
buttonText: S.of(context).understand,
buttonAction: () => Navigator.of(context).pop());
});
}
});
var needToPresentYat = false;
@@ -1,10 +1,8 @@
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter/material.dart';
@@ -15,7 +13,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:mobx/mobx.dart';
import 'package:share_plus/share_plus.dart';
class AddressPage extends BasePage {
AddressPage({
@@ -100,7 +97,12 @@ class AddressPage extends BasePage {
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => Share.share(addressListViewModel.address.address),
onPressed: () {
ShareUtil.share(
text: addressListViewModel.address.address,
context: context,
);
},
icon: shareImage,
),
) : null;
@@ -115,7 +117,8 @@ class AddressPage extends BasePage {
}
await Future<void>.delayed(Duration(seconds: 1));
await showPopUp<void>(
if (context.mounted) {
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
@@ -129,6 +132,7 @@ class AddressPage extends BasePage {
Navigator.of(context).pop();
});
});
}
});
return KeyboardActions(
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cake_wallet/generated/i18n.dart';
class TransactionRow extends StatelessWidget {
TransactionRow(
@@ -9,6 +8,7 @@ class TransactionRow extends StatelessWidget {
required this.formattedAmount,
required this.formattedFiatAmount,
required this.isPending,
required this.title,
required this.onTap});
final VoidCallback onTap;
@@ -17,6 +17,7 @@ class TransactionRow extends StatelessWidget {
final String formattedAmount;
final String formattedFiatAmount;
final bool isPending;
final String title;
@override
Widget build(BuildContext context) {
@@ -49,11 +50,7 @@ class TransactionRow extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
(direction == TransactionDirection.incoming
? S.of(context).received
: S.of(context).sent) +
(isPending ? S.of(context).pending : ''),
Text(title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
@@ -60,7 +60,8 @@ class TransactionsPage extends StatelessWidget {
formattedFiatAmount:
dashboardViewModel.balanceViewModel.isFiatDisabled
? '' : item.formattedFiatAmount,
isPending: transaction.isPending));
isPending: transaction.isPending,
title: item.formattedTitle + item.formattedStatus));
}
if (item is TradeListItem) {
@@ -112,7 +112,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
width: 16,
color: Theme.of(context).primaryTextTheme!.overline!.color!);
_setEffects(context);
_setEffects();
return Container(
child: ScrollableWithBottomSection(
@@ -234,7 +234,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
);
}
void _setEffects(BuildContext context) {
void _setEffects() {
if (_effectsInstalled) {
return;
}
@@ -245,12 +245,12 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertTitle: S.of(popupContext).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
});
}
@@ -259,118 +259,24 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
builder: (BuildContext popupContext) {
return ConfirmSendingAlert(
alertTitle: S.of(context).confirm_sending,
amount: S.of(context).send_amount,
alertTitle: S.of(popupContext).confirm_sending,
amount: S.of(popupContext).send_amount,
amountValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction!.amountFormatted,
fee: S.of(context).send_fee,
fee: S.of(popupContext).send_fee,
feeValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction!.feeFormatted,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(popupContext).ok,
leftButtonText: S.of(popupContext).cancel,
actionRightButton: () async {
Navigator.of(context).pop();
Navigator.of(popupContext).pop();
await widget.exchangeTradeViewModel.sendViewModel
.commitTransaction();
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return Observer(builder: (_) {
final state = widget
.exchangeTradeViewModel.sendViewModel.state;
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
Center(
child: Padding(
padding: EdgeInsets.only(
top: 220, left: 24, right: 24),
child: Text(
S.of(context).send_success(widget
.exchangeTradeViewModel
.wallet
.currency
.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme!
.headline6!
.color,
decoration: TextDecoration.none,
),
),
),
),
Positioned(
left: 24,
right: 24,
bottom: 24,
child: PrimaryButton(
onPressed: () =>
Navigator.of(context).pop(),
text: S.of(context).send_got_it,
color: Theme.of(context)
.accentTextTheme!
.bodyText1!
.color!,
textColor: Colors.white))
],
);
}
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.backgroundColor
.withOpacity(0.25)),
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 220),
child: Text(
S.of(context).send_sending,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
decoration: TextDecoration.none,
),
),
),
),
),
)
],
);
});
});
transactionStatePopup();
},
actionLeftButton: () => Navigator.of(context).pop(),
actionLeftButton: () => Navigator.of(popupContext).pop(),
feeFiatAmount: widget.exchangeTradeViewModel
.pendingTransactionFeeFiatAmountFormatted,
fiatAmountValue: widget.exchangeTradeViewModel
@@ -385,12 +291,12 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
builder: (BuildContext popupContext) {
return AlertWithOneAction(
alertTitle: S.of(context).sending,
alertContent: S.of(context).transaction_sent,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
alertTitle: S.of(popupContext).sending,
alertContent: S.of(popupContext).transaction_sent,
buttonText: S.of(popupContext).ok,
buttonAction: () => Navigator.of(popupContext).pop());
});
});
}
@@ -398,4 +304,102 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
_effectsInstalled = true;
}
void transactionStatePopup() {
showPopUp<void>(
context: context,
builder: (BuildContext popupContext) {
return Observer(builder: (_) {
final state = widget
.exchangeTradeViewModel.sendViewModel.state;
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(popupContext).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
Center(
child: Padding(
padding: EdgeInsets.only(
top: 220, left: 24, right: 24),
child: Text(
S.of(popupContext).send_success(widget
.exchangeTradeViewModel
.wallet
.currency
.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(popupContext)
.primaryTextTheme!
.headline6!
.color,
decoration: TextDecoration.none,
),
),
),
),
Positioned(
left: 24,
right: 24,
bottom: 24,
child: PrimaryButton(
onPressed: () =>
Navigator.of(popupContext).pop(),
text: S.of(popupContext).send_got_it,
color: Theme.of(popupContext)
.accentTextTheme!
.bodyText1!
.color!,
textColor: Colors.white))
],
);
}
return Stack(
children: <Widget>[
Container(
color: Theme.of(popupContext).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(popupContext)
.backgroundColor
.withOpacity(0.25)),
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 220),
child: Text(
S.of(popupContext).send_sending,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(popupContext).primaryTextTheme!.headline6!.color!,
decoration: TextDecoration.none,
),
),
),
),
),
)
],
);
});
});
}
}
+7 -4
View File
@@ -1,13 +1,11 @@
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:share_plus/share_plus.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/di.dart';
@@ -101,7 +99,12 @@ class ReceivePage extends BasePage {
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
iconSize: 25,
onPressed: () => Share.share(addressListViewModel.address.address),
onPressed: () {
ShareUtil.share(
text: addressListViewModel.address.address,
context: context,
);
},
icon: shareImage
)
);
+7 -4
View File
@@ -1,12 +1,11 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:share_plus/share_plus.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@@ -159,8 +158,12 @@ class WalletSeedPage extends BasePage {
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
onPressed: () =>
Share.share(walletSeedViewModel.seed),
onPressed: () {
ShareUtil.share(
text: walletSeedViewModel.seed,
context: context,
);
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white),
+4 -2
View File
@@ -339,7 +339,7 @@ class SendPage extends BasePage {
showErrorValidationAlert(context);
return;
}
await sendViewModel.createTransaction();
},
@@ -379,7 +379,8 @@ class SendPage extends BasePage {
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showPopUp<void>(
if (context.mounted) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return ConfirmSendingAlert(
@@ -423,6 +424,7 @@ class SendPage extends BasePage {
},
actionLeftButton: () => Navigator.of(context).pop());
});
}
});
}
@@ -2,6 +2,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arro
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@@ -35,10 +36,11 @@ class ConnectionSyncPage extends BasePage {
handler: (context) => _presentReconnectAlert(context),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
SettingsCellWithArrow(
title: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
),
if (dashboardViewModel.hasRescan)
SettingsCellWithArrow(
title: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
),
StandardListSeparator(padding: EdgeInsets.symmetric(horizontal: 24)),
NodeHeaderListRow(
title: S.of(context).add_new_node,
@@ -30,6 +30,8 @@ class SettingsLinkProviderCell extends StandardListRow {
color: Palette.blueCraiola));
static void _launchUrl(String url) async {
if (await canLaunch(url)) await launch(url, forceSafariVC: false);
try {
await launch(url, forceSafariVC: false);
} catch (e) {}
}
}
}
@@ -1,131 +0,0 @@
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu_item.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/palette.dart';
class WalletMenu {
WalletMenu(this.context, this.walletListViewModel);
final WalletListViewModel walletListViewModel;
final BuildContext context;
final List<WalletMenuItem> menuItems = [
WalletMenuItem(
title: S.current.wallet_list_load_wallet,
firstGradientColor: Palette.cornflower,
secondGradientColor: Palette.royalBlue,
image: Image.asset('assets/images/load.png',
height: 24, width: 24, color: Colors.white)),
WalletMenuItem(
title: S.current.show_seed,
firstGradientColor: Palette.moderateOrangeYellow,
secondGradientColor: Palette.moderateOrange,
image: Image.asset('assets/images/eye_action.png',
height: 24, width: 24, color: Colors.white)),
WalletMenuItem(
title: S.current.remove,
firstGradientColor: Palette.lightRed,
secondGradientColor: Palette.persianRed,
image: Image.asset('assets/images/trash.png',
height: 24, width: 24, color: Colors.white)),
WalletMenuItem(
title: S.current.rescan,
firstGradientColor: Palette.shineGreen,
secondGradientColor: Palette.moderateGreen,
image: Image.asset('assets/images/scanner.png',
height: 24, width: 24, color: Colors.white))
];
List<WalletMenuItem> generateItemsForWalletMenu(bool isCurrentWallet) {
final items = <WalletMenuItem>[];
if (!isCurrentWallet) items.add(menuItems[0]);
if (isCurrentWallet) items.add(menuItems[1]);
if (!isCurrentWallet) items.add(menuItems[2]);
if (isCurrentWallet) items.add(menuItems[3]);
return items;
}
Future<void> action(
int index, WalletListItem wallet) async {
switch (index) {
case 0:
await Navigator.of(context).pushNamed(Routes.auth, arguments:
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (!isAuthenticatedSuccessfully) {
return;
}
try {
auth.changeProcessText(
S.of(context).wallet_list_loading_wallet(wallet.name));
await walletListViewModel.loadWallet(wallet);
auth.close();
Navigator.of(context).pop();
} catch (e) {
auth.changeProcessText(S
.of(context)
.wallet_list_failed_to_load(wallet.name, e.toString()));
}
});
break;
case 1:
await Navigator.of(context).pushNamed(Routes.auth, arguments:
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (!isAuthenticatedSuccessfully) {
return;
}
auth.close();
await Navigator.of(context).pushNamed(Routes.seed, arguments: false);
});
break;
case 2:
final isComfirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: 'Remove wallet',
alertContent: S.of(context).confirm_delete_wallet,
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).remove,
actionLeftButton: () => Navigator.of(context).pop(false),
actionRightButton: () => Navigator.of(context).pop(true));
});
if (isComfirmed == null || !isComfirmed) {
return;
}
await Navigator.of(context).pushNamed(Routes.auth, arguments:
(bool isAuthenticatedSuccessfully, AuthPageState auth) async {
if (!isAuthenticatedSuccessfully) {
return;
}
try {
auth.changeProcessText(
S.of(context).wallet_list_removing_wallet(wallet.name));
await walletListViewModel.remove(wallet);
auth.close();
} catch (e) {
auth.changeProcessText(S
.of(context)
.wallet_list_failed_to_remove(wallet.name, e.toString()));
}
});
break;
case 3:
await Navigator.of(context).pushNamed(Routes.rescan);
break;
default:
break;
}
}
}
@@ -1,16 +0,0 @@
import 'dart:ui';
import 'package:flutter/cupertino.dart';
class WalletMenuItem {
WalletMenuItem({
required this.title,
required this.firstGradientColor,
required this.secondGradientColor,
required this.image
});
final String title;
final Color firstGradientColor;
final Color secondGradientColor;
final Image image;
}
@@ -1,112 +0,0 @@
import 'dart:ui';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/alert_background.dart';
import 'package:cake_wallet/src/widgets/alert_close_button.dart';
class WalletMenuAlert extends StatelessWidget {
WalletMenuAlert({
required this.wallet,
required this.walletMenu,
required this.items
});
final WalletListItem wallet;
final WalletMenu walletMenu;
final List<WalletMenuItem> items;
final closeButton = Image.asset('assets/images/close.png',
color: Palette.darkBlueCraiola,
);
@override
Widget build(BuildContext context) {
return AlertBackground(
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(
left: 24,
right: 24,
),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(14)),
child: Container(
color: Theme.of(context).textTheme!.bodyText1!.decorationColor!,
padding: EdgeInsets.only(left: 24),
child: ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: items.length,
separatorBuilder: (context, _) => Container(
height: 1,
color: Theme.of(context).accentTextTheme!.subtitle1!.backgroundColor!,
),
itemBuilder: (_, index) {
final item = items[index];
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(
walletMenu.menuItems.indexOf(item),
wallet);
},
child: Container(
height: 60,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
height: 32,
width: 32,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(4)),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
item.firstGradientColor,
item.secondGradientColor
]
)
),
child: Center(
child: item.image,
),
),
SizedBox(width: 12),
Expanded(
child: Text(
item.title,
style: TextStyle(
color: Theme.of(context).primaryTextTheme!.headline6!.color!,
fontSize: 18,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
decoration: TextDecoration.none
),
)
)
],
),
),
);
},
),
),
),
),
AlertCloseButton(image: closeButton)
],
),
);
}
}
+2 -2
View File
@@ -10,7 +10,7 @@ class TwitterApi {
static const userPath = '/2/users/by/username/';
static Future<TwitterUser> lookupUserByName({required String userName}) async {
final queryParams = {'user.fields': 'description'};
final queryParams = {'user.fields': 'description', 'expansions': 'pinned_tweet_id'};
final headers = {'authorization': 'Bearer $twitterBearerToken'};
@@ -32,6 +32,6 @@ class TwitterApi {
throw Exception(responseJSON['errors'][0]['detail']);
}
return TwitterUser.fromJson(responseJSON['data'] as Map<String, dynamic>);
return TwitterUser.fromJson(responseJSON);
}
}
+35 -6
View File
@@ -1,16 +1,45 @@
class TwitterUser {
TwitterUser({required this.id, required this.username, required this.name, this.description});
TwitterUser(
{required this.id,
required this.username,
required this.name,
required this.description,
this.tweets});
final String id;
final String username;
final String name;
final String? description;
final String description;
final List<Tweet>? tweets;
factory TwitterUser.fromJson(Map<String, dynamic> json) {
return TwitterUser(
id: json['id'] as String,
username: json['username'] as String,
name: json['name'] as String,
description: json['description'] as String?);
id: json['data']['id'] as String,
username: json['data']['username'] as String,
name: json['data']['name'] as String,
description: json['data']['description'] as String? ?? '',
tweets: json['includes'] != null
? List.from(json['includes']['tweets'] as List)
.map((e) => Tweet.fromJson(e as Map<String, dynamic>))
.toList()
: null,
);
}
}
class Tweet {
Tweet({
required this.id,
required this.text,
});
final String id;
final String text;
factory Tweet.fromJson(Map<String, dynamic> json) {
return Tweet(
id: json['id'] as String,
text: json['text'] as String,
);
}
}
+35 -6
View File
@@ -1,16 +1,20 @@
import 'dart:convert';
import 'dart:io';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mailer/flutter_mailer.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ExceptionHandler {
static bool _hasError = false;
static const _coolDownDurationInDays = 7;
static void _saveException(String? error, StackTrace? stackTrace) async {
final appDocDir = await getApplicationDocumentsDirectory();
@@ -58,18 +62,33 @@ class ExceptionHandler {
}
}
static void onError(FlutterErrorDetails errorDetails) {
static void onError(FlutterErrorDetails errorDetails) async {
if (kDebugMode) {
FlutterError.presentError(errorDetails);
return;
}
if (_ignoreError(errorDetails.exception.toString())) {
return;
}
_saveException(errorDetails.exception.toString(), errorDetails.stack);
if (_hasError) {
final sharedPrefs = await SharedPreferences.getInstance();
final lastPopupDate =
DateTime.tryParse(sharedPrefs.getString(PreferencesKey.lastPopupDate) ?? '') ??
DateTime.now().subtract(Duration(days: _coolDownDurationInDays + 1));
final durationSinceLastReport = DateTime.now().difference(lastPopupDate).inDays;
if (_hasError || durationSinceLastReport < _coolDownDurationInDays) {
return;
}
_hasError = true;
sharedPrefs.setString(PreferencesKey.lastPopupDate, DateTime.now().toString());
WidgetsBinding.instance.addPostFrameCallback(
(timeStamp) async {
await showPopUp<void>(
@@ -98,8 +117,18 @@ class ExceptionHandler {
}
/// Ignore User related errors or system errors
static bool _ignoreError(String error) {
return error.contains("errno = 103") || // SocketException: Software caused connection abort
error.contains("errno = 9"); // SocketException: Bad file descriptor (iOS socket exception)
}
static bool _ignoreError(String error) =>
_ignoredErrors.any((element) => error.contains(element));
static const List<String> _ignoredErrors = const [
"errno = 103", // SocketException: Software caused connection abort
"errno = 9", // SocketException: Bad file descriptor
"errno = 32", // SocketException: Write failed (OS Error: Broken pipe)
"errno = 60", // SocketException: Operation timed out
"errno = 110", // SocketException: Connection timed out
"errno = 54", // SocketException: Connection reset by peer
"errno = 49", // SocketException: Can't assign requested address
"errno = 28", // OS Error: No space left on device
"PERMISSION_NOT_GRANTED",
];
}
+37
View File
@@ -0,0 +1,37 @@
import 'package:flutter/material.dart';
import 'package:share_plus/share_plus.dart';
import 'package:cross_file/cross_file.dart';
class ShareUtil {
static const _mimeType = 'application/*';
static void share({required String text, required BuildContext context}) {
Share.share(
text,
sharePositionOrigin: _sharePosition(context),
);
}
static Future<void> shareFile({
required String filePath,
required String fileName,
required BuildContext context,
}) async {
Share.shareXFiles(
<XFile>[
XFile(
filePath,
name: fileName,
mimeType: _mimeType,
)
],
sharePositionOrigin: _sharePosition(context),
);
}
static Rect? _sharePosition(BuildContext context) {
final box = context.findRenderObject() as RenderBox?;
return box!.localToGlobal(Offset.zero) & box.size;
}
}
@@ -239,7 +239,7 @@ abstract class DashboardViewModelBase with Store {
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>
wallet;
bool get hasRescan => wallet.type == WalletType.monero;
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
BalanceViewModel balanceViewModel;
@@ -1,5 +1,7 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
@@ -11,6 +13,7 @@ import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cw_core/keyable.dart';
import 'package:cw_core/wallet_type.dart';
class TransactionListItem extends ActionListItem with Keyable {
TransactionListItem(
{required this.transaction,
@@ -35,6 +38,30 @@ class TransactionListItem extends ActionListItem with Keyable {
? '---'
: transaction.amountFormatted();
}
String get formattedTitle {
if (transaction.direction == TransactionDirection.incoming) {
return S.current.received;
}
return S.current.sent;
}
String get formattedPendingStatus {
if (transaction.confirmations >= 0 && transaction.confirmations < 10) {
return ' (${transaction.confirmations}/10)';
}
return '';
}
String get formattedStatus {
if (transaction.direction == TransactionDirection.incoming) {
if (balanceViewModel.wallet.type == WalletType.monero ||
balanceViewModel.wallet.type == WalletType.haven) {
return formattedPendingStatus;
}
}
return transaction.isPending ? S.current.pending : '';
}
String get formattedFiatAmount {
var amount = '';
@@ -192,7 +192,7 @@ abstract class ExchangeViewModelBase with Store {
ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates;
@computed
TransactionPriority get transactionPriority {
final priority = _settingsStore.priority[wallet.type];
@@ -308,10 +308,11 @@ abstract class ExchangeViewModelBase with Store {
Future<void> _calculateBestRate() async {
final amount = double.tryParse(isFixedRateMode ? receiveAmount : depositAmount) ?? 1;
final _providers = _tradeAvailableProviders
.where((element) => !isFixedRateMode || element.supportsFixedRate).toList();
final result = await Future.wait<double>(
_tradeAvailableProviders
.where((element) => !isFixedRateMode || element.supportsFixedRate)
.map((element) => element.fetchRate(
_providers.map((element) => element.fetchRate(
from: depositCurrency,
to: receiveCurrency,
amount: amount,
@@ -324,7 +325,12 @@ abstract class ExchangeViewModelBase with Store {
for (int i=0;i<result.length;i++) {
if (result[i] != 0) {
/// add this provider as its valid for this trade
_sortedAvailableProviders[result[i]] = _tradeAvailableProviders[i];
try {
_sortedAvailableProviders[result[i]] = _providers[i];
} catch (e) {
// will throw "Concurrent modification during iteration" error if modified at the same
// time [createTrade] is called, as this is not a normal map, but a sorted map
}
}
}
if (_sortedAvailableProviders.isNotEmpty) {
+5 -4
View File
@@ -7,7 +7,6 @@ import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
part 'support_view_model.g.dart';
@@ -19,7 +18,9 @@ abstract class SupportViewModelBase with Store {
RegularListItem(
title: S.current.faq,
handler: (BuildContext context) async {
if (await canLaunch(url)) await launch(url);
try {
await launch(url);
} catch (e) {}
},
),
LinkListItem(
@@ -42,7 +43,7 @@ abstract class SupportViewModelBase with Store {
title: 'Telegram',
icon: 'assets/images/Telegram.png',
linkTitle: '@cakewallet_bot',
link: 'https:t.me/cakewallet_bot'),
link: 'https://t.me/cakewallet_bot'),
LinkListItem(
title: 'Twitter',
icon: 'assets/images/Twitter.png',
@@ -84,7 +85,7 @@ abstract class SupportViewModelBase with Store {
// link: 'mailto:support@y.at')
];
static const url = 'https://cakewallet.com/guide/';
static const url = 'https://guides.cakewallet.com';
List<SettingsListItem> items;
}