Generic fixes and enhancements (#1083)

* Add exception handler to fiat APIs
Increase send card size for coin control
Fix Monero.com unspent coins hive box issue
minor bug fix

* Remove EIP-1559 parameters from Eth transaction
Enhance error reporting

* Throw error if not enough monero utx outputs are selected

* Fix Search text color

* Fix Ethereum sending EIP-1559 transactions

* Add transaction data to ERC20 transactions

* Add input check in single output transactions as well

* Fix Node deletion issue
Handle user input error in anonpay

* Remove exception handler from fiat conversion since it's not working with isolates

* Require enough utxo for amount and fees; More insightful Error messages

* Add cakewallet to applinks [skip ci]

* Add cakewallet app link for iOS [skip ci]

* Add applink depending on app scheme variable

* Add applink in iOS custom to the app getting built [skip ci]

* Handle normal app links without considering them as Payment URIs

* Minor fix [skip ci]

* Fixate encrypt package version as the recent update they made has some issues [skip ci]

---------

Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
Omar Hatem 2023-09-14 22:14:16 +03:00 committed by GitHub
parent 4c9c6a1eae
commit ce4d375abf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 94 additions and 16 deletions

View file

@ -46,6 +46,7 @@
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="__APP_SCHEME__" />
<data android:scheme="bitcoin" /> <data android:scheme="bitcoin" />
<data android:scheme="bitcoin-wallet" /> <data android:scheme="bitcoin-wallet" />
<data android:scheme="bitcoin_wallet" /> <data android:scheme="bitcoin_wallet" />

View file

@ -93,6 +93,7 @@ class EthereumClient {
EthereumAddress.fromHex(toAddress), EthereumAddress.fromHex(toAddress),
BigInt.parse(amount), BigInt.parse(amount),
credentials: privateKey, credentials: privateKey,
transaction: transaction,
); );
}; };
} }
@ -107,7 +108,7 @@ class EthereumClient {
} }
Future<String> sendTransaction(Uint8List signedTransaction) async => Future<String> sendTransaction(Uint8List signedTransaction) async =>
await _client!.sendRawTransaction(signedTransaction); await _client!.sendRawTransaction(prependTransactionType(0x02, signedTransaction));
Future getTransactionDetails(String transactionHash) async { Future getTransactionDetails(String transactionHash) async {
// Wait for the transaction receipt to become available // Wait for the transaction receipt to become available

View file

@ -1,4 +1,8 @@
class MoneroTransactionNoInputsException implements Exception { class MoneroTransactionNoInputsException implements Exception {
MoneroTransactionNoInputsException(this.inputsSize);
int inputsSize;
@override @override
String toString() => 'Not enough inputs available. Please select more under Coin Control'; String toString() => 'Not enough inputs ($inputsSize) selected. Please select more under Coin Control';
} }

View file

@ -210,7 +210,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
} }
if (inputs.isEmpty) { if (inputs.isEmpty) {
throw MoneroTransactionNoInputsException(); throw MoneroTransactionNoInputsException(0);
} }
if (hasMultiDestination) { if (hasMultiDestination) {
@ -222,10 +222,15 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
final int totalAmount = outputs.fold(0, (acc, value) => final int totalAmount = outputs.fold(0, (acc, value) =>
acc + (value.formattedCryptoAmount ?? 0)); acc + (value.formattedCryptoAmount ?? 0));
final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
if (unlockedBalance < totalAmount) { if (unlockedBalance < totalAmount) {
throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.'); throw MoneroTransactionCreationException('You do not have enough XMR to send this amount.');
} }
if (allInputsAmount < totalAmount + estimatedFee) {
throw MoneroTransactionNoInputsException(inputs.length);
}
final moneroOutputs = outputs.map((output) { final moneroOutputs = outputs.map((output) {
final outputAddress = output.isParsedAddress final outputAddress = output.isParsedAddress
? output.extractedAddress ? output.extractedAddress
@ -262,6 +267,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.'); 'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
} }
final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
if ((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
(formattedAmount == null && allInputsAmount != unlockedBalance)) {
throw MoneroTransactionNoInputsException(inputs.length);
}
pendingTransactionDescription = await transaction_history.createTransaction( pendingTransactionDescription = await transaction_history.createTransaction(
address: address!, address: address!,
amount: amount, amount: amount,

View file

@ -8,6 +8,25 @@ PODS:
- Flutter - Flutter
- ReachabilitySwift - ReachabilitySwift
- CryptoSwift (1.7.1) - CryptoSwift (1.7.1)
- cw_haven (0.0.1):
- cw_haven/Boost (= 0.0.1)
- cw_haven/Haven (= 0.0.1)
- cw_haven/OpenSSL (= 0.0.1)
- cw_haven/Sodium (= 0.0.1)
- cw_shared_external
- Flutter
- cw_haven/Boost (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/Haven (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/OpenSSL (0.0.1):
- cw_shared_external
- Flutter
- cw_haven/Sodium (0.0.1):
- cw_shared_external
- Flutter
- cw_monero (0.0.2): - cw_monero (0.0.2):
- cw_monero/Boost (= 0.0.2) - cw_monero/Boost (= 0.0.2)
- cw_monero/Monero (= 0.0.2) - cw_monero/Monero (= 0.0.2)
@ -140,6 +159,7 @@ DEPENDENCIES:
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- CryptoSwift - CryptoSwift
- cw_haven (from `.symlinks/plugins/cw_haven/ios`)
- cw_monero (from `.symlinks/plugins/cw_monero/ios`) - cw_monero (from `.symlinks/plugins/cw_monero/ios`)
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
@ -185,6 +205,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/barcode_scan2/ios" :path: ".symlinks/plugins/barcode_scan2/ios"
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios" :path: ".symlinks/plugins/connectivity_plus/ios"
cw_haven:
:path: ".symlinks/plugins/cw_haven/ios"
cw_monero: cw_monero:
:path: ".symlinks/plugins/cw_monero/ios" :path: ".symlinks/plugins/cw_monero/ios"
cw_shared_external: cw_shared_external:
@ -239,6 +261,7 @@ SPEC CHECKSUMS:
BigInt: f668a80089607f521586bbe29513d708491ef2f7 BigInt: f668a80089607f521586bbe29513d708491ef2f7
connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1 CryptoSwift: d3d18dc357932f7e6d580689e065cf1f176007c1
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d cw_monero: 4cf3b96f2da8e95e2ef7d6703dd4d2c509127b7d
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7

View file

@ -36,6 +36,10 @@
<string>cakewallet</string> <string>cakewallet</string>
</array> </array>
</dict> </dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
</dict>
<dict> <dict>
<key>CFBundleTypeRole</key> <key>CFBundleTypeRole</key>
<string>Editor</string> <string>Editor</string>

View file

@ -114,7 +114,7 @@ Future<void> initializeAppConfigs() async {
CakeHive.registerAdapter(OrderAdapter()); CakeHive.registerAdapter(OrderAdapter());
} }
if (!isMoneroOnly && !CakeHive.isAdapterRegistered(UnspentCoinsInfo.typeId)) { if (!CakeHive.isAdapterRegistered(UnspentCoinsInfo.typeId)) {
CakeHive.registerAdapter(UnspentCoinsInfoAdapter()); CakeHive.registerAdapter(UnspentCoinsInfoAdapter());
} }

View file

@ -9,6 +9,7 @@ import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.da
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart'; import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/themes/extensions/address_theme.dart'; import 'package:cake_wallet/themes/extensions/address_theme.dart';
import 'package:cake_wallet/themes/extensions/menu_theme.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart';
import 'package:cake_wallet/themes/extensions/picker_theme.dart';
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -55,7 +56,8 @@ class HomeSettingsPage extends BasePage {
padding: const EdgeInsetsDirectional.only(start: 16), padding: const EdgeInsetsDirectional.only(start: 16),
child: TextFormField( child: TextFormField(
controller: _searchController, controller: _searchController,
style: TextStyle(color: Theme.of(context).dialogTheme.backgroundColor), style: TextStyle(
color: Theme.of(context).extension<PickerTheme>()!.searchHintColor),
decoration: InputDecoration( decoration: InputDecoration(
hintText: S.of(context).search_add_token, hintText: S.of(context).search_add_token,
prefixIcon: Image.asset("assets/images/search_icon.png"), prefixIcon: Image.asset("assets/images/search_icon.png"),

View file

@ -140,7 +140,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} }
_reset(); _reset();
totpAuth.close( totpAuth.close(
route: launchUri != null ? Routes.send : null, route: _isValidPaymentUri() ? Routes.send : null,
arguments: PaymentRequest.fromUri(launchUri), arguments: PaymentRequest.fromUri(launchUri),
); );
launchUri = null; launchUri = null;
@ -152,7 +152,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
} else { } else {
_reset(); _reset();
auth.close( auth.close(
route: launchUri != null ? Routes.send : null, route: _isValidPaymentUri() ? Routes.send : null,
arguments: PaymentRequest.fromUri(launchUri), arguments: PaymentRequest.fromUri(launchUri),
); );
launchUri = null; launchUri = null;
@ -161,7 +161,7 @@ class RootState extends State<Root> with WidgetsBindingObserver {
}, },
); );
}); });
} else if (launchUri != null) { } else if (_isValidPaymentUri()) {
widget.navigatorKey.currentState?.pushNamed( widget.navigatorKey.currentState?.pushNamed(
Routes.send, Routes.send,
arguments: PaymentRequest.fromUri(launchUri), arguments: PaymentRequest.fromUri(launchUri),
@ -183,4 +183,6 @@ class RootState extends State<Root> with WidgetsBindingObserver {
_isInactive = value; _isInactive = value;
_isInactiveController.add(value); _isInactiveController.add(value);
} }
bool _isValidPaymentUri() => launchUri?.path.isNotEmpty ?? false;
} }

View file

@ -100,7 +100,7 @@ class SendPage extends BasePage {
AppBarStyle get appBarStyle => AppBarStyle.transparent; AppBarStyle get appBarStyle => AppBarStyle.transparent;
double _sendCardHeight(BuildContext context) { double _sendCardHeight(BuildContext context) {
final double initialHeight = sendViewModel.hasCoinControl ? 490 : 465; final double initialHeight = sendViewModel.hasCoinControl ? 500 : 465;
if (!ResponsiveLayoutUtil.instance.isMobile) { if (!ResponsiveLayoutUtil.instance.isMobile) {
return initialHeight - 66; return initialHeight - 66;

View file

@ -34,13 +34,12 @@ class ManageNodesPage extends BasePage {
SizedBox(height: 20), SizedBox(height: 20),
Observer( Observer(
builder: (BuildContext context) { builder: (BuildContext context) {
int itemsCount = nodeListViewModel.nodes.length;
return Flexible( return Flexible(
child: SectionStandardList( child: SectionStandardList(
sectionCount: 1, sectionCount: 1,
dividerPadding: EdgeInsets.symmetric(horizontal: 24), dividerPadding: EdgeInsets.symmetric(horizontal: 24),
itemCounter: (int sectionIndex) { itemCounter: (int sectionIndex) => itemsCount,
return nodeListViewModel.nodes.length;
},
itemBuilder: (_, index) { itemBuilder: (_, index) {
return Observer( return Observer(
builder: (context) { builder: (context) {

View file

@ -32,6 +32,14 @@ class ExceptionHandler {
const String separator = '''\n\n========================================================== const String separator = '''\n\n==========================================================
==========================================================\n\n'''; ==========================================================\n\n''';
/// don't save existing errors
if (file.existsSync()) {
final String fileContent = await file.readAsString();
if (fileContent.contains("${exception.values.first}")) {
return;
}
}
file.writeAsStringSync( file.writeAsStringSync(
"$exception $separator", "$exception $separator",
mode: FileMode.append, mode: FileMode.append,
@ -83,6 +91,10 @@ class ExceptionHandler {
library: errorDetails.library, library: errorDetails.library,
); );
if (errorDetails.silent) {
return;
}
final sharedPrefs = await SharedPreferences.getInstance(); final sharedPrefs = await SharedPreferences.getInstance();
final lastPopupDate = final lastPopupDate =

View file

@ -93,7 +93,11 @@ abstract class AnonInvoicePageViewModelBase with Store {
Future<void> createInvoice() async { Future<void> createInvoice() async {
state = IsExecutingState(); state = IsExecutingState();
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
final amountInCrypto = double.parse(amount); final amountInCrypto = double.tryParse(amount);
if (amountInCrypto == null) {
state = FailureState('Amount is invalid');
return;
}
if (minimum != null && amountInCrypto < minimum!) { if (minimum != null && amountInCrypto < minimum!) {
state = FailureState('Amount is too small'); state = FailureState('Amount is too small');
return; return;

View file

@ -225,7 +225,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
@computed @computed
List<WalletContact> get walletContactsToShow => contactListViewModel.walletContacts List<WalletContact> get walletContactsToShow => contactListViewModel.walletContacts
.where((element) => receiveCurrency == null || element.type == receiveCurrency) .where((element) => element.type == receiveCurrency)
.toList(); .toList();
@action @action
@ -550,6 +550,9 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
amount = amount.replaceAll(',', '.'); amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully) { if (limitsState is LimitsLoadedSuccessfully) {
if (double.tryParse(amount) == null) {
continue;
}
if (limits.max != null && double.parse(amount) < limits.min!) { if (limits.max != null && double.parse(amount) < limits.min!) {
continue; continue;
} else if (limits.max != null && double.parse(amount) > limits.max!) { } else if (limits.max != null && double.parse(amount) > limits.max!) {

View file

@ -49,7 +49,7 @@ dependencies:
lottie: ^1.3.0 lottie: ^1.3.0
animate_do: ^2.1.0 animate_do: ^2.1.0
cupertino_icons: ^1.0.5 cupertino_icons: ^1.0.5
encrypt: ^5.0.1 encrypt: 5.0.1
crypto: ^3.0.2 crypto: ^3.0.2
# password: ^1.0.0 # password: ^1.0.0
basic_utils: ^5.6.1 basic_utils: ^5.6.1

View file

@ -5,6 +5,7 @@ APP_ANDROID_VERSION=""
APP_ANDROID_BUILD_VERSION="" APP_ANDROID_BUILD_VERSION=""
APP_ANDROID_ID="" APP_ANDROID_ID=""
APP_ANDROID_PACKAGE="" APP_ANDROID_PACKAGE=""
APP_ANDROID_SCHEME=""
MONERO_COM="monero.com" MONERO_COM="monero.com"
CAKEWALLET="cakewallet" CAKEWALLET="cakewallet"
@ -18,12 +19,14 @@ MONERO_COM_VERSION="1.6.0"
MONERO_COM_BUILD_NUMBER=56 MONERO_COM_BUILD_NUMBER=56
MONERO_COM_BUNDLE_ID="com.monero.app" MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app" MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet" CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.9.0" CAKEWALLET_VERSION="4.9.0"
CAKEWALLET_BUILD_NUMBER=169 CAKEWALLET_BUILD_NUMBER=169
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet" CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet" CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"
HAVEN_NAME="Haven" HAVEN_NAME="Haven"
HAVEN_VERSION="1.0.0" HAVEN_VERSION="1.0.0"
@ -44,6 +47,7 @@ case $APP_ANDROID_TYPE in
APP_ANDROID_BUILD_NUMBER=$MONERO_COM_BUILD_NUMBER APP_ANDROID_BUILD_NUMBER=$MONERO_COM_BUILD_NUMBER
APP_ANDROID_BUNDLE_ID=$MONERO_COM_BUNDLE_ID APP_ANDROID_BUNDLE_ID=$MONERO_COM_BUNDLE_ID
APP_ANDROID_PACKAGE=$MONERO_COM_PACKAGE APP_ANDROID_PACKAGE=$MONERO_COM_PACKAGE
APP_ANDROID_SCHEME=$MONERO_COM_SCHEME
;; ;;
$CAKEWALLET) $CAKEWALLET)
APP_ANDROID_NAME=$CAKEWALLET_NAME APP_ANDROID_NAME=$CAKEWALLET_NAME
@ -51,6 +55,7 @@ case $APP_ANDROID_TYPE in
APP_ANDROID_BUILD_NUMBER=$CAKEWALLET_BUILD_NUMBER APP_ANDROID_BUILD_NUMBER=$CAKEWALLET_BUILD_NUMBER
APP_ANDROID_BUNDLE_ID=$CAKEWALLET_BUNDLE_ID APP_ANDROID_BUNDLE_ID=$CAKEWALLET_BUNDLE_ID
APP_ANDROID_PACKAGE=$CAKEWALLET_PACKAGE APP_ANDROID_PACKAGE=$CAKEWALLET_PACKAGE
APP_ANDROID_SCHEME=$CAKEWALLET_SCHEME
;; ;;
$HAVEN) $HAVEN)
APP_ANDROID_NAME=$HAVEN_NAME APP_ANDROID_NAME=$HAVEN_NAME
@ -67,3 +72,4 @@ export APP_ANDROID_VERSION
export APP_ANDROID_BUILD_NUMBER export APP_ANDROID_BUILD_NUMBER
export APP_ANDROID_BUNDLE_ID export APP_ANDROID_BUNDLE_ID
export APP_ANDROID_PACKAGE export APP_ANDROID_PACKAGE
export APP_ANDROID_SCHEME

View file

@ -8,6 +8,7 @@ fi
cd ../.. cd ../..
sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml sed -i "0,/version:/{s/version:.*/version: ${APP_ANDROID_VERSION}+${APP_ANDROID_BUILD_NUMBER}/}" ./pubspec.yaml
sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml sed -i "0,/version:/{s/__APP_PACKAGE__/${APP_ANDROID_PACKAGE}/}" ./android/app/src/main/AndroidManifest.xml
sed -i "0,/__APP_SCHEME__/s/__APP_SCHEME__/${APP_ANDROID_SCHEME}/" ./android/app/src/main/AndroidManifest.xml
sed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml sed -i "0,/version:/{s/__versionCode__/${APP_ANDROID_BUILD_NUMBER}/}" ./android/app/src/main/AndroidManifest.xml
sed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml sed -i "0,/version:/{s/__versionName__/${APP_ANDROID_VERSION}/}" ./android/app/src/main/AndroidManifest.xml
cd scripts/android cd scripts/android

View file

@ -16,6 +16,11 @@ cp -rf ./ios/Runner/InfoBase.plist ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${APP_IOS_BUNDLE_ID}" ./ios/Runner/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier ${APP_IOS_BUNDLE_ID}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${APP_IOS_VERSION}" ./ios/Runner/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${APP_IOS_VERSION}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${APP_IOS_BUILD_NUMBER}" ./ios/Runner/Info.plist /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${APP_IOS_BUILD_NUMBER}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLName string ${APP_IOS_TYPE}" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLSchemes array" ./ios/Runner/Info.plist
/usr/libexec/PlistBuddy -c "Add :CFBundleURLTypes:1:CFBundleURLSchemes: string ${APP_IOS_TYPE}" ./ios/Runner/Info.plist
CONFIG_ARGS="" CONFIG_ARGS=""
case $APP_IOS_TYPE in case $APP_IOS_TYPE in