diff --git a/android/app/build.gradle b/android/app/build.gradle index 9132b0867..06b86844c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -104,6 +104,7 @@ android { proguardFile "${rootProject.projectDir}/../node_modules/detox/android/detox/proguard-rules-app.pro" } } + } task copyFiatUnits(type: Copy) { @@ -117,6 +118,8 @@ dependencies { // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") implementation files("../../node_modules/rn-ldk/android/libs/LDK-release.aar") + implementation 'androidx.core:core-ktx:1.13.1' + implementation 'androidx.core:core-remoteviews:1.1.0' if (hermesEnabled.toBoolean()) { implementation("com.facebook.react:hermes-android") @@ -129,7 +132,9 @@ dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.google.android.material:material:1.4.0' implementation "androidx.work:work-runtime:2.9.0" + implementation "androidx.work:work-runtime-ktx:2.9.0" } apply plugin: 'com.google.gms.google-services' // Google Services plugin -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) \ No newline at end of file +apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle") +apply plugin: 'org.jetbrains.kotlin.android'; applyNativeModulesAppBuildGradle(project) \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 121d60b5a..ade4b6cb1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ + - - - - - - - - + + + + + + + + getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - return packages; - } - - @Override - protected String getJSMainModuleName() { - return "index"; - } - - @Override - protected boolean isNewArchEnabled() { - return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; - } - - @Override - protected Boolean isHermesEnabled() { - return BuildConfig.IS_HERMES_ENABLED; - } - }; - - @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; - } - - @Override - public void onCreate() { - super.onCreate(); - I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance(); - sharedI18nUtilInstance.allowRTL(getApplicationContext(), true); - SoLoader.init(this, /* native exopackage */ false); - - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - DefaultNewArchitectureEntryPoint.load(); - } - - SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE); - String isDoNotTrackEnabled = sharedPref.getString("donottrack", "0"); - - if (!isDoNotTrackEnabled.equals("1")) { - Bugsnag.start(this); - } - - - // Schedule periodic widget updates - PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(WidgetUpdateWorker.class, 4, TimeUnit.MINUTES).build(); - WorkManager.getInstance(this).enqueueUniquePeriodicWork("UpdateWidgetWork", ExistingPeriodicWorkPolicy.REPLACE, workRequest); - - } -} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt new file mode 100644 index 000000000..0df09249c --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.kt @@ -0,0 +1,57 @@ +package io.bluewallet.bluewallet + +import android.app.Application +import android.content.Context +import com.bugsnag.android.Bugsnag +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactInstanceManager +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint +import com.facebook.react.defaults.DefaultReactNativeHost +import com.facebook.soloader.SoLoader +import com.facebook.react.modules.i18nmanager.I18nUtil + +class MainApplication : Application(), ReactApplication { + + private val mReactNativeHost = object : DefaultReactNativeHost(this) { + override fun getUseDeveloperSupport() = BuildConfig.DEBUG + + override fun getPackages(): List { + val packages = PackageList(this).packages + // Packages that cannot be autolinked yet can be added manually here, for example: + return packages + } + + override fun getJSMainModuleName() = "index" + + override val isNewArchEnabled = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + + override val isHermesEnabled = BuildConfig.IS_HERMES_ENABLED + } + + override fun getReactNativeHost() = mReactNativeHost + + override fun onCreate() { + super.onCreate() + val sharedI18nUtilInstance = I18nUtil.getInstance() + sharedI18nUtilInstance.allowRTL(applicationContext, true) + SoLoader.init(this, /* native exopackage */ false) + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + // If you opted-in for the New Architecture, we load the native entry point for this app. + DefaultNewArchitectureEntryPoint.load() + } + + val sharedPref = applicationContext.getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE) + + // Retrieve the "donottrack" value. Default to "0" if not found. + val isDoNotTrackEnabled = sharedPref.getString("donottrack", "0") + + // Check if do not track is not enabled and initialize Bugsnag if so + if (isDoNotTrackEnabled != "1") { + // Initialize Bugsnag or your error tracking here + Bugsnag.start(this) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java deleted file mode 100644 index 3a35aa2d8..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java +++ /dev/null @@ -1,122 +0,0 @@ -package io.bluewallet.bluewallet; - -import android.content.Context; -import android.util.Log; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URI; -import java.net.URL; - -public class MarketAPI { - - private static final String TAG = "MarketAPI"; - private static final String HARD_CODED_JSON = "{\n" + - " \"USD\": {\n" + - " \"endPointKey\": \"USD\",\n" + - " \"locale\": \"en-US\",\n" + - " \"source\": \"Kraken\",\n" + - " \"symbol\": \"$\",\n" + - " \"country\": \"United States (US Dollar)\"\n" + - " }\n" + - "}"; - - public static String fetchPrice(Context context, String currency) { - try { - JSONObject json = new JSONObject(HARD_CODED_JSON); - JSONObject currencyInfo = json.getJSONObject(currency); - String source = currencyInfo.getString("source"); - String endPointKey = currencyInfo.getString("endPointKey"); - - String urlString = buildURLString(source, endPointKey); - Log.d(TAG, "Fetching from URL: " + urlString); - URI uri = new URI(urlString); - URL url = uri.toURL(); - HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); - urlConnection.setRequestMethod("GET"); - urlConnection.connect(); - - int responseCode = urlConnection.getResponseCode(); - if (responseCode != 200) { - return null; - } - - InputStreamReader reader = new InputStreamReader(urlConnection.getInputStream()); - StringBuilder jsonResponse = new StringBuilder(); - int read; - char[] buffer = new char[1024]; - while ((read = reader.read(buffer)) != -1) { - jsonResponse.append(buffer, 0, read); - } - - return parseJSONBasedOnSource(jsonResponse.toString(), source, endPointKey); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - private static String buildURLString(String source, String endPointKey) { - switch (source) { - case "Yadio": - return "https://api.yadio.io/json/" + endPointKey; - case "YadioConvert": - return "https://api.yadio.io/convert/1/BTC/" + endPointKey; - case "Exir": - return "https://api.exir.io/v1/ticker?symbol=btc-irt"; - case "wazirx": - return "https://api.wazirx.com/api/v2/tickers/btcinr"; - case "Bitstamp": - return "https://www.bitstamp.net/api/v2/ticker/btc" + endPointKey.toLowerCase(); - case "Coinbase": - return "https://api.coinbase.com/v2/prices/BTC-" + endPointKey.toUpperCase() + "/buy"; - case "CoinGecko": - return "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=" + endPointKey.toLowerCase(); - case "BNR": - return "https://www.bnr.ro/nbrfxrates.xml"; - case "Kraken": - return "https://api.kraken.com/0/public/Ticker?pair=XXBTZ" + endPointKey.toUpperCase(); - default: - return "https://api.coindesk.com/v1/bpi/currentprice/" + endPointKey + ".json"; - } - } - - private static String parseJSONBasedOnSource(String jsonString, String source, String endPointKey) { - try { - JSONObject json = new JSONObject(jsonString); - switch (source) { - case "Yadio": - JSONObject rateDict = json.getJSONObject(endPointKey); - return rateDict.getString("price"); - case "YadioConvert": - return json.getString("rate"); - case "CoinGecko": - JSONObject bitcoinDict = json.getJSONObject("bitcoin"); - return bitcoinDict.getString(endPointKey.toLowerCase()); - case "Exir": - return json.getString("last"); - case "Bitstamp": - return json.getString("last"); - case "wazirx": - JSONObject tickerDict = json.getJSONObject("ticker"); - return tickerDict.getString("buy"); - case "Coinbase": - JSONObject data = json.getJSONObject("data"); - return data.getString("amount"); - case "Kraken": - JSONObject result = json.getJSONObject("result"); - JSONObject tickerData = result.getJSONObject("XXBTZ" + endPointKey.toUpperCase()); - JSONArray c = tickerData.getJSONArray("c"); - return c.getString(0); - default: - return null; - } - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } -} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.kt b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.kt new file mode 100644 index 000000000..819767458 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.kt @@ -0,0 +1,99 @@ +package io.bluewallet.bluewallet + +import android.content.Context +import android.util.Log +import org.json.JSONObject +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL + +object MarketAPI { + + private const val TAG = "MarketAPI" + private const val HARD_CODED_JSON = "{\n" + + " \"USD\": {\n" + + " \"endPointKey\": \"USD\",\n" + + " \"locale\": \"en-US\",\n" + + " \"source\": \"Kraken\",\n" + + " \"symbol\": \"$\",\n" + + " \"country\": \"United States (US Dollar)\"\n" + + " }\n" + + "}" + + var baseUrl: String? = null + + fun fetchPrice(context: Context, currency: String): String? { + return try { + val json = JSONObject(HARD_CODED_JSON) + val currencyInfo = json.getJSONObject(currency) + val source = currencyInfo.getString("source") + val endPointKey = currencyInfo.getString("endPointKey") + + val urlString = buildURLString(source, endPointKey) + Log.d(TAG, "Fetching price from URL: $urlString") + + val url = URL(urlString) + val urlConnection = url.openConnection() as HttpURLConnection + urlConnection.requestMethod = "GET" + urlConnection.connect() + + val responseCode = urlConnection.responseCode + if (responseCode != 200) { + Log.e(TAG, "Failed to fetch price. Response code: $responseCode") + return null + } + + val reader = InputStreamReader(urlConnection.inputStream) + val jsonResponse = StringBuilder() + val buffer = CharArray(1024) + var read: Int + while (reader.read(buffer).also { read = it } != -1) { + jsonResponse.append(buffer, 0, read) + } + + parseJSONBasedOnSource(jsonResponse.toString(), source, endPointKey) + } catch (e: Exception) { + Log.e(TAG, "Error fetching price", e) + null + } + } + + private fun buildURLString(source: String, endPointKey: String): String { + return if (baseUrl != null) { + baseUrl + endPointKey + } else { + when (source) { + "Yadio" -> "https://api.yadio.io/json/$endPointKey" + "YadioConvert" -> "https://api.yadio.io/convert/1/BTC/$endPointKey" + "Exir" -> "https://api.exir.io/v1/ticker?symbol=btc-irt" + "wazirx" -> "https://api.wazirx.com/api/v2/tickers/btcinr" + "Bitstamp" -> "https://www.bitstamp.net/api/v2/ticker/btc${endPointKey.lowercase()}" + "Coinbase" -> "https://api.coinbase.com/v2/prices/BTC-${endPointKey.uppercase()}/buy" + "CoinGecko" -> "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=${endPointKey.lowercase()}" + "BNR" -> "https://www.bnr.ro/nbrfxrates.xml" + "Kraken" -> "https://api.kraken.com/0/public/Ticker?pair=XXBTZ${endPointKey.uppercase()}" + else -> "https://api.coindesk.com/v1/bpi/currentprice/$endPointKey.json" + } + } + } + + private fun parseJSONBasedOnSource(jsonString: String, source: String, endPointKey: String): String? { + return try { + val json = JSONObject(jsonString) + when (source) { + "Yadio" -> json.getJSONObject(endPointKey).getString("price") + "YadioConvert" -> json.getString("rate") + "CoinGecko" -> json.getJSONObject("bitcoin").getString(endPointKey.lowercase()) + "Exir" -> json.getString("last") + "Bitstamp" -> json.getString("last") + "wazirx" -> json.getJSONObject("ticker").getString("buy") + "Coinbase" -> json.getJSONObject("data").getString("amount") + "Kraken" -> json.getJSONObject("result").getJSONObject("XXBTZ${endPointKey.uppercase()}").getJSONArray("c").getString(0) + else -> null + } + } catch (e: Exception) { + Log.e(TAG, "Error parsing price", e) + null + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java deleted file mode 100644 index 3e06a53b2..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java +++ /dev/null @@ -1,80 +0,0 @@ -package io.bluewallet.bluewallet; - -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import android.view.View; -import android.widget.RemoteViews; -import androidx.work.Worker; -import androidx.work.WorkerParameters; - -import java.text.DateFormat; -import java.util.Date; -import java.util.Locale; - -public class WidgetUpdateManager extends Worker { - private static final String TAG = "WidgetUpdateManager"; - private static final String PREFS_NAME = "BitcoinPriceWidgetPrefs"; - private static final String PREF_PREFIX_KEY = "appwidget_"; - private static final String PREFS_LAST_UPDATE_TIME_KEY = "lastUpdateTime_"; - private static final String PREFS_LAST_PRICE_KEY = "lastPrice_"; - private static final String PREFS_PREV_PRICE_KEY = "prevPrice_"; - private static final String PREFS_PREV_UPDATE_TIME_KEY = "prevUpdateTime_"; - - public WidgetUpdateManager(Context context, WorkerParameters params) { - super(context, params); - } - - @Override - public Result doWork() { - Context context = getApplicationContext(); - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, BitcoinPriceWidget.class)); - - for (int appWidgetId : appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId); - } - return Result.success(); - } - - public static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0); - String previousPrice = prefs.getString(PREFS_PREV_PRICE_KEY + appWidgetId, "N/A"); - String lastPrice = prefs.getString(PREFS_LAST_PRICE_KEY + appWidgetId, "N/A"); - String lastUpdateTime = prefs.getString(PREFS_LAST_UPDATE_TIME_KEY + appWidgetId, "N/A"); - String previousUpdateTime = prefs.getString(PREFS_PREV_UPDATE_TIME_KEY + appWidgetId, "N/A"); - - Log.d(TAG, "Fetch completed with price: " + lastPrice + " at " + lastUpdateTime + ". Previous price: " + previousPrice + " at " + previousUpdateTime); - - String price = MarketAPI.fetchPrice(context, "USD"); - if (price != null) { - Log.d(TAG, "Fetch completed with price: " + price); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(PREFS_PREV_PRICE_KEY + appWidgetId, lastPrice); - editor.putString(PREFS_PREV_UPDATE_TIME_KEY + appWidgetId, lastUpdateTime); - editor.putString(PREFS_LAST_PRICE_KEY + appWidgetId, price); - editor.putString(PREFS_LAST_UPDATE_TIME_KEY + appWidgetId, DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date())); - editor.apply(); - - RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout); - views.setTextViewText(R.id.price_value, price); - views.setTextViewText(R.id.last_updated_time, DateFormat.getTimeInstance(DateFormat.SHORT).format(new Date())); - views.setViewVisibility(R.id.price_value, View.VISIBLE); - views.setViewVisibility(R.id.last_updated_label, View.VISIBLE); - views.setViewVisibility(R.id.last_updated_time, View.VISIBLE); - - if (!"N/A".equals(previousPrice)) { - views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE); - views.setViewVisibility(R.id.previous_price, View.VISIBLE); - } else { - views.setViewVisibility(R.id.price_arrow_container, View.GONE); - views.setViewVisibility(R.id.previous_price, View.GONE); - } - appWidgetManager.updateAppWidget(appWidgetId, views); - } else { - Log.e(TAG, "Failed to fetch Bitcoin price"); - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java deleted file mode 100644 index d62aecd1a..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java +++ /dev/null @@ -1,108 +0,0 @@ -package io.bluewallet.bluewallet; - -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Log; -import android.view.View; -import android.widget.RemoteViews; - -import androidx.annotation.NonNull; -import androidx.work.Worker; -import androidx.work.WorkerParameters; -import androidx.work.OneTimeWorkRequest; - -import java.text.NumberFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -public class WidgetUpdateWorker extends Worker { - - private static final String TAG = "WidgetUpdateWorker"; - private static final String PREFS_NAME = "BitcoinPriceWidgetPrefs"; - private static final String PREF_PREFIX_KEY_CURRENT = "appwidget_current_"; - private static final String PREF_PREFIX_KEY_PREVIOUS = "appwidget_previous_"; - - public WidgetUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) { - super(context, params); - } - - @NonNull - @Override - public Result doWork() { - Context context = getApplicationContext(); - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - ComponentName thisWidget = new ComponentName(context, BitcoinPriceWidget.class); - int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); - for (int appWidgetId : appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId); - } - return Result.success(); - } - - private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { - SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0); - String prevPrice = prefs.getString(PREF_PREFIX_KEY_PREVIOUS + appWidgetId, "N/A"); - String currentPrice = prefs.getString(PREF_PREFIX_KEY_CURRENT + appWidgetId, "N/A"); - - Log.d(TAG, "Previous price: " + prevPrice); - Log.d(TAG, "Current price: " + currentPrice); - - RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout); - - // Fetch the latest price - String newPrice = MarketAPI.fetchPrice(context, "USD"); - if (newPrice != null) { - String currentTime = new SimpleDateFormat("hh:mm a", Locale.getDefault()).format(new Date()); - NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.getDefault()); - - // Update the widget views - views.setTextViewText(R.id.price_value, currencyFormat.format(Double.parseDouble(newPrice))); - views.setTextViewText(R.id.last_updated_time, currentTime); - views.setViewVisibility(R.id.last_updated_label, View.VISIBLE); - views.setViewVisibility(R.id.last_updated_time, View.VISIBLE); - - if (!prevPrice.equals("N/A") && !prevPrice.equals(newPrice)) { - views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE); - views.setTextViewText(R.id.previous_price, currencyFormat.format(Double.parseDouble(prevPrice))); - views.setViewVisibility(R.id.previous_price_label, View.VISIBLE); - views.setViewVisibility(R.id.previous_price, View.VISIBLE); - if (Double.parseDouble(newPrice) > Double.parseDouble(prevPrice)) { - views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_up_float); - } else { - views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_down_float); - } - } else { - views.setViewVisibility(R.id.price_arrow_container, View.GONE); - views.setViewVisibility(R.id.previous_price_label, View.GONE); - views.setViewVisibility(R.id.previous_price, View.GONE); - } - - // Save the new price and time - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(PREF_PREFIX_KEY_PREVIOUS + appWidgetId, currentPrice); - editor.putString(PREF_PREFIX_KEY_CURRENT + appWidgetId, newPrice); - editor.apply(); - - Log.d(TAG, "Fetch completed with price: " + newPrice + " at " + currentTime + ". Previous price: " + prevPrice); - - appWidgetManager.updateAppWidget(appWidgetId, views); - - // Log the next update time - long nextUpdateTimeMillis = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(4); - String nextUpdateTime = new SimpleDateFormat("hh:mm a", Locale.getDefault()).format(new Date(nextUpdateTimeMillis)); - Log.d(TAG, "Next fetch scheduled at: " + nextUpdateTime); - } else { - Log.e(TAG, "Failed to fetch Bitcoin price"); - views.setTextViewText(R.id.price_value, "Error"); - appWidgetManager.updateAppWidget(appWidgetId, views); - } - } - - public static OneTimeWorkRequest createWorkRequest() { - return new OneTimeWorkRequest.Builder(WidgetUpdateWorker.class).build(); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt new file mode 100644 index 000000000..30b30f1e4 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.kt @@ -0,0 +1,107 @@ +package io.bluewallet.bluewallet + +import android.appwidget.AppWidgetManager +import android.content.ComponentName +import android.content.Context +import android.content.SharedPreferences +import android.util.Log +import android.view.View +import android.widget.RemoteViews +import androidx.work.* +import org.json.JSONObject +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.URL +import java.text.NumberFormat +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.TimeUnit + +class WidgetUpdateWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) { + + companion object { + const val TAG = "WidgetUpdateWorker" + const val WORK_NAME = "widget_update_work" + const val REPEAT_INTERVAL_MINUTES = 15L + + fun scheduleWork(context: Context) { + val workRequest = PeriodicWorkRequestBuilder( + REPEAT_INTERVAL_MINUTES, TimeUnit.MINUTES + ).build() + WorkManager.getInstance(context).enqueueUniquePeriodicWork( + WORK_NAME, + ExistingPeriodicWorkPolicy.REPLACE, + workRequest + ) + Log.d(TAG, "Scheduling work for widget updates, will run every $REPEAT_INTERVAL_MINUTES minutes") + } + } + + override fun doWork(): Result { + Log.d(TAG, "Widget update worker running") + + val appWidgetManager = AppWidgetManager.getInstance(applicationContext) + val thisWidget = ComponentName(applicationContext, BitcoinPriceWidget::class.java) + val appWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget) + val views = RemoteViews(applicationContext.packageName, R.layout.widget_layout) + + val sharedPref = applicationContext.getSharedPreferences("widget_prefs", Context.MODE_PRIVATE) + + val currentTime = SimpleDateFormat("hh:mm a", Locale.getDefault()).format(Date()) + val price = fetchPrice() + val previousPrice = sharedPref.getString("previous_price", null) + + // Log fetched data + Log.d(TAG, "Fetch completed with price: $price at $currentTime. Previous price: $previousPrice") + + // Update views + val currencyFormat = NumberFormat.getCurrencyInstance(Locale.getDefault()).apply { + maximumFractionDigits = 0 + } + views.setTextViewText(R.id.price_value, currencyFormat.format(price.toDouble())) + views.setTextViewText(R.id.last_updated_time, currentTime) + views.setViewVisibility(R.id.last_updated_label, View.VISIBLE) + views.setViewVisibility(R.id.last_updated_time, View.VISIBLE) + + if (previousPrice != null) { + views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE) + views.setTextViewText(R.id.previous_price, currencyFormat.format(previousPrice.toDouble())) + if (price.toDouble() > previousPrice.toDouble()) { + views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_up_float) + } else { + views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_down_float) + } + } else { + views.setViewVisibility(R.id.price_arrow_container, View.GONE) + } + + appWidgetManager.updateAppWidget(appWidgetIds, views) + + savePrice(sharedPref, price) + + return Result.success() + } + + private fun fetchPrice(): String { + val urlString = "https://api.kraken.com/0/public/Ticker?pair=XXBTZUSD" + val url = URL(urlString) + val urlConnection = url.openConnection() as HttpURLConnection + return try { + val reader = InputStreamReader(urlConnection.inputStream) + val jsonResponse = StringBuilder() + val buffer = CharArray(1024) + var read: Int + while (reader.read(buffer).also { read = it } != -1) { + jsonResponse.append(buffer, 0, read) + } + val json = JSONObject(jsonResponse.toString()) + json.getJSONObject("result").getJSONObject("XXBTZUSD").getJSONArray("c").getString(0) + } finally { + urlConnection.disconnect() + } + } + + private fun savePrice(sharedPref: SharedPreferences, price: String) { + sharedPref.edit().putString("previous_price", price).apply() + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUtils.java b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUtils.java deleted file mode 100644 index 3b1a128fa..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUtils.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.bluewallet.bluewallet; - -import android.appwidget.AppWidgetManager; -import android.content.ComponentName; -import android.content.Context; -import android.widget.RemoteViews; - -public class WidgetUtils { - - public static void updateAppWidget(Context context, RemoteViews views) { - AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); - ComponentName thisWidget = new ComponentName(context, BitcoinPriceWidget.class); - int[] allWidgetIds = appWidgetManager.getAppWidgetIds(thisWidget); - - for (int widgetId : allWidgetIds) { - appWidgetManager.updateAppWidget(widgetId, views); - } - } -} diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index f0a8fbef3..4b2a094b9 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -1,3 +1,6 @@ BlueWallet + Loading... + Last Updated + From diff --git a/android/app/src/main/res/xml/bitcoin_price_widget_info.xml b/android/app/src/main/res/xml/bitcoin_price_widget_info.xml index db053a920..736f12190 100644 --- a/android/app/src/main/res/xml/bitcoin_price_widget_info.xml +++ b/android/app/src/main/res/xml/bitcoin_price_widget_info.xml @@ -1,9 +1,10 @@ + android:resizeMode="none" +/> diff --git a/android/build.gradle b/android/build.gradle index 7b4ae292c..609bfedc5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -15,7 +15,7 @@ buildscript { // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. ndkVersion = "23.1.7779620" kotlin_version = '1.9.23' - kotlinVersion = '1.9.20' + kotlinVersion = '1.9.23' } repositories { google() diff --git a/components/TransactionListItem.tsx b/components/TransactionListItem.tsx index d700f6efb..b3a13be03 100644 --- a/components/TransactionListItem.tsx +++ b/components/TransactionListItem.tsx @@ -22,6 +22,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { DetailViewStackParamList } from '../navigation/DetailViewStackParamList'; import { useStorage } from '../hooks/context/useStorage'; import ToolTipMenu from './TooltipMenu'; +import { CommonToolTipActions } from '../typings/CommonToolTipActions'; interface TransactionListItemProps { itemPriceUnit: BitcoinUnit; @@ -255,17 +256,17 @@ export const TransactionListItem: React.FC = React.mem const onToolTipPress = useCallback( (id: any) => { - if (id === actionKeys.CopyAmount) { + if (id === CommonToolTipActions.CopyAmount.id) { handleOnCopyAmountTap(); - } else if (id === actionKeys.CopyNote) { + } else if (id === CommonToolTipActions.CopyNote.id) { handleOnCopyNote(); - } else if (id === actionKeys.OpenInBlockExplorer) { + } else if (id === CommonToolTipActions.OpenInBlockExplorer.id) { handleOnViewOnBlockExplorer(); - } else if (id === actionKeys.ExpandNote) { + } else if (id === CommonToolTipActions.ExpandNote.id) { handleOnExpandNote(); - } else if (id === actionKeys.CopyBlockExplorerLink) { + } else if (id === CommonToolTipActions.CopyBlockExplorerLink.id) { handleCopyOpenInBlockExplorerPress(); - } else if (id === actionKeys.CopyTXID) { + } else if (id === CommonToolTipActions.CopyTXID.id) { handleOnCopyTransactionID(); } }, @@ -282,51 +283,19 @@ export const TransactionListItem: React.FC = React.mem const actions: (Action | Action[])[] = []; if (rowTitle !== loc.lnd.expired) { - actions.push({ - id: actionKeys.CopyAmount, - text: loc.transactions.details_copy_amount, - icon: actionIcons.Clipboard, - }); + actions.push(CommonToolTipActions.CopyAmount); } if (subtitle) { - actions.push({ - id: actionKeys.CopyNote, - text: loc.transactions.details_copy_note, - icon: actionIcons.Clipboard, - }); + actions.push(CommonToolTipActions.CopyNote); } if (item.hash) { - actions.push( - { - id: actionKeys.CopyTXID, - text: loc.transactions.details_copy_txid, - icon: actionIcons.Clipboard, - }, - { - id: actionKeys.CopyBlockExplorerLink, - text: loc.transactions.details_copy_block_explorer_link, - icon: actionIcons.Clipboard, - }, - [ - { - id: actionKeys.OpenInBlockExplorer, - text: loc.transactions.details_show_in_block_explorer, - icon: actionIcons.Link, - }, - ], - ); + actions.push(CommonToolTipActions.CopyTXID, CommonToolTipActions.CopyBlockExplorerLink, [CommonToolTipActions.OpenInBlockExplorer]); } if (subtitle && subtitleNumberOfLines === 1) { - actions.push([ - { - id: actionKeys.ExpandNote, - text: loc.transactions.expand_note, - icon: actionIcons.Note, - }, - ]); + actions.push([CommonToolTipActions.ExpandNote]); } return actions as Action[] | Action[][]; @@ -363,30 +332,3 @@ export const TransactionListItem: React.FC = React.mem ); }); - -const actionKeys = { - CopyTXID: 'copyTX_ID', - CopyBlockExplorerLink: 'copy_blockExplorer', - ExpandNote: 'expandNote', - OpenInBlockExplorer: 'open_in_blockExplorer', - CopyAmount: 'copyAmount', - CopyNote: 'copyNote', -}; - -const actionIcons = { - Eye: { - iconValue: 'eye', - }, - EyeSlash: { - iconValue: 'eye.slash', - }, - Clipboard: { - iconValue: 'doc.on.doc', - }, - Link: { - iconValue: 'link', - }, - Note: { - iconValue: 'note.text', - }, -}; diff --git a/ios/BlueWallet.xcodeproj/project.pbxproj b/ios/BlueWallet.xcodeproj/project.pbxproj index 2f33593d4..b9edaf205 100644 --- a/ios/BlueWallet.xcodeproj/project.pbxproj +++ b/ios/BlueWallet.xcodeproj/project.pbxproj @@ -169,8 +169,19 @@ B4D0B2662C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */; }; B4D0B2682C1DED67006B6B1B /* ReceiveMethod.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */; }; B4EE583C226703320003363C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B40D4E35225841ED00428FCC /* Assets.xcassets */; }; + B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; }; + B4EFF73C2C3F6C5E0095D655 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4EFF73A2C3F6C5E0095D655 /* MockData.swift */; }; + B4EFF73D2C3F6C6C0095D655 /* MarketData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E82BCC371A00162242 /* MarketData.swift */; }; + B4EFF7412C3F6C960095D655 /* PriceWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA4BC255872E3009312A5 /* PriceWidget.swift */; }; + B4EFF7422C3F6C990095D655 /* PriceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D6CA5272558EC52009312A5 /* PriceView.swift */; }; + B4EFF7432C3F6F650095D655 /* Currency.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033C92BCC350A00162242 /* Currency.swift */; }; + B4EFF7442C3F6F6A0095D655 /* FiatUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D2AA8072568B8F40090B089 /* FiatUnit.swift */; }; + B4EFF7452C3F6FF30095D655 /* Placeholders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DEB4BFA254FBA0E00E9F9AA /* Placeholders.swift */; }; + B4EFF7462C3F6FF90095D655 /* WalletData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033E32BCC36FF00162242 /* WalletData.swift */; }; + B4EFF7472C3F70010095D655 /* LatestTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033DC2BCC36C300162242 /* LatestTransaction.swift */; }; + B4EFF7482C3F70090095D655 /* BitcoinUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = B44033BE2BCC32F800162242 /* BitcoinUnit.swift */; }; C59F90CE0D04D3E4BB39BC5D /* libPods-BlueWalletUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6F02C2F7CA3591E4E0B06EBA /* libPods-BlueWalletUITests.a */; }; - C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + C978A716948AB7DEC5B6F677 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -461,6 +472,7 @@ B4D0B2652C1DEB7F006B6B1B /* ReceiveInterfaceMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveInterfaceMode.swift; sourceTree = ""; }; B4D0B2672C1DED67006B6B1B /* ReceiveMethod.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiveMethod.swift; sourceTree = ""; }; B4D3235A177F4580BA52F2F9 /* libRNCSlider.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCSlider.a; sourceTree = ""; }; + B4EFF73A2C3F6C5E0095D655 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; B642AFB13483418CAB6FF25E /* libRCTQRCodeLocalImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTQRCodeLocalImage.a; sourceTree = ""; }; B68F8552DD4428F64B11DCFB /* Pods-BlueWallet.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BlueWallet.debug.xcconfig"; path = "Target Support Files/Pods-BlueWallet/Pods-BlueWallet.debug.xcconfig"; sourceTree = ""; }; B9D9B3A7B2CB4255876B67AF /* libz.tbd */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; @@ -485,7 +497,7 @@ files = ( 782F075B5DD048449E2DECE9 /* libz.tbd in Frameworks */, 764B49B1420D4AEB8109BF62 /* libsqlite3.0.tbd in Frameworks */, - C978A716948AB7DEC5B6F677 /* BuildFile in Frameworks */, + C978A716948AB7DEC5B6F677 /* (null) in Frameworks */, 773E382FE62E836172AAB98B /* libPods-BlueWallet.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -550,6 +562,7 @@ children = ( 00E356F01AD99517003FC87E /* Supporting Files */, B49038D82B8FBAD300A8164A /* BlueWalletUITest.swift */, + B4EFF73A2C3F6C5E0095D655 /* MockData.swift */, ); path = BlueWalletTests; sourceTree = ""; @@ -1627,6 +1640,7 @@ B44033DB2BCC369B00162242 /* Colors.swift in Sources */, B40D4E632258425500428FCC /* ReceiveInterfaceController.swift in Sources */, B43D0378225847C500FBAA95 /* WalletGradient.swift in Sources */, + B4EFF73B2C3F6C5E0095D655 /* MockData.swift in Sources */, B44033C02BCC32F800162242 /* BitcoinUnit.swift in Sources */, B44033E52BCC36FF00162242 /* WalletData.swift in Sources */, B44033EF2BCC374500162242 /* Numeric+abbreviated.swift in Sources */, @@ -1645,6 +1659,16 @@ buildActionMask = 2147483647; files = ( B49038D92B8FBAD300A8164A /* BlueWalletUITest.swift in Sources */, + B4EFF7472C3F70010095D655 /* LatestTransaction.swift in Sources */, + B4EFF7422C3F6C990095D655 /* PriceView.swift in Sources */, + B4EFF7482C3F70090095D655 /* BitcoinUnit.swift in Sources */, + B4EFF73D2C3F6C6C0095D655 /* MarketData.swift in Sources */, + B4EFF73C2C3F6C5E0095D655 /* MockData.swift in Sources */, + B4EFF7432C3F6F650095D655 /* Currency.swift in Sources */, + B4EFF7412C3F6C960095D655 /* PriceWidget.swift in Sources */, + B4EFF7462C3F6FF90095D655 /* WalletData.swift in Sources */, + B4EFF7452C3F6FF30095D655 /* Placeholders.swift in Sources */, + B4EFF7442C3F6F6A0095D655 /* FiatUnit.swift in Sources */, B47B21EC2B2128B8001F6690 /* BlueWalletUITests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/BlueWalletTests/MockData.swift b/ios/BlueWalletTests/MockData.swift new file mode 100644 index 000000000..8460b865a --- /dev/null +++ b/ios/BlueWalletTests/MockData.swift @@ -0,0 +1,15 @@ +// +// MockData.swift +// BlueWallet +// +// Created by Marcos Rodriguez on 7/10/24. +// Copyright © 2024 BlueWallet. All rights reserved. +// + +import Foundation + +struct MockData { + static let currentMarketData = MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2023-01-01T00:00:00+00:00") + static let previousMarketData = MarketData(nextBlock: "", sats: "", price: "$9,000", rate: 9000, dateString: "2022-12-31T00:00:00+00:00") + static let noChangeMarketData = MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2023-01-01T00:00:00+00:00") +} diff --git a/ios/Widgets/Shared/Views/PriceView.swift b/ios/Widgets/Shared/Views/PriceView.swift index 6f24b1a8b..04502f5e2 100644 --- a/ios/Widgets/Shared/Views/PriceView.swift +++ b/ios/Widgets/Shared/Views/PriceView.swift @@ -15,9 +15,13 @@ struct PriceView: View { var body: some View { switch entry.family { case .accessoryInline, .accessoryCircular, .accessoryRectangular: - wrappedView(for: getView(for: entry.family), family: entry.family) + if #available(iOSApplicationExtension 16.0, *) { + wrappedView(for: getView(for: entry.family), family: entry.family) + } else { + getView(for: entry.family) + } default: - defaultView.background(Color.widgetBackground) + defaultView.background(Color(UIColor.systemBackground)) } } @@ -40,7 +44,7 @@ struct PriceView: View { ZStack { if family == .accessoryRectangular { AccessoryWidgetBackground() - .background(Color(.systemBackground)) + .background(Color(UIColor.systemBackground)) .clipShape(RoundedRectangle(cornerRadius: 10)) } else { AccessoryWidgetBackground() @@ -118,26 +122,26 @@ struct PriceView: View { } .padding(.all, 8) .frame(maxWidth: .infinity, alignment: .leading) - .background(Color(.systemBackground)) + .background(Color(UIColor.systemBackground)) .clipShape(RoundedRectangle(cornerRadius: 10)) } private var defaultView: some View { VStack(alignment: .trailing, spacing: nil, content: { - Text("Last Updated").font(Font.system(size: 11, weight: .regular, design: .default)).foregroundColor(.textColorLightGray) + Text("Last Updated").font(Font.system(size: 11, weight: .regular)).foregroundColor(Color(UIColor.lightGray)) HStack(alignment: .lastTextBaseline, spacing: nil, content: { - Text(entry.currentMarketData?.formattedDate ?? "").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01).transition(.opacity) + Text(entry.currentMarketData?.formattedDate ?? "").lineLimit(1).foregroundColor(.primary).font(Font.system(size: 13, weight: .regular)).minimumScaleFactor(0.01).transition(.opacity) }) Spacer() VStack(alignment: .trailing, spacing: 16, content: { HStack(alignment: .lastTextBaseline, spacing: nil, content: { - Text(entry.currentMarketData?.price ?? "").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:28, weight: .bold, design: .default)).minimumScaleFactor(0.01).transition(.opacity) + Text(entry.currentMarketData?.price ?? "").lineLimit(1).foregroundColor(.primary).font(Font.system(size: 28, weight: .bold)).minimumScaleFactor(0.01).transition(.opacity) }) if let previousMarketDataPrice = entry.previousMarketData?.price, let currentMarketDataRate = entry.currentMarketData?.rate, let previousMarketDataRate = entry.previousMarketData?.rate, previousMarketDataRate > 0, currentMarketDataRate != previousMarketDataRate { HStack(alignment: .lastTextBaseline, spacing: nil, content: { - Image(systemName: currentMarketDataRate > previousMarketDataRate ? "arrow.up" : "arrow.down") - Text("from").lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01) - Text(previousMarketDataPrice).lineLimit(1).foregroundColor(.textColor).font(Font.system(size:13, weight: .regular, design: .default)).minimumScaleFactor(0.01) + Image(systemName: currentMarketDataRate > previousMarketDataRate ? "arrow.up" : "arrow.down") + Text("from").lineLimit(1).foregroundColor(.primary).font(Font.system(size: 13, weight: .regular)).minimumScaleFactor(0.01) + Text(previousMarketDataPrice).lineLimit(1).foregroundColor(.primary).font(Font.system(size: 13, weight: .regular)).minimumScaleFactor(0.01) }).transition(.slide) } }) @@ -178,7 +182,7 @@ struct PriceView_Previews: PreviewProvider { .previewContext(WidgetPreviewContext(family: .accessoryInline)) PriceView(entry: PriceWidgetEntry(date: Date(), family: .accessoryRectangular, currentMarketData: MarketData(nextBlock: "", sats: "", price: "$10,000", rate: 10000, dateString: "2019-09-18T17:27:00+00:00"), previousMarketData: emptyMarketData)) .previewContext(WidgetPreviewContext(family: .accessoryRectangular)) - } - } - } - } + } + } + } +} diff --git a/package-lock.json b/package-lock.json index e5deece08..4a9248a61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "coinselect": "3.1.13", "crypto-js": "4.2.0", "dayjs": "1.11.11", - "detox": "20.25.0", + "detox": "20.25.1", "ecpair": "2.0.1", "ecurve": "1.0.6", "electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc", @@ -9650,9 +9650,9 @@ } }, "node_modules/detox": { - "version": "20.25.0", - "resolved": "https://registry.npmjs.org/detox/-/detox-20.25.0.tgz", - "integrity": "sha512-PI4JR7XZQM/VPLLscf07WHdjHu/n9zRRKajEFrgIIpaZK2txDJZnv1rqaIC+4yFGEvCVSbxYQFrjjihLERDWrA==", + "version": "20.25.1", + "resolved": "https://registry.npmjs.org/detox/-/detox-20.25.1.tgz", + "integrity": "sha512-l+VhhtwfIR8DR4Htvo3KUIx1VPQkYanwjxsKUlXrOM60e47GB45U87la3VYVpFEvUQ5liD46tWWDkY0fkhoOOw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -29881,9 +29881,9 @@ "dev": true }, "detox": { - "version": "20.25.0", - "resolved": "https://registry.npmjs.org/detox/-/detox-20.25.0.tgz", - "integrity": "sha512-PI4JR7XZQM/VPLLscf07WHdjHu/n9zRRKajEFrgIIpaZK2txDJZnv1rqaIC+4yFGEvCVSbxYQFrjjihLERDWrA==", + "version": "20.25.1", + "resolved": "https://registry.npmjs.org/detox/-/detox-20.25.1.tgz", + "integrity": "sha512-l+VhhtwfIR8DR4Htvo3KUIx1VPQkYanwjxsKUlXrOM60e47GB45U87la3VYVpFEvUQ5liD46tWWDkY0fkhoOOw==", "requires": { "ajv": "^8.6.3", "bunyan": "^1.8.12", diff --git a/package.json b/package.json index a9ec3153f..21cc5c967 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "e2e:release-build": "detox build -c android.release", "e2e:release-test": "detox test -c android.release", "tslint": "tsc", - "lint": " npm run tslint && node scripts/find-unused-loc.js && eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components navigation", + "lint": " npm run tslint && node scripts/find-unused-loc.js && eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components navigation typings", "lint:fix": "npm run lint -- --fix", "lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep -E '\\.js|\\.ts' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0", "unit": "jest -b -i tests/unit/*" @@ -130,7 +130,7 @@ "coinselect": "3.1.13", "crypto-js": "4.2.0", "dayjs": "1.11.11", - "detox": "20.25.0", + "detox": "20.25.1", "ecpair": "2.0.1", "ecurve": "1.0.6", "electrum-client": "github:BlueWallet/rn-electrum-client#1bfe3cc", diff --git a/scripts/find-unused-loc.js b/scripts/find-unused-loc.js index 1ad5fa54d..6a1de9eae 100644 --- a/scripts/find-unused-loc.js +++ b/scripts/find-unused-loc.js @@ -2,7 +2,7 @@ const fs = require('fs'); const path = require('path'); const mainLocFile = './loc/en.json'; -const dirsToInterate = ['components', 'screen', 'blue_modules', 'class', 'hooks', 'helpers', 'navigation']; +const dirsToInterate = ['components', 'screen', 'blue_modules', 'class', 'hooks', 'helpers', 'navigation', 'typings']; const addFiles = ['BlueComponents.js', 'App.tsx', 'navigation/index.tsx']; const allowedLocPrefixes = ['loc.lnurl_auth', 'loc.units']; diff --git a/typings/CommonToolTipActions.ts b/typings/CommonToolTipActions.ts new file mode 100644 index 000000000..d1f816ddc --- /dev/null +++ b/typings/CommonToolTipActions.ts @@ -0,0 +1,61 @@ +import loc from '../loc'; + +const keys = { + CopyTXID: 'copyTX_ID', + CopyBlockExplorerLink: 'copy_blockExplorer', + ExpandNote: 'expandNote', + OpenInBlockExplorer: 'open_in_blockExplorer', + CopyAmount: 'copyAmount', + CopyNote: 'copyNote', +}; + +const icons = { + Eye: { + iconValue: 'eye', + }, + EyeSlash: { + iconValue: 'eye.slash', + }, + Clipboard: { + iconValue: 'doc.on.doc', + }, + Link: { + iconValue: 'link', + }, + Note: { + iconValue: 'note.text', + }, +}; + +export const CommonToolTipActions = { + CopyTXID: { + id: keys.CopyTXID, + text: loc.transactions.details_copy_txid, + icon: icons.Clipboard, + }, + CopyBlockExplorerLink: { + id: keys.CopyBlockExplorerLink, + text: loc.transactions.details_copy_block_explorer_link, + icon: icons.Clipboard, + }, + OpenInBlockExplorer: { + id: keys.OpenInBlockExplorer, + text: loc.transactions.details_show_in_block_explorer, + icon: icons.Link, + }, + ExpandNote: { + id: keys.ExpandNote, + text: loc.transactions.expand_note, + icon: icons.Note, + }, + CopyAmount: { + id: keys.CopyAmount, + text: loc.transactions.details_copy_amount, + icon: icons.Clipboard, + }, + CopyNote: { + id: keys.CopyNote, + text: loc.transactions.details_copy_note, + icon: icons.Clipboard, + }, +};