diff --git a/BeamWallet.xcodeproj/project.pbxproj b/BeamWallet.xcodeproj/project.pbxproj index 835f3aef..b4580b29 100644 --- a/BeamWallet.xcodeproj/project.pbxproj +++ b/BeamWallet.xcodeproj/project.pbxproj @@ -4844,7 +4844,7 @@ CODE_SIGN_ENTITLEMENTS = Resources/BeamWalletTestNet.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = KNU2R94BJK; ENABLE_BITCODE = NO; @@ -4922,7 +4922,7 @@ CODE_SIGN_ENTITLEMENTS = Resources/BeamWalletTestNet.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = KNU2R94BJK; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -5008,7 +5008,7 @@ CODE_SIGN_ENTITLEMENTS = Resources/BeamWalletMasterNet.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 43; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = KNU2R94BJK; ENABLE_BITCODE = NO; @@ -5088,7 +5088,7 @@ CODE_SIGN_ENTITLEMENTS = Resources/BeamWalletMasterNet.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 26; + CURRENT_PROJECT_VERSION = 43; DEVELOPMENT_TEAM = KNU2R94BJK; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/BeamWallet/BeamSDK/AppModel.h b/BeamWallet/BeamSDK/AppModel.h index e7b7daf5..d924ebe8 100644 --- a/BeamWallet/BeamSDK/AppModel.h +++ b/BeamWallet/BeamSDK/AppModel.h @@ -81,7 +81,7 @@ typedef int BMRestoreType; typedef void(^NewAddressGeneratedBlock)(BMAddress* _Nullable address, NSError* _Nullable error); typedef void(^ExportOwnerKey)(NSString * _Nonnull key); -typedef void(^FeecalculatedBlock)(uint64_t fee, double change, uint64_t shieldedInputsFee); +typedef void(^FeecalculatedBlock)(uint64_t fee, double change, uint64_t shieldedInputsFee, double max); typedef void(^PublicAddressBlock)(NSString * _Nonnull address); typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url); @@ -90,6 +90,7 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url); @property (nonatomic) NewAddressGeneratedBlock _Nullable generatedNewAddressBlock; @property (nonatomic) FeecalculatedBlock _Nullable feecalculatedBlock; @property (nonatomic) PublicAddressBlock _Nullable getPublicAddressBlock; + @property (nonatomic) ExportCSVBlock _Nullable getCSVBlock; @property (nonatomic,readwrite) NSPointerArray * _Nonnull delegates; @@ -173,7 +174,7 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url); -(BOOL)isToken:(NSString*_Nullable)address; -(void)generateWithdrawAddress:(NewAddressGeneratedBlock _Nonnull )block; --(NSString*_Nonnull)generateOfflineAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount; +-(void)generateOfflineAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block; -(NSString*_Nonnull)generateRegularAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount isPermanentAddress:(BOOL)isPermanentAddress; -(void)generateMaxPrivacyAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block; @@ -210,7 +211,7 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url); -(NSString*_Nonnull)getAddressTypeString:(BMAddressType)type; // send --(NSString*_Nullable)canSend:(double)amount assetId:(int)assetId fee:(double)fee to:(NSString*_Nullable)to; +-(NSString*_Nullable)canSend:(double)amount assetId:(int)assetId fee:(double)fee to:(NSString*_Nullable)to maxAmount:(double)maxAmount; -(NSString*_Nullable)feeError:(double)fee; -(NSString*_Nullable)canReceive:(double)amount fee:(double)fee; -(void)send:(double)amount fee:(double)fee assetId:(int)assetId to:(NSString*_Nonnull)to from:(NSString*_Nonnull)from comment:(NSString*_Nonnull)comment isOffline:(BOOL)isOffline; @@ -296,6 +297,6 @@ typedef void(^ExportCSVBlock)(NSString * _Nonnull data, NSURL * _Nonnull url); -(void)rescan; -(void)enableBodyRequests:(BOOL)value; - +-(double)grothToBeam:(uint64_t)groth; @end diff --git a/BeamWallet/BeamSDK/AppModel.mm b/BeamWallet/BeamSDK/AppModel.mm index 1031410c..c8198142 100644 --- a/BeamWallet/BeamSDK/AppModel.mm +++ b/BeamWallet/BeamSDK/AppModel.mm @@ -138,7 +138,7 @@ @implementation AppModel { ECC::NoLeak passwordHash; NSString *pathLog; - ByteBuffer lastVouchers; + ShieldedVoucherList lastVouchers; NSString *lastWalledId; std::string *lastWalledIdS; } @@ -1051,35 +1051,57 @@ -(void)generateWithdrawAddress:(NewAddressGeneratedBlock _Nonnull )block { } --(NSString*_Nonnull)generateOfflineAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount { +-(void)generateOfflineAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount result:(PublicAddressBlock _Nonnull)block { uint64_t bAmount = round(amount * Rules::Coin); - + WalletID m_walletID(Zero); m_walletID.FromHex(walleetId.string); auto address = walletDb->getAddress(m_walletID); - if(![lastWalledId isEqualToString:walleetId]) { - lastWalledId = walleetId; - lastVouchers = wallet->generateVouchers(address->m_OwnID, 1); - } - - TxParameters offlineParameters; - offlineParameters.SetParameter(TxParameterID::TransactionType, beam::wallet::TxType::PushTransaction); - offlineParameters.SetParameter(TxParameterID::ShieldedVoucherList, lastVouchers); - offlineParameters.SetParameter(TxParameterID::PeerID, address->m_walletID); - offlineParameters.SetParameter(TxParameterID::PeerWalletIdentity, address->m_Identity); - offlineParameters.SetParameter(TxParameterID::PeerOwnID, address->m_OwnID); - offlineParameters.SetParameter(TxParameterID::IsPermanentPeerID, true); - offlineParameters.SetParameter(TxParameterID::AssetID, uint32_t(assetId)); - - if (bAmount > 0) { - offlineParameters.SetParameter(TxParameterID::Amount, bAmount); - } + auto func = GenerateVaucherFunc(); + func.newGenerateVaucherBlock = ^(ShieldedVoucherList list) { + auto token = GenerateOfflineToken(*address, bAmount, uint32_t(assetId), list, std::string(BEAM_LIB_VERSION)); + block([NSString stringWithUTF8String:token.c_str()]); + }; - auto token = to_string(offlineParameters); - return [NSString stringWithUTF8String:token.c_str()]; + wallet->getAsync()->generateVouchers(address->m_OwnID, 1, func); + +// uint64_t bAmount = round(amount * Rules::Coin); +// +// WalletID m_walletID(Zero); +// m_walletID.FromHex(walleetId.string); +// +// auto address = walletDb->getAddress(m_walletID); +// +// if(![lastWalledId isEqualToString:walleetId]) { +// +// lastWalledId = walleetId; +// +// auto func = GenerateVaucherFunc(); +// func.newGenerateVaucherBlock = ^(ShieldedVoucherList list) { +// self->lastVouchers = list; +// }; +// +// wallet->getAsync()->generateVouchers(address->m_OwnID, 1, func); +// } +// +// TxParameters offlineParameters; +// offlineParameters.SetParameter(TxParameterID::TransactionType, beam::wallet::TxType::PushTransaction); +// offlineParameters.SetParameter(TxParameterID::ShieldedVoucherList, lastVouchers); +// offlineParameters.SetParameter(TxParameterID::PeerID, address->m_walletID); +// offlineParameters.SetParameter(TxParameterID::PeerWalletIdentity, address->m_Identity); +// offlineParameters.SetParameter(TxParameterID::PeerOwnID, address->m_OwnID); +// offlineParameters.SetParameter(TxParameterID::IsPermanentPeerID, true); +// offlineParameters.SetParameter(TxParameterID::AssetID, uint32_t(assetId)); +// +// if (bAmount > 0) { +// offlineParameters.SetParameter(TxParameterID::Amount, bAmount); +// } +// +// auto token = to_string(offlineParameters); +// return [NSString stringWithUTF8String:token.c_str()]; } -(NSString*_Nonnull)generateRegularAddress:(NSString*_Nonnull)walleetId assetId:(int)assetId amount:(double)amount isPermanentAddress:(BOOL)isPermanentAddress { @@ -1641,49 +1663,34 @@ -(void)editAddress:(BMAddress*_Nonnull)address { { [_presendedNotifications setValue:address.walletId forKey:address.walletId]; - std::vector addresses = wallet->ownAddresses; + auto status = beam::wallet::WalletAddress::ExpirationStatus::AsIs; - for (int i=0; igetAsync()->saveAddress(addresses[i]); - - break; } } + + wallet->getAsync()->updateAddress(walletID, address.label.string, status); } } } @@ -1878,8 +1885,16 @@ -(double)remainingBeam:(double)amount fee:(double)fee { return realAmount; } --(NSString*_Nullable)canSend:(double)amount assetId:(int)assetId fee:(double)fee to:(NSString*_Nullable)to { +-(NSString*_Nullable)canSend:(double)amount assetId:(int)assetId fee:(double)fee to:(NSString*_Nullable)to maxAmount:(double)maxAmount { NSString *errorString = [self sendError:amount assetId:assetId fee:fee to:to]; + if (errorString == nil) { + if (amount > maxAmount && maxAmount != 0) { + NSString *amountString = [[StringManager sharedManager] realAmountString:maxAmount]; + NSString *assetName = [[[AssetsManager sharedManager] getAsset:assetId] unitName]; + NSString *fullName = [NSString stringWithFormat:@"%@ %@", amountString, assetName]; + return [NSString stringWithFormat:[@"max_funds_error" localized], fullName]; + } + } return errorString; } @@ -1917,11 +1932,15 @@ -(void)calculateFee:(double)amount assetId:(int)assetId fee:(double)fee isShield Amount bAmount = round(amount * Rules::Coin); Amount bFee = fee; - + wallet->getAsync()->selectCoins(bAmount, bFee, beam::Asset::ID(assetId), isShielded); // wallet->getAsync()->calcShieldedCoinSelectionInfo(bAmount, 0, assetId, isShielded); } +-(double)grothToBeam:(uint64_t)groth { + double real = double(groth / Rules::Coin); + return real; +} -(NSString*)sendError:(double)amount assetId:(int)assetId fee:(double)fee checkMinAmount:(BOOL)check { diff --git a/BeamWallet/BeamSDK/AssetsManager.h b/BeamWallet/BeamSDK/AssetsManager.h index 1c157ec8..3fcf25e5 100644 --- a/BeamWallet/BeamSDK/AssetsManager.h +++ b/BeamWallet/BeamSDK/AssetsManager.h @@ -37,5 +37,8 @@ -(void)changeAssets; -(double)getRealAvailableAmount:(int)assetId; +-(NSMutableArray*_Nonnull)getAssetsWithBalance; +-(NSMutableArray*_Nonnull)getAssetsWithBalanceWithBeam; + @end diff --git a/BeamWallet/BeamSDK/AssetsManager.mm b/BeamWallet/BeamSDK/AssetsManager.mm index 58325e11..d225769b 100644 --- a/BeamWallet/BeamSDK/AssetsManager.mm +++ b/BeamWallet/BeamSDK/AssetsManager.mm @@ -24,7 +24,7 @@ static NSString *assetsKey = @"assetsKeyNew"; -NSArray *colors = @[@"#72fdff",@"#2acf1d",@"#ffbb54",@"#d885ff",@"#008eff",@"#ff746b",@"#91e300",@"#ffe75a",@"#9643ff",@"#395bff",@"#ff3b3b",@"#73ff7c",@"#ffa86c",@"#ff3abe",@"#00aee1",@"#ff5200",@"#6464ff",@"#ff7a21",@"#63afff",@"#c81f68"]; +NSArray *colors = @[@"#72fdff",@"#2acf1d",@"#ffbb54",@"#d885ff",@"#008eff",@"#d885ff",@"#91e300",@"#ffe75a",@"#9643ff",@"#395bff",@"#ff3b3b",@"#73ff7c",@"#ffa86c",@"#ff3abe",@"#00aee1",@"#ff5200",@"#6464ff",@"#ff7a21",@"#63afff",@"#c81f68"]; @implementation AssetsManager @@ -120,5 +120,30 @@ -(double)getRealAvailableAmount:(int)assetId { return asset.realAmount; } +-(NSMutableArray*_Nonnull)getAssetsWithBalance { + NSMutableArray *results = [NSMutableArray new]; + for (BMAsset *asset in self.assets.reverseObjectEnumerator) { + if (asset.realAmount > 0 && ![asset isBeam]) { + [results addObject:asset]; + } + } + return results; +} + +-(NSMutableArray*_Nonnull)getAssetsWithBalanceWithBeam { + NSMutableArray *results = [NSMutableArray new]; + BMAsset *beamAsset = nil; + for (BMAsset *asset in self.assets.reverseObjectEnumerator) { + if (asset.realAmount > 0 && ![asset isBeam]) { + [results addObject:asset]; + } + else if ([asset isBeam]) { + beamAsset = asset; + } + } + [results insertObject:beamAsset atIndex:0]; + return results; +} + @end diff --git a/BeamWallet/BeamSDK/Objects/BMAsset.h b/BeamWallet/BeamSDK/Objects/BMAsset.h index 4081ae52..7eb570e1 100644 --- a/BeamWallet/BeamSDK/Objects/BMAsset.h +++ b/BeamWallet/BeamSDK/Objects/BMAsset.h @@ -53,10 +53,14 @@ -(UInt64)locked; -(double)realLocked; +-(UInt64)change; +-(double)realChange; + -(BOOL)isBeam; -(BOOL)isDemoX; -(double)USD; -(UInt64)dateUsed; +-(BOOL)isIncomming; @end diff --git a/BeamWallet/BeamSDK/Objects/BMAsset.m b/BeamWallet/BeamSDK/Objects/BMAsset.m index 4a041abd..187c8373 100644 --- a/BeamWallet/BeamSDK/Objects/BMAsset.m +++ b/BeamWallet/BeamSDK/Objects/BMAsset.m @@ -99,12 +99,26 @@ -(BOOL)hasInProgressBalance { return (_realSending >0 || _realReceiving > 0); } +-(UInt64)change { + if (_realSending > 0 && _realReceiving > 0) { + return _receiving; + } + return 0; +} + +-(double)realChange { + if (_realSending > 0 && _realReceiving > 0) { + return _realReceiving; + } + return 0; +} + -(UInt64)locked { - return _receiving + _maturing + _maxPrivacy; + return _maturing + _maxPrivacy + [self change]; } -(double)realLocked { - return _realReceiving + _realMaturing + _realMaxPrivacy; + return _realMaturing + _realMaxPrivacy + [self realChange]; } -(BOOL)isBeam { @@ -112,7 +126,7 @@ -(BOOL)isBeam { } -(BOOL)isDemoX { - return [_unitName isEqualToString:@"DEMOX"]; + return [[_unitName uppercaseString] isEqualToString:@"DEMOX"] || [[_unitName uppercaseString] isEqualToString:@"BEAMX"]; } -(double)USD { @@ -127,4 +141,12 @@ -(UInt64)dateUsed { return 0; } +-(BOOL)isIncomming { + BMTransaction *transaction = [[AssetsManager sharedManager] getLastTransaction:(int)self.assetId]; + if (transaction != nil && transaction.isIncome) { + return YES; + } + return NO; +} + @end diff --git a/BeamWallet/BeamSDK/Objects/BMTransaction.h b/BeamWallet/BeamSDK/Objects/BMTransaction.h index bb5399f1..4646ffae 100644 --- a/BeamWallet/BeamSDK/Objects/BMTransaction.h +++ b/BeamWallet/BeamSDK/Objects/BMTransaction.h @@ -52,14 +52,14 @@ typedef UInt64 BMTransactionType; @interface BMTransaction : NSObject { } -@property (nonatomic,strong) NSString *ID; +@property (nonatomic,strong) NSString *_Nonnull ID; @property (nonatomic,assign) UInt64 createdTime; -@property (nonatomic,strong) NSString *status; -@property (nonatomic,strong) NSString *failureReason; -@property (nonatomic,strong) NSString *kernelId; -@property (nonatomic,strong) NSString *senderAddress; -@property (nonatomic,strong) NSString *receiverAddress; -@property (nonatomic,strong) NSString *comment; +@property (nonatomic,strong) NSString * _Nonnull status; +@property (nonatomic,strong) NSString * _Nonnull failureReason; +@property (nonatomic,strong) NSString * _Nonnull kernelId; +@property (nonatomic,strong) NSString * _Nonnull senderAddress; +@property (nonatomic,strong) NSString * _Nonnull receiverAddress; +@property (nonatomic,strong) NSString * _Nonnull comment; @property (nonatomic,assign) BOOL isIncome; @property (nonatomic,assign) BOOL isSelf; @property (nonatomic,assign) BOOL canCancel; @@ -74,25 +74,25 @@ typedef UInt64 BMTransactionType; @property (nonatomic,assign) BMTransactionStatus enumStatus; @property (nonatomic,assign) BMTransactionType enumType; @property (nonatomic,assign) int assetId; -@property (nonatomic,strong) BMAsset *asset; +@property (nonatomic,strong) BMAsset * _Nullable asset; -@property (nonatomic,strong) NSString *senderContactName; -@property (nonatomic,strong) NSString *receiverContactName; +@property (nonatomic,strong) NSString * _Nonnull senderContactName; +@property (nonatomic,strong) NSString * _Nonnull receiverContactName; -@property (nonatomic,strong) NSString *identity; -@property (nonatomic,strong) NSString *token; +@property (nonatomic,strong) NSString * _Nonnull identity; +@property (nonatomic,strong) NSString * _Nonnull token; -@property (nonatomic,strong) NSString *senderIdentity; -@property (nonatomic,strong) NSString *receiverIdentity; +@property (nonatomic,strong) NSString * _Nonnull senderIdentity; +@property (nonatomic,strong) NSString * _Nonnull receiverIdentity; --(NSString*)amountString; --(UIImage*)statusIcon; --(NSString*)statusName; --(NSString*)statusType; --(NSString*)getAddressType; +-(NSString*_Nonnull)amountString; +-(UIImage*_Nonnull)statusIcon; +-(NSString*_Nonnull)statusName; +-(NSString*_Nonnull)statusType; +-(NSString*_Nonnull)getAddressType; --(NSString*)formattedDate; --(NSString*)shortDate; +-(NSString*_Nonnull)formattedDate; +-(NSString*_Nonnull)shortDate; -(BOOL)isFailed; -(BOOL)hasPaymentProof; -(BOOL)isCancelled; @@ -100,11 +100,11 @@ typedef UInt64 BMTransactionType; -(BOOL)isExpired; -(BOOL)canSaveContact; --(NSString*)details; --(NSString*)csvLine; --(NSString*)textDetails; +-(NSString*_Nonnull)details; +-(NSString*_Nonnull)csvLine; +-(NSString*_Nonnull)textDetails; --(NSMutableAttributedString*)searchString:(NSString*)searchText; +-(NSMutableAttributedString*_Nonnull)searchString:(NSString*_Nonnull)searchText; @end diff --git a/BeamWallet/BeamSDK/WalletModel.mm b/BeamWallet/BeamSDK/WalletModel.mm index 8f2f9d79..aa5185c4 100644 --- a/BeamWallet/BeamSDK/WalletModel.mm +++ b/BeamWallet/BeamSDK/WalletModel.mm @@ -106,6 +106,7 @@ asset.maturing = AmountBig::get_Lo(value.maturing); asset.realAmount = (double(int64_t(AmountBig::get_Lo(value.available))) / Rules::Coin) + (double(int64_t(AmountBig::get_Lo(value.shielded))) / Rules::Coin); + asset.realMaturing = double(int64_t(AmountBig::get_Lo(value.maturing))) / Rules::Coin; asset.realReceiving = (double(int64_t(AmountBig::get_Lo(value.receiving))) / Rules::Coin); asset.realSending = (double(int64_t(AmountBig::get_Lo(value.sending))) / Rules::Coin); asset.realShielded = double(int64_t(AmountBig::get_Lo(value.shielded))) / Rules::Coin; @@ -128,6 +129,7 @@ asset.maturing = AmountBig::get_Lo(value.maturing); asset.realAmount = (double(int64_t(AmountBig::get_Lo(value.available))) / Rules::Coin) + (double(int64_t(AmountBig::get_Lo(value.shielded))) / Rules::Coin); + asset.realMaturing = double(int64_t(AmountBig::get_Lo(value.maturing))) / Rules::Coin; asset.realReceiving = (double(int64_t(AmountBig::get_Lo(value.receiving))) / Rules::Coin); asset.realSending = (double(int64_t(AmountBig::get_Lo(value.sending))) / Rules::Coin); asset.realShielded = double(int64_t(AmountBig::get_Lo(value.shielded))) / Rules::Coin; @@ -1467,8 +1469,9 @@ NSLog(@"onCoinsSelectionCalculated"); auto change = double(int64_t(selectionRes.m_changeAsset)) / Rules::Coin; - - [AppModel sharedManager].feecalculatedBlock(selectionRes.m_minimalExplicitFee, change, 0); + double real = double(int64_t(AmountBig::get_Lo(selectionRes.m_selectedSumAsset))) / Rules::Coin; + + [AppModel sharedManager].feecalculatedBlock(selectionRes.m_minimalExplicitFee, change, 0, real); } void WalletModel::onPublicAddress(const std::string& publicAddr) diff --git a/BeamWallet/Controls/BMNotificationView.swift b/BeamWallet/Controls/BMNotificationView.swift index 9e05871e..9bf11aaf 100644 --- a/BeamWallet/Controls/BMNotificationView.swift +++ b/BeamWallet/Controls/BMNotificationView.swift @@ -66,7 +66,12 @@ class BMNotificationView: UIView { var icon: UIImage? var title: String? var detail: NSMutableAttributedString? - let assetValue = String.currency(value: transaction.realAmount, name: transaction.asset.unitName) + + guard let asset = transaction.asset else { + return + } + + let assetValue = String.currencyShort(value: transaction.realAmount, name: asset.unitName) if transaction.isIncome { if (transaction.enumStatus == BMTransactionStatusRegistering || transaction.enumStatus == BMTransactionStatusPending) && !transaction.isSelf { @@ -147,10 +152,14 @@ class BMNotificationView: UIView { static func showTransaction (notification: BMNotification, delegate:BMNotificationViewDelegate) { DispatchQueue.main.async { if let transaction = AppModel.sharedManager().transaction(byId: notification.pId) { + guard let asset = transaction.asset else { + return + } + var icon: UIImage? var title: String? var detail: NSMutableAttributedString? - let assetValue = String.currency(value: transaction.realAmount, name: transaction.asset.unitName) + let assetValue = String.currencyShort(value: transaction.realAmount, name: asset.unitName) if transaction.isIncome { if transaction.isFailed() || transaction.isExpired() || transaction.isCancelled() { diff --git a/BeamWallet/Controls/Cell/BMAmountCell.swift b/BeamWallet/Controls/Cell/BMAmountCell.swift index 85bfecc7..83d6f64c 100644 --- a/BeamWallet/Controls/Cell/BMAmountCell.swift +++ b/BeamWallet/Controls/Cell/BMAmountCell.swift @@ -29,6 +29,7 @@ class BMAmountCell: BaseCell { @IBOutlet weak private var currencyArrow: UIImageView! @IBOutlet weak private var erorLabel: UILabel! @IBOutlet weak private var secondCurrencyLabel: UILabel! + @IBOutlet weak private var maxAmountErrorLabel: UILabel! @IBOutlet var topNameOffset: NSLayoutConstraint! @IBOutlet var topStackOffset: NSLayoutConstraint! @@ -86,7 +87,7 @@ class BMAmountCell: BaseCell { public var currency:String? { didSet{ - if currency != nil && AssetsManager.shared().assets.count > 0 { + if currency != nil && AssetsManager.shared().getAssetsWithBalance().count >= 1 { currencyLabel.isUserInteractionEnabled = true currencyArrow.isHidden = false @@ -95,6 +96,19 @@ class BMAmountCell: BaseCell { } } } + public var maxAmountError:String? + { + didSet{ + self.maxAmountErrorLabel.text = maxAmountError + if maxAmountError != nil { + self.maxAmountErrorLabel.isHidden = false + } + else { + self.maxAmountErrorLabel.isHidden = true + } + } + } + override func awakeFromNib() { super.awakeFromNib() @@ -105,6 +119,9 @@ class BMAmountCell: BaseCell { secondCurrencyLabel.textColor = UIColor.main.blueyGrey secondCurrencyLabel.font = RegularFont(size: 14) + maxAmountErrorLabel.textColor = UIColor.main.blueyGrey + maxAmountErrorLabel.font = RegularFont(size: 14) + erorLabel.textColor = UIColor.main.red erorLabel.isHidden = true erorLabel.text = nil @@ -124,6 +141,7 @@ class BMAmountCell: BaseCell { if Settings.sharedManager().isDarkMode { secondCurrencyLabel.textColor = UIColor.main.steel nameLabel.textColor = UIColor.main.steel; + maxAmountErrorLabel.textColor = UIColor.main.steel; } } diff --git a/BeamWallet/Controls/Cell/BMAmountCell.xib b/BeamWallet/Controls/Cell/BMAmountCell.xib index 1a35ebb5..391c2c2d 100644 --- a/BeamWallet/Controls/Cell/BMAmountCell.xib +++ b/BeamWallet/Controls/Cell/BMAmountCell.xib @@ -116,6 +116,15 @@ + @@ -147,6 +156,7 @@ + diff --git a/BeamWallet/Manager/NotificationManager.swift b/BeamWallet/Manager/NotificationManager.swift index 8cae3f87..59aed386 100644 --- a/BeamWallet/Manager/NotificationManager.swift +++ b/BeamWallet/Manager/NotificationManager.swift @@ -87,7 +87,10 @@ class NotificationManager : NSObject { } else if (notification.type == TRANSACTION) { if let transaction = AppModel.sharedManager().transaction(byId: notification.pId) { - let assetValue = String.currencyShort(value: transaction.realAmount, name: transaction.asset.unitName) + guard let asset = transaction.asset else { + return + } + let assetValue = String.currencyShort(value: transaction.realAmount, name: asset.unitName) var title = "" var detail = "" diff --git a/BeamWallet/Model/Address/ReceiveAddressViewModel.swift b/BeamWallet/Model/Address/ReceiveAddressViewModel.swift index 8bbbff39..8d86ef85 100644 --- a/BeamWallet/Model/Address/ReceiveAddressViewModel.swift +++ b/BeamWallet/Model/Address/ReceiveAddressViewModel.swift @@ -90,8 +90,15 @@ class ReceiveAddressViewModel: NSObject { self.address.maxPrivacyToken = token; } } - - address.offlineToken = AppModel.sharedManager().generateOfflineAddress(address.walletId, assetId: Int32(selectedAssetId), amount: bamount) + + if isOwn { + AppModel.sharedManager().generateOfflineAddress(address.walletId, assetId: Int32(selectedAssetId), amount: bamount) { (token) in + DispatchQueue.main.async { + self.address.offlineToken = token; + self.onAddressCreated?(nil) + } + } + } } public func createAddress() { diff --git a/BeamWallet/Model/Transaction/SendTransactionViewModel.swift b/BeamWallet/Model/Transaction/SendTransactionViewModel.swift index e82c0e6f..91ac1b19 100644 --- a/BeamWallet/Model/Transaction/SendTransactionViewModel.swift +++ b/BeamWallet/Model/Transaction/SendTransactionViewModel.swift @@ -27,6 +27,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { private var isFocused = false public var copyAddress:String? + public var maxAmountError:String? public var amountError:String? public var toAddressError:String? public var newVersionError:String? @@ -63,7 +64,8 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { } public var maxPrivacy:Bool = false - + public var maxSendAmount:Double = 0 + public var selectedAssetId = 0 public var selectedCurrencyString: String { get { @@ -78,10 +80,17 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { } public func calculateFee() { - let isShielded = (addressType == BMAddressTypeShielded || addressType == BMAddressTypeOfflinePublic || + let isShielded = (addressType == BMAddressTypeOfflinePublic || addressType == BMAddressTypeMaxPrivacy || isSendOffline) - AppModel.sharedManager().calculateFee(Double(amount) ?? 0, assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), isShielded: isShielded) { (result, changed, shieldedInputsFee) in + //addressType == BMAddressTypeShielded || + + var assetName = (AssetsManager.shared().getAsset(Int32(self.selectedAssetId))?.unitName ?? "") + " " + if assetName == "assets" { + assetName = "BEAM" + } + + AppModel.sharedManager().calculateFee(Double(amount) ?? 0, assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), isShielded: isShielded) { (result, changed, shieldedInputsFee, max) in DispatchQueue.main.async { self.shieldedInputsFee = shieldedInputsFee let current = UInt64(self.fee) ?? 0 @@ -95,6 +104,28 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { self.minFee = self.fee self.onFeeChanged?() } + + let selectedAmount = Double(self.amount) ?? 0 + let realFee = AppModel.sharedManager().realTotal(0, fee: Double(result), assetId: 0) + self.maxSendAmount = max - realFee + if selectedAmount > self.maxSendAmount { + if self.sendAll { + self.amount = String(self.maxSendAmount) + var assetName = (AssetsManager.shared().getAsset(Int32(self.selectedAssetId))?.unitName ?? "") + " " + if assetName == "assets" { + assetName = "BEAM" + } + if assetName.count > 10 { + assetName = assetName.prefix(10) + "..." + } + let amountString = String.currencyShort(value: self.maxSendAmount, name: assetName) + self.maxAmountError = String(format: Localizable.shared.strings.max_funds_hint, amountString) + self.onAmountMaxError?() + } + else { + self.maxAmountError = nil + } + } } } } @@ -111,6 +142,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { public var onContactChanged : ((Bool) -> Void)? public var onAddressTypeChanged : ((Bool) -> Void)? public var onTokensCountChanged : ((Int) -> Void)? + public var onAmountMaxError : (() -> Void)? public var saveContactName = String.empty() public var sbbsAddress = String.empty() @@ -169,6 +201,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { public var inputAmount = String.empty() { didSet { amountError = nil + maxAmountError = nil if !sendAll { calculateFee() } @@ -216,7 +249,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { } if repeatTransaction.realAmount > 0 { amount = String.currency(value: repeatTransaction.realAmount).replacingOccurrences(of: " BEAM", with: "") - comment = repeatTransaction.comment ?? "" + comment = repeatTransaction.comment } } } @@ -286,7 +319,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { } public func checkAmountError() { - let canSend = AppModel.sharedManager().canSend((Double(amount) ?? 0), assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), to: toAddress) + let canSend = AppModel.sharedManager().canSend((Double(amount) ?? 0), assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), to: toAddress, maxAmount: self.maxSendAmount) if canSend != Localizable.shared.strings.incorrect_address && ((Double(amount) ?? 0)) > 0 { amountError = canSend @@ -313,7 +346,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { public func canSend() -> Bool { let valid = AppModel.sharedManager().isValidAddress(toAddress) let expired = AppModel.sharedManager().isExpiredAddress(toAddress) - let canSend = AppModel.sharedManager().canSend((Double(amount) ?? 0), assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), to: toAddress) + let canSend = AppModel.sharedManager().canSend((Double(amount) ?? 0), assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), to: toAddress, maxAmount: self.maxSendAmount) let isError = (!valid || expired || canSend != nil) @@ -486,7 +519,7 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { let isShielded = (addressType == BMAddressTypeShielded || addressType == BMAddressTypeOfflinePublic || addressType == BMAddressTypeMaxPrivacy || isSendOffline) - AppModel.sharedManager().calculateFee((Double(amount) ?? 0), assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), isShielded: isShielded) { (fee, change, shieldedInputsFee) in + AppModel.sharedManager().calculateFee((Double(amount) ?? 0), assetId: Int32(selectedAssetId), fee: (Double(fee) ?? 0), isShielded: isShielded) { (fee, change, shieldedInputsFee, max) in self.onCalculateChanged?(change) } } @@ -499,8 +532,14 @@ class SendTransactionViewModel: NSObject, WalletModelDelegate { } public func amountString(amount: String, isFee:Bool, assetId:Int, color: UIColor? = nil, doubleAmount:Double = 0.0) -> NSMutableAttributedString { - let assetName = (AssetsManager.shared().getAsset(Int32(assetId))?.unitName ?? "") + " " - + var assetName = (AssetsManager.shared().getAsset(Int32(assetId))?.unitName ?? "") + " " + if assetName == "assets" { + assetName = "BEAM" + } + if assetName.count > 10 { + assetName = assetName.prefix(10) + "..." + } + let amountString = isFee ? ((amount + Localizable.shared.strings.beam + "\n")) : ((amount + " " + assetName + "\n")) var secondString = isFee ? ExchangeManager.shared().exchangeValueAsset(Double(amount) ?? 0, assetID: UInt64(0)) : ExchangeManager.shared().exchangeValueAsset(Double(amount) ?? 0, assetID: UInt64(self.selectedAssetId)) diff --git a/BeamWallet/Model/Transaction/TransactionViewModel.swift b/BeamWallet/Model/Transaction/TransactionViewModel.swift index 8806c8b0..74eac252 100644 --- a/BeamWallet/Model/Transaction/TransactionViewModel.swift +++ b/BeamWallet/Model/Transaction/TransactionViewModel.swift @@ -94,7 +94,7 @@ class TransactionViewModel: NSObject { if let transactions = AppModel.sharedManager().transactions { self.transactions = transactions as! [BMTransaction] self.transactions = self.transactions.filter({ t in - t.asset.shortName != nil + t.asset != nil && t.asset?.shortName != nil }) } @@ -217,7 +217,7 @@ extension TransactionViewModel : WalletModelDelegate { else{ self.transactions = transactions self.transactions = self.transactions.filter({ t in - t.asset.shortName != nil + t.asset != nil && t.asset?.shortName != nil }) } self.onDataChanged?() diff --git a/BeamWallet/Model/Wallet/AssetViewModel.swift b/BeamWallet/Model/Wallet/AssetViewModel.swift index d962b2ac..c656b8b0 100644 --- a/BeamWallet/Model/Wallet/AssetViewModel.swift +++ b/BeamWallet/Model/Wallet/AssetViewModel.swift @@ -32,6 +32,9 @@ class AssetViewModel: NSObject { public var onDataChanged : (() -> Void)? public var assets = [BMAsset]() + private var isBeamRemoved = false + + public var filteredAssets = [BMAsset]() public var filtertype = AssetFilterType.recent_old { didSet { @@ -52,6 +55,16 @@ class AssetViewModel: NSObject { AppModel.sharedManager().removeDelegate(self) } + func removeBeam() { + isBeamRemoved = true + self.filteredAssets.removeAll { asset in + return asset.isBeam() + } + self.assets.removeAll { asset in + return asset.isBeam() + } + } + public func sort() { self.assets = AssetsManager.shared().assets as! [BMAsset] self.assets = self.assets.filter({ a in @@ -86,6 +99,37 @@ class AssetViewModel: NSObject { return a1.usd() < a2.usd() } } + + self.assets = self.assets.filter({ asset in + return asset.realLocked() > 0 || asset.realAmount > 0 + || asset.isBeam() || asset.sending > 0 || (asset.receiving > 0 && asset.isIncomming()) + }) + + self.filteredAssets = self.assets.filter({ asset in + return asset.realLocked() > 0 || asset.realAmount > 0 + || asset.isBeam() || asset.sending > 0 || (asset.receiving > 0 && asset.isIncomming()) + }) + + if !isBeamRemoved { + if let index = self.filteredAssets.firstIndex(where: { asset in + asset.isBeam() + }) { + if index != 0 { + let beam = self.filteredAssets[index] + self.filteredAssets.remove(at: index) + self.filteredAssets.insert(beam, at: 0) + } + } + } + else { + self.filteredAssets.removeAll { asset in + return asset.isBeam() + } + self.assets.removeAll { asset in + return asset.isBeam() + } + } + } public func getAssetInfo(asset:BMAsset) -> [BMThreeLineItem] { @@ -127,8 +171,8 @@ class AssetViewModel: NSObject { section_1.append(shielded) - let lockedBalance = asset.realMaxPrivacy + asset.realMaturing + asset.realSending + asset.realReceiving - let changeBalance = asset.realSending + asset.realReceiving + let lockedBalance = asset.realLocked() + let changeBalance = asset.realChange() let locked = BMThreeLineItem(title: Localizable.shared.strings.locked.uppercased(), detail: asset.isBeam() ? String.currency(value: lockedBalance) : String.currency(value: lockedBalance, name: asset.unitName), subDetail: ExchangeManager.shared().exchangeValueAsset(lockedBalance, assetID: asset.assetId), titleColor: UIColor.white, detailColor: UIColor.white, subDetailColor: UIColor.white.withAlphaComponent(0.7), titleFont: BoldFont(size: 14), detailFont: RegularFont(size: 14), subDetailFont: RegularFont(size: 14), hasArrow: true) diff --git a/BeamWallet/Protocols/LocalizableStrings.swift b/BeamWallet/Protocols/LocalizableStrings.swift index 2adb7af6..e4968bcf 100644 --- a/BeamWallet/Protocols/LocalizableStrings.swift +++ b/BeamWallet/Protocols/LocalizableStrings.swift @@ -527,8 +527,8 @@ class LocalizableStrings : NSObject { var online_new_status = "online_new_status".localized var receive_description_2 = "receive_description_2".localized var only_online_support = "only_online_support".localized - - + var max_funds_error = "max_funds_error".localized + var max_funds_hint = "max_funds_hint".localized public func new_version_available_title(version: String) -> String { return "new_version_available_title".localized.replacingOccurrences(of: "(version)", with: version) diff --git a/BeamWallet/ViewControllers/Main/Assets/AssetsViewController.swift b/BeamWallet/ViewControllers/Main/Assets/AssetsViewController.swift index 3c886db3..60765d6d 100644 --- a/BeamWallet/ViewControllers/Main/Assets/AssetsViewController.swift +++ b/BeamWallet/ViewControllers/Main/Assets/AssetsViewController.swift @@ -32,6 +32,8 @@ class AssetsViewController: BaseTableViewController { title = Localizable.shared.strings.assets + assetViewModel.removeBeam() + tableView.delegate = self tableView.dataSource = self tableView.register([WalletStatusCell.self, AssetAvailableCell.self]) diff --git a/BeamWallet/ViewControllers/Main/Notifications/NotificationItem.swift b/BeamWallet/ViewControllers/Main/Notifications/NotificationItem.swift index c5708637..740cd5a3 100644 --- a/BeamWallet/ViewControllers/Main/Notifications/NotificationItem.swift +++ b/BeamWallet/ViewControllers/Main/Notifications/NotificationItem.swift @@ -44,7 +44,10 @@ class NotificationItem { } else if(notification.type == TRANSACTION) { if let transaction = AppModel.sharedManager().transaction(byId: notification.pId) { - let assetValue = String.currencyShort(value: transaction.realAmount, name: transaction.asset.unitName) + guard let asset = transaction.asset else { + return + } + let assetValue = String.currencyShort(value: transaction.realAmount, name: asset.unitName) if transaction.isIncome { if transaction.isFailed() || transaction.isExpired() || transaction.isCancelled() { diff --git a/BeamWallet/ViewControllers/Main/UTXO/Cells/UTXOTransactionCell.swift b/BeamWallet/ViewControllers/Main/UTXO/Cells/UTXOTransactionCell.swift index 09b860d1..79b6171a 100644 --- a/BeamWallet/ViewControllers/Main/UTXO/Cells/UTXOTransactionCell.swift +++ b/BeamWallet/ViewControllers/Main/UTXO/Cells/UTXOTransactionCell.swift @@ -40,11 +40,11 @@ extension UTXOTransactionCell: Configurable { if !options.transaction.isIncome { arrowImage.image = IconSent() - typeLabel.text = Localizable.shared.strings.send + " \(options.transaction.asset.unitName ?? "")" + typeLabel.text = Localizable.shared.strings.send + " \(options.transaction.asset?.unitName ?? "")" } else{ arrowImage.image = IconReceived() - typeLabel.text = Localizable.shared.strings.receive + " \(options.transaction.asset.unitName ?? "")" + typeLabel.text = Localizable.shared.strings.receive + " \(options.transaction.asset?.unitName ?? "")" } if options.transaction.comment.isEmpty { diff --git a/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionCell.swift b/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionCell.swift index 9abccd16..debfab71 100755 --- a/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionCell.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionCell.swift @@ -43,11 +43,15 @@ class WalletTransactionCell: RippleCell { extension WalletTransactionCell: Configurable { func configure(with options: (row: Int, transaction:BMTransaction, additionalInfo:Bool)) { + guard let asset = options.transaction.asset else { + return + } + secondAvailableLabel.text = ExchangeManager.shared().exchangeValueAsset(options.transaction.realAmount, assetID: UInt64(options.transaction.assetId)) mainView.backgroundColor = (options.row % 2 == 0) ? UIColor.main.cellBackgroundColor : UIColor.main.marine - assetIcon.setAsset(options.transaction.asset) + assetIcon.setAsset(asset) statusIcon.image = options.transaction.statusIcon() diff --git a/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionSearchCell.swift b/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionSearchCell.swift index d900d45e..f994598e 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionSearchCell.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Cells/WalletTransactionSearchCell.swift @@ -52,11 +52,15 @@ class WalletTransactionSearchCell: RippleCell { extension WalletTransactionSearchCell: Configurable { func configure(with options: (row: Int, transaction:BMTransaction, additionalInfo:Bool)) { + guard let asset = options.transaction.asset else { + return + } + secondAvailableLabel.text = ExchangeManager.shared().exchangeValueAsset(options.transaction.realAmount, assetID: UInt64(options.transaction.assetId)) mainView.backgroundColor = (options.row % 2 == 0) ? UIColor.main.cellBackgroundColor : UIColor.main.marine - assetIcon.setAsset(options.transaction.asset) + assetIcon.setAsset(asset) statusIcon.image = options.transaction.statusIcon() diff --git a/BeamWallet/ViewControllers/Main/Wallet/Receive/ReceiveViewController.swift b/BeamWallet/ViewControllers/Main/Wallet/Receive/ReceiveViewController.swift index 84efa089..8c28243d 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Receive/ReceiveViewController.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Receive/ReceiveViewController.swift @@ -433,7 +433,7 @@ extension ReceiveViewController : BMCellProtocol { if tableView.indexPath(for: sender) != nil, let cell = sender as? BMAmountCell { var menu = [BMPopoverMenu.BMPopoverMenuItem]() - for asset in AssetsManager.shared().assets as! [BMAsset] { + for asset in AssetsManager.shared().getAssetsWithBalanceWithBeam() as! [BMAsset] { menu.append(BMPopoverMenu.BMPopoverMenuItem(name: asset.unitName, icon: nil, action: .asset, selected: self.viewModel.selectedAssetId == Int(asset.assetId))) } diff --git a/BeamWallet/ViewControllers/Main/Wallet/Send/SendConfirmViewController.swift b/BeamWallet/ViewControllers/Main/Wallet/Send/SendConfirmViewController.swift index ec0ec506..284fbc06 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Send/SendConfirmViewController.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Send/SendConfirmViewController.swift @@ -203,7 +203,7 @@ extension SendConfirmViewController: WalletModelDelegate { let asset = (AssetsManager.shared().getAsset(Int32(self.viewModel.selectedAssetId))) let assetName = (asset?.unitName ?? "") - let totalString = (self.viewModel.selectedAssetId == 0 ? String.currency(value: (amount), name: assetName) : String.currency(value: (0.0), name: assetName)).replacingOccurrences(of: assetName, with: "").replacingOccurrences(of: " ", with: "") + let totalString = (self.viewModel.selectedAssetId == 0 ? String.currencyShort(value: (amount), name: assetName) : String.currencyShort(value: (0.0), name: assetName)).replacingOccurrences(of: assetName, with: "") let totalDetail = self.viewModel.amountString(amount: totalString, isFee: false, assetId: viewModel.selectedAssetId, color: UIColor.white, doubleAmount: amount) @@ -213,7 +213,7 @@ extension SendConfirmViewController: WalletModelDelegate { let total = AppModel.sharedManager().realTotal(Double(self.viewModel.amount) ?? 0, fee: Double(self.viewModel.fee) ?? 0, assetId: Int32(self.viewModel.selectedAssetId)) let left = (asset?.realAmount ?? 0.0) - total - let leftString = String.currency(value: left, name: assetName).replacingOccurrences(of: assetName, with: "") + let leftString = String.currencyShort(value: left, name: assetName).replacingOccurrences(of: assetName, with: "") let leftDetail = self.viewModel.amountString(amount: leftString, isFee: false, assetId: viewModel.selectedAssetId, color: UIColor.white, doubleAmount: left) @@ -225,7 +225,7 @@ extension SendConfirmViewController: WalletModelDelegate { let beamAsset = (AssetsManager.shared().getAsset(Int32(0))) let total = AppModel.sharedManager().remainingBeam(Double(beamAsset?.realAmount ?? 0), fee: Double(self.viewModel.fee) ?? 0) - let leftString = String.currency(value: total, name: beamAsset?.unitName ?? "").replacingOccurrences(of: "BEAM", with: "") + let leftString = String.currencyShort(value: total, name: beamAsset?.unitName ?? "").replacingOccurrences(of: "BEAM", with: "") let leftDetail = self.viewModel.amountString(amount: leftString, isFee: false, assetId: 0, color: UIColor.white, doubleAmount: left) let leftItem = BMMultiLineItem(title: Localizable.shared.strings.remaining_beam.uppercased(), detail: leftDetail.string, detailFont: SemiboldFont(size: 16), detailColor: UIColor.white) diff --git a/BeamWallet/ViewControllers/Main/Wallet/Send/SendViewController.swift b/BeamWallet/ViewControllers/Main/Wallet/Send/SendViewController.swift index e352694f..49ed2c7d 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Send/SendViewController.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Send/SendViewController.swift @@ -148,6 +148,13 @@ class SendViewController: BaseTableViewController { } } + viewModel.onAmountMaxError = { [weak self] in + guard let strongSelf = self else { return } + if strongSelf.isAppear { + strongSelf.tableView.reloadData() + } + } + viewModel.onAddressTypeChanged = { [weak self] _ in guard let strongSelf = self else { return } UIView.performWithoutAnimation { @@ -376,6 +383,7 @@ extension SendViewController: UITableViewDataSource { cell.setSecondAmount(amount: viewModel.secondAmount) cell.topNameOffset.constant = 20 cell.titleColor = UIColor.white + cell.maxAmountError = viewModel.maxAmountError return cell } else { @@ -588,7 +596,7 @@ extension SendViewController: BMCellProtocol { if tableView.indexPath(for: sender) != nil, let cell = sender as? BMAmountCell { var menu = [BMPopoverMenu.BMPopoverMenuItem]() - for asset in AssetsManager.shared().assets as! [BMAsset] { + for asset in AssetsManager.shared().getAssetsWithBalanceWithBeam() as! [BMAsset] { menu.append(BMPopoverMenu.BMPopoverMenuItem(name: asset.unitName, icon: nil, action: .asset, selected: self.viewModel.selectedAssetId == Int(asset.assetId))) } diff --git a/BeamWallet/ViewControllers/Main/Wallet/Transaction/Cell/TransactionDetailCell.swift b/BeamWallet/ViewControllers/Main/Wallet/Transaction/Cell/TransactionDetailCell.swift index 38887728..266384a2 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Transaction/Cell/TransactionDetailCell.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Transaction/Cell/TransactionDetailCell.swift @@ -47,8 +47,12 @@ class TransactionDetailCell: UITableViewCell { extension TransactionDetailCell: Configurable { func configure(with transaction:BMTransaction) { + guard let asset = transaction.asset else { + return + } + circleView.isBig = true - circleView.setAsset(transaction.asset) + circleView.setAsset(asset) statusIcon.image = transaction.statusIcon() secondAmountLabel.text = ExchangeManager.shared().exchangeValueAsset(transaction.realAmount, assetID: UInt64(transaction.assetId)) @@ -56,7 +60,7 @@ extension TransactionDetailCell: Configurable { secondAmountLabel.isHidden = Settings.sharedManager().isHideAmounts securityIcon.isHidden = !Settings.sharedManager().isHideAmounts - amountLabel.text = String.currency(value: transaction.realAmount, name: transaction.asset.unitName) + amountLabel.text = String.currency(value: transaction.realAmount, name: asset.unitName) statusLabel.text = transaction.statusType().capitalizingFirstLetter().replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") switch transaction.isIncome { diff --git a/BeamWallet/ViewControllers/Main/Wallet/Transaction/PaymentProofDetailViewController.swift b/BeamWallet/ViewControllers/Main/Wallet/Transaction/PaymentProofDetailViewController.swift index 192ae26a..c52bae8a 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Transaction/PaymentProofDetailViewController.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Transaction/PaymentProofDetailViewController.swift @@ -352,7 +352,7 @@ extension PaymentProofDetailViewController: GeneralInfoCellDelegate { if let path = tableView.indexPath(for: cell) { if details[path.section][path.row].title == Localizable.shared.strings.kernel_id.uppercased(), let transaction = self.transaction { - let kernelId = transaction.kernelId! + let kernelId = transaction.kernelId let link = Settings.sharedManager().explorerAddress + kernelId if let url = URL(string: link) { openUrl(url: url) diff --git a/BeamWallet/ViewControllers/Main/Wallet/Transaction/TransactionViewController.swift b/BeamWallet/ViewControllers/Main/Wallet/Transaction/TransactionViewController.swift index f3cb07c8..786de653 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/Transaction/TransactionViewController.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/Transaction/TransactionViewController.swift @@ -316,7 +316,7 @@ extension TransactionViewController: GeneralInfoCellDelegate { func onClickToCell(cell: UITableViewCell) { if let path = tableView.indexPath(for: cell) { if viewModel.details[path.row].title == Localizable.shared.strings.kernel_id.uppercased() { - let kernelId = viewModel.transaction!.kernelId! + let kernelId = viewModel.transaction!.kernelId let link = Settings.sharedManager().explorerAddress + kernelId if let url = URL(string: link) { openUrl(url: url) diff --git a/BeamWallet/ViewControllers/Main/Wallet/WalletViewController.swift b/BeamWallet/ViewControllers/Main/Wallet/WalletViewController.swift index 1f2b4886..b2e3ae70 100644 --- a/BeamWallet/ViewControllers/Main/Wallet/WalletViewController.swift +++ b/BeamWallet/ViewControllers/Main/Wallet/WalletViewController.swift @@ -221,6 +221,9 @@ extension WalletViewController: UITableViewDelegate { else if transactionViewModel.transactions.count == 0, section == 2 { return 0 } + else if assetViewModel.assets.count <= 1, section == 1 { + return 20 + } return BMTableHeaderTitleView.height } @@ -236,7 +239,7 @@ extension WalletViewController: UITableViewDelegate { pushViewController(vc: vc) } else if indexPath.section == 1 { - let vc = AssetDetailViewController(asset: assetViewModel.assets[indexPath.row]) + let vc = AssetDetailViewController(asset: assetViewModel.filteredAssets[indexPath.row]) pushViewController(vc: vc) } } @@ -254,7 +257,7 @@ extension WalletViewController: UITableViewDataSource { header.buttonFrame = header.bounds return header } - else if section == 1 { + else if section == 1, assetViewModel.assets.count > 1 { let header = BMTableHeaderTitleView(title: Localizable.shared.strings.assets.uppercased(), handler: #selector(onAssets), target: self) header.letterSpacing = 1.5 header.textColor = UIColor.white @@ -263,6 +266,11 @@ extension WalletViewController: UITableViewDataSource { header.buttonFrame = header.bounds return header } + else if assetViewModel.assets.count <= 1, section == 1 { + let v = UIView() + v.backgroundColor = UIColor.clear + return v + } return nil } @@ -275,7 +283,7 @@ extension WalletViewController: UITableViewDataSource { case 0: return statusViewModel.cells.count case 1: - return (assetViewModel.assets.count == 0) ? 0 : ((assetViewModel.assets.count > maximumAssetsCount) ? maximumTransactionsCount : assetViewModel.assets.count) + return (assetViewModel.filteredAssets.count == 0) ? 0 : ((assetViewModel.filteredAssets.count > maximumAssetsCount) ? maximumTransactionsCount : assetViewModel.filteredAssets.count) case 2: return (transactionViewModel.transactions.count == 0) ? 1 : ((transactionViewModel.transactions.count > maximumTransactionsCount) ? maximumTransactionsCount : transactionViewModel.transactions.count) default: @@ -305,7 +313,7 @@ extension WalletViewController: UITableViewDataSource { } case 1: let cell = tableView.dequeueReusableCell(withType: AssetAvailableCell.self, for: indexPath) - cell.setAsset(assetViewModel.assets[indexPath.row]) + cell.setAsset(assetViewModel.filteredAssets[indexPath.row]) return cell case 2: if transactionViewModel.transactions.count == 0 { diff --git a/Resources/InfoTestnet.plist b/Resources/InfoTestnet.plist index 136221f1..92e38a26 100644 --- a/Resources/InfoTestnet.plist +++ b/Resources/InfoTestnet.plist @@ -30,8 +30,8 @@ CFBundleTypeName Dat file - LSHandlerRank - Alternate + LSHandlerRank + Alternate LSItemContentTypes com.giena.Interface.document.dat diff --git a/Resources/cs.lproj/Localizable.strings b/Resources/cs.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/cs.lproj/Localizable.strings +++ b/Resources/cs.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/en.lproj/Localizable.strings b/Resources/en.lproj/Localizable.strings index e8c8a310..d751ec6c 100644 --- a/Resources/en.lproj/Localizable.strings +++ b/Resources/en.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; @@ -605,3 +605,5 @@ and never kept on the blockchain."; "remote_node_hint" = "To support maximum anonymity set and offline\ntransactions please connect to mobile node\nor own node configured with your owner key."; "receive_description_2" = "For online payment to complete, you should get online during 12 hours after coins are sent."; "only_online_support" = "You are currently connected to the node\nthat supports only online transactions."; +"max_funds_error" = "Insufficient funds to complete the transaction. Maximum amount is %@"; +"max_funds_hint" = "Maximum amount is %@"; diff --git a/Resources/es.lproj/Localizable.strings b/Resources/es.lproj/Localizable.strings index 6295d9cf..1272b6ea 100644 --- a/Resources/es.lproj/Localizable.strings +++ b/Resources/es.lproj/Localizable.strings @@ -334,7 +334,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/fi-FI.lproj/Localizable.strings b/Resources/fi-FI.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/fi-FI.lproj/Localizable.strings +++ b/Resources/fi-FI.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/fr.lproj/Localizable.strings b/Resources/fr.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/fr.lproj/Localizable.strings +++ b/Resources/fr.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/ko.lproj/Localizable.strings b/Resources/ko.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/ko.lproj/Localizable.strings +++ b/Resources/ko.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/nl.lproj/Localizable.strings b/Resources/nl.lproj/Localizable.strings index 6295d9cf..1272b6ea 100644 --- a/Resources/nl.lproj/Localizable.strings +++ b/Resources/nl.lproj/Localizable.strings @@ -334,7 +334,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/ru.lproj/Localizable.strings b/Resources/ru.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/ru.lproj/Localizable.strings +++ b/Resources/ru.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/sv.lproj/Localizable.strings b/Resources/sv.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/sv.lproj/Localizable.strings +++ b/Resources/sv.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/th.lproj/Localizable.strings b/Resources/th.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/th.lproj/Localizable.strings +++ b/Resources/th.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/tr.lproj/Localizable.strings b/Resources/tr.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/tr.lproj/Localizable.strings +++ b/Resources/tr.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/vi.lproj/Localizable.strings b/Resources/vi.lproj/Localizable.strings index 497f7d9f..d568b060 100644 --- a/Resources/vi.lproj/Localizable.strings +++ b/Resources/vi.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks"; diff --git a/Resources/zh-Hans.lproj/Localizable.strings b/Resources/zh-Hans.lproj/Localizable.strings index 8f514dab..584a6a5e 100644 --- a/Resources/zh-Hans.lproj/Localizable.strings +++ b/Resources/zh-Hans.lproj/Localizable.strings @@ -333,7 +333,7 @@ and never kept on the blockchain."; "restore_create_text" = "If you'll restore a wallet all transaction history and addresses will be lost."; "proceed" = "Proceed"; "info_restore_title" = "Do not simultaneously run two wallets initiated from the same seed phrase"; -"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not sycnrhonized between wallets in any case."; +"info_restore_text" = "If you run two wallets with the same seed, their balances will not be synchronized. To keep the balances in sync, use your own trusted node configured with your owner key. Also note that transaction history is not synchronized between wallets in any case."; "crash_title" = "Beam Wallet crashed unexpectedly"; "crash_message" = "Would you agree to report your limited amount of information to us? Collecting crash information would allow us to discover and fix bugs much faster. This is only a single crash-related report, we won't keep collecting any of your data later."; "crash_negative" = "No, thanks";