This commit is contained in:
Marcos Rodriguez Velez 2024-06-29 17:42:14 -04:00
parent f76bec5753
commit 29aa934f8d
No known key found for this signature in database
GPG key ID: 6030B2F48CCE86D7
12 changed files with 805 additions and 408 deletions

View file

@ -84,6 +84,12 @@ android {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
sourceSets {
main {
assets.srcDirs = ['src/main/assets', 'src/main/res/assets']
}
}
buildTypes {
release {
// Caution! In production, you need to generate your own keystore file.
@ -95,6 +101,13 @@ android {
}
}
task copyFiatUnits(type: Copy) {
from '../../models/fiatUnits.json'
into 'src/main/assets'
}
preBuild.dependsOn(copyFiatUnits)
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
@ -110,6 +123,7 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.work:work-runtime:2.7.1"
}
apply plugin: 'com.google.gms.google-services' // Google Services plugin

View file

@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.ACTION_GET_CONTENT" />
<uses-permission android:name="android.permission.ACTION_CREATE_DOCUMENT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
@ -58,7 +59,8 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
<receiver
android:name=".BitcoinPriceWidget"
android:exported="true"
android:label="Bitcoin Price Widget">
@ -70,7 +72,6 @@
android:resource="@xml/bitcoin_price_widget_info" />
</receiver>
<service
android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
android:exported="false">

View file

@ -0,0 +1,408 @@
{
"USD": {
"endPointKey": "USD",
"locale": "en-US",
"source": "Kraken",
"symbol": "$",
"country": "United States (US Dollar)"
},
"AED": {
"endPointKey": "AED",
"locale": "ar-AE",
"source": "CoinGecko",
"symbol": "د.إ.",
"country": "United Arab Emirates (UAE Dirham)"
},
"ANG": {
"endPointKey": "ANG",
"locale": "en-SX",
"source": "CoinDesk",
"symbol": "ƒ",
"country": "Sint Maarten (Netherlands Antillean Guilder)"
},
"ARS": {
"endPointKey": "ARS",
"locale": "es-AR",
"source": "Yadio",
"symbol": "$",
"country": "Argentina (Argentine Peso)"
},
"AUD": {
"endPointKey": "AUD",
"locale": "en-AU",
"source": "CoinGecko",
"symbol": "$",
"country": "Australia (Australian Dollar)"
},
"AWG": {
"endPointKey": "AWG",
"locale": "nl-AW",
"source": "CoinDesk",
"symbol": "ƒ",
"country": "Aruba (Aruban Florin)"
},
"BHD": {
"endPointKey": "BHD",
"locale": "ar-BH",
"source": "CoinGecko",
"symbol": "د.ب.",
"country": "Bahrain (Bahraini Dinar)"
},
"BRL": {
"endPointKey": "BRL",
"locale": "pt-BR",
"source": "CoinGecko",
"symbol": "R$",
"country": "Brazil (Brazilian Real)"
},
"CAD": {
"endPointKey": "CAD",
"locale": "en-CA",
"source": "CoinGecko",
"symbol": "$",
"country": "Canada (Canadian Dollar)"
},
"CHF": {
"endPointKey": "CHF",
"locale": "de-CH",
"source": "CoinGecko",
"symbol": "CHF",
"country": "Switzerland (Swiss Franc)"
},
"CLP": {
"endPointKey": "CLP",
"locale": "es-CL",
"source": "Yadio",
"symbol": "$",
"country": "Chile (Chilean Peso)"
},
"CNY": {
"endPointKey": "CNY",
"locale": "zh-CN",
"source": "Coinbase",
"symbol": "¥",
"country": "China (Chinese Yuan)"
},
"COP": {
"endPointKey": "COP",
"locale": "es-CO",
"source": "CoinDesk",
"symbol": "$",
"country": "Colombia (Colombian Peso)"
},
"CZK": {
"endPointKey": "CZK",
"locale": "cs-CZ",
"source": "CoinGecko",
"symbol": "Kč",
"country": "Czech Republic (Czech Koruna)"
},
"DKK": {
"endPointKey": "DKK",
"locale": "da-DK",
"source": "CoinGecko",
"symbol": "kr",
"country": "Denmark (Danish Krone)"
},
"EUR": {
"endPointKey": "EUR",
"locale": "en-IE",
"source": "Kraken",
"symbol": "€",
"country": "European Union (Euro)"
},
"GBP": {
"endPointKey": "GBP",
"locale": "en-GB",
"source": "Kraken",
"symbol": "£",
"country": "United Kingdom (British Pound)"
},
"HRK": {
"endPointKey": "HRK",
"locale": "hr-HR",
"source": "CoinDesk",
"symbol": "HRK",
"country": "Croatia (Croatian Kuna)"
},
"HUF": {
"endPointKey": "HUF",
"locale": "hu-HU",
"source": "CoinGecko",
"symbol": "Ft",
"country": "Hungary (Hungarian Forint)"
},
"IDR": {
"endPointKey": "IDR",
"locale": "id-ID",
"source": "CoinGecko",
"symbol": "Rp",
"country": "Indonesia (Indonesian Rupiah)"
},
"ILS": {
"endPointKey": "ILS",
"locale": "he-IL",
"source": "CoinGecko",
"symbol": "₪",
"country": "Israel (Israeli New Shekel)"
},
"INR": {
"endPointKey": "INR",
"locale": "hi-IN",
"source": "wazirx",
"symbol": "₹",
"country": "India (Indian Rupee)"
},
"IRR": {
"endPointKey": "IRR",
"locale": "fa-IR",
"source": "Exir",
"symbol": "﷼",
"country": "Iran (Iranian Rial)"
},
"IRT": {
"endPointKey": "IRT",
"locale": "fa-IR",
"source": "Exir",
"symbol": "تومان",
"country": "Iran (Iranian Toman)"
},
"ISK": {
"endPointKey": "ISK",
"locale": "is-IS",
"source": "CoinDesk",
"symbol": "kr",
"country": "Iceland (Icelandic Króna)"
},
"JPY": {
"endPointKey": "JPY",
"locale": "ja-JP",
"source": "CoinGecko",
"symbol": "¥",
"country": "Japan (Japanese Yen)"
},
"KES": {
"endPointKey": "KES",
"locale": "en-KE",
"source": "CoinDesk",
"symbol": "Ksh",
"country": "Kenya (Kenyan Shilling)"
},
"KRW": {
"endPointKey": "KRW",
"locale": "ko-KR",
"source": "CoinGecko",
"symbol": "₩",
"country": "South Korea (South Korean Won)"
},
"KWD": {
"endPointKey": "KWD",
"locale": "ar-KW",
"source": "CoinGecko",
"symbol": "د.ك.",
"country": "Kuwait (Kuwaiti Dinar)"
},
"LBP": {
"endPointKey": "LBP",
"locale": "ar-LB",
"source": "YadioConvert",
"symbol": "ل.ل.",
"country": "Lebanon (Lebanese Pound)"
},
"LKR": {
"endPointKey": "LKR",
"locale": "si-LK",
"source": "CoinGecko",
"symbol": "රු.",
"country": "Sri Lanka (Sri Lankan Rupee)"
},
"MXN": {
"endPointKey": "MXN",
"locale": "es-MX",
"source": "CoinGecko",
"symbol": "$",
"country": "Mexico (Mexican Peso)"
},
"MYR": {
"endPointKey": "MYR",
"locale": "ms-MY",
"source": "CoinGecko",
"symbol": "RM",
"country": "Malaysia (Malaysian Ringgit)"
},
"MZN": {
"endPointKey": "MZN",
"locale": "seh-MZ",
"source": "CoinDesk",
"symbol": "MTn",
"country": "Mozambique (Mozambican Metical)"
},
"NGN": {
"endPointKey": "NGN",
"locale": "en-NG",
"source": "CoinGecko",
"symbol": "₦",
"country": "Nigeria (Nigerian Naira)"
},
"NOK": {
"endPointKey": "NOK",
"locale": "nb-NO",
"source": "CoinGecko",
"symbol": "kr",
"country": "Norway (Norwegian Krone)"
},
"NZD": {
"endPointKey": "NZD",
"locale": "en-NZ",
"source": "CoinGecko",
"symbol": "$",
"country": "New Zealand (New Zealand Dollar)"
},
"OMR": {
"endPointKey": "OMR",
"locale": "ar-OM",
"source": "CoinDesk",
"symbol": "ر.ع.",
"country": "Oman (Omani Rial)"
},
"PHP": {
"endPointKey": "PHP",
"locale": "en-PH",
"source": "CoinGecko",
"symbol": "₱",
"country": "Philippines (Philippine Peso)"
},
"PLN": {
"endPointKey": "PLN",
"locale": "pl-PL",
"source": "CoinGecko",
"symbol": "zł",
"country": "Poland (Polish Zloty)"
},
"QAR": {
"endPointKey": "QAR",
"locale": "ar-QA",
"source": "CoinDesk",
"symbol": "ر.ق.",
"country": "Qatar (Qatari Riyal)"
},
"RON": {
"endPointKey": "RON",
"locale": "ro-RO",
"source": "BNR",
"symbol": "lei",
"country": "Romania (Romanian Leu)"
},
"RUB": {
"endPointKey": "RUB",
"locale": "ru-RU",
"source": "CoinGecko",
"symbol": "₽",
"country": "Russia (Russian Ruble)"
},
"SAR": {
"endPointKey": "SAR",
"locale": "ar-SA",
"source": "CoinGecko",
"symbol": "ر.س.",
"country": "Saudi Arabia (Saudi Riyal)"
},
"SEK": {
"endPointKey": "SEK",
"locale": "sv-SE",
"source": "CoinGecko",
"symbol": "kr",
"country": "Sweden (Swedish Krona)"
},
"SGD": {
"endPointKey": "SGD",
"locale": "zh-SG",
"source": "CoinGecko",
"symbol": "S$",
"country": "Singapore (Singapore Dollar)"
},
"THB": {
"endPointKey": "THB",
"locale": "th-TH",
"source": "CoinGecko",
"symbol": "฿",
"country": "Thailand (Thai Baht)"
},
"TRY": {
"endPointKey": "TRY",
"locale": "tr-TR",
"source": "CoinGecko",
"symbol": "₺",
"country": "Turkey (Turkish Lira)"
},
"TWD": {
"endPointKey": "TWD",
"locale": "zh-Hant-TW",
"source": "CoinGecko",
"symbol": "NT$",
"country": "Taiwan (New Taiwan Dollar)"
},
"TZS": {
"endPointKey": "TZS",
"locale": "en-TZ",
"source": "CoinDesk",
"symbol": "TSh",
"country": "Tanzania (Tanzanian Shilling)"
},
"UAH": {
"endPointKey": "UAH",
"locale": "uk-UA",
"source": "CoinGecko",
"symbol": "₴",
"country": "Ukraine (Ukrainian Hryvnia)"
},
"UGX": {
"endPointKey": "UGX",
"locale": "en-UG",
"source": "CoinDesk",
"symbol": "USh",
"country": "Uganda (Ugandan Shilling)"
},
"UYU": {
"endPointKey": "UYU",
"locale": "es-UY",
"source": "CoinDesk",
"symbol": "$",
"country": "Uruguay (Uruguayan Peso)"
},
"VEF": {
"endPointKey": "VEF",
"locale": "es-VE",
"source": "CoinGecko",
"symbol": "Bs.",
"country": "Venezuela (Venezuelan Bolívar Fuerte)"
},
"VES": {
"endPointKey": "VES",
"locale": "es-VE",
"source": "Yadio",
"symbol": "Bs.",
"country": "Venezuela (Venezuelan Bolívar Soberano)"
},
"XAF": {
"endPointKey": "XAF",
"locale": "fr-CF",
"source": "CoinDesk",
"symbol": "Fr",
"country": "Central African Republic (Central African Franc)"
},
"ZAR": {
"endPointKey": "ZAR",
"locale": "en-ZA",
"source": "CoinGecko",
"symbol": "R",
"country": "South Africa (South African Rand)"
},
"GHS": {
"endPointKey": "GHS",
"locale": "en-GH",
"source": "CoinDesk",
"symbol": "₵",
"country": "Ghana (Ghanaian Cedi)"
}
}

View file

@ -1,205 +1,78 @@
package io.bluewallet.bluewallet;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import androidx.annotation.NonNull;
import androidx.work.Constraints;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class BitcoinPriceWidget extends AppWidgetProvider {
private static final String TAG = "BitcoinPriceWidget";
private static final String ACTION_UPDATE = "io.bluewallet.bluewallet.UPDATE";
private static final String PREFS_NAME = "BitcoinPriceWidgetPrefs";
private static final String PREF_PREFIX_KEY = "appwidget_";
private static final int UPDATE_INTERVAL_MINUTES = 10; // Adjustable interval in minutes
private static ExecutorService executorService;
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
initializeExecutorService();
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
if (executorService != null) {
executorService.shutdown();
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
initializeExecutorService();
if (appWidgetIds.length > 0) {
for (int appWidgetId : appWidgetIds) {
Log.d(TAG, "Updating widget ID: " + appWidgetId);
Log.d(TAG, "Updating widget");
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
// Set up the pending intent to open the app when the widget is clicked
Intent launchAppIntent = new Intent(context, MainActivity.class);
PendingIntent launchAppPendingIntent = PendingIntent.getActivity(context, 0, launchAppIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
views.setOnClickPendingIntent(R.id.widget_layout, launchAppPendingIntent);
// Set the loading indicator visible initially
views.setViewVisibility(R.id.loading_indicator, View.VISIBLE);
views.setViewVisibility(R.id.price_value, View.GONE);
views.setViewVisibility(R.id.last_updated, View.GONE);
views.setViewVisibility(R.id.last_updated_time, View.GONE);
appWidgetManager.updateAppWidget(appWidgetId, views);
executorService.execute(new FetchBitcoinPriceTask(context, appWidgetManager, appWidgetId));
}
scheduleNextUpdate(context);
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
WidgetUpdateWorker.scheduleWork(context); // Schedule to run periodically
}
private void initializeExecutorService() {
if (executorService == null || executorService.isShutdown()) {
executorService = Executors.newSingleThreadExecutor();
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
String currentPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_current_price", "N/A");
String currentTime = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_current_time", "N/A");
String previousPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_previous_price", "N/A");
String previousTime = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_previous_time", "N/A");
private void scheduleNextUpdate(Context context) {
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(UpdateWidgetWorker.class)
.setInitialDelay(UPDATE_INTERVAL_MINUTES, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(context).enqueue(workRequest);
}
private static class FetchBitcoinPriceTask implements Runnable {
private final WeakReference<Context> contextRef;
private final AppWidgetManager appWidgetManager;
private final int appWidgetId;
FetchBitcoinPriceTask(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
this.contextRef = new WeakReference<>(context);
this.appWidgetManager = appWidgetManager;
this.appWidgetId = appWidgetId;
}
@Override
public void run() {
Context context = contextRef.get();
if (context == null) return;
Log.d(TAG, "Starting to fetch Bitcoin price...");
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String preferredCurrency = prefs.getString("preferredCurrency", "USD");
String price = MarketAPI.fetchPrice(context, preferredCurrency);
if (price != null) {
updateWidgetWithPrice(context, price);
} else {
handleError(context);
}
}
private void updateWidgetWithPrice(Context context, String price) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
// Fetch current and previous data
String prevPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_prev_price", "N/A");
String prevTime = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_prev_time", "N/A");
String currentPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_current_price", null);
String currentTime = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_current_time", null);
SharedPreferences.Editor editor = prefs.edit();
String newTime = new SimpleDateFormat("hh:mm a", Locale.getDefault()).format(new Date());
Log.d(TAG, "Fetch completed with price: " + price + " at " + newTime + ". Previous price: " + prevPrice + " at " + prevTime);
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.US);
views.setTextViewText(R.id.price_value, currencyFormat.format(Double.parseDouble(price)));
if (currentPrice != null) {
double previousPrice = Double.parseDouble(currentPrice);
double newPrice = Double.parseDouble(price);
if (newPrice > previousPrice) {
views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_up_float);
} else if (newPrice < previousPrice) {
views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_down_float);
} else {
views.setImageViewResource(R.id.price_arrow, 0);
}
if (newPrice != previousPrice) {
views.setTextViewText(R.id.previous_price, "from " + currencyFormat.format(previousPrice));
views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE);
views.setViewVisibility(R.id.previous_price, View.VISIBLE);
} else {
views.setTextViewText(R.id.previous_price, "");
views.setViewVisibility(R.id.price_arrow_container, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
}
} else {
views.setImageViewResource(R.id.price_arrow, 0);
views.setTextViewText(R.id.previous_price, "");
views.setViewVisibility(R.id.price_arrow_container, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
}
// Shift current to previous
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_prev_price", currentPrice);
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_prev_time", currentTime);
// Set new current
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_current_price", price);
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_current_time", newTime);
editor.apply();
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
if (!currentPrice.equals("N/A")) {
views.setTextViewText(R.id.price_value, currentPrice);
views.setTextViewText(R.id.last_updated, "Last Updated");
views.setTextViewText(R.id.last_updated_time, newTime);
views.setTextViewText(R.id.last_updated_time, currentTime);
views.setViewVisibility(R.id.loading_indicator, View.GONE);
views.setViewVisibility(R.id.price_value, View.VISIBLE);
views.setViewVisibility(R.id.last_updated, View.VISIBLE);
views.setViewVisibility(R.id.last_updated_time, View.VISIBLE);
appWidgetManager.updateAppWidget(appWidgetId, views);
} else {
views.setTextViewText(R.id.price_value, "Loading...");
views.setViewVisibility(R.id.loading_indicator, View.VISIBLE);
}
private void handleError(Context context) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
String errorMessage = "Network Error";
Log.e(TAG, errorMessage);
views.setTextViewText(R.id.price_value, errorMessage);
views.setTextViewText(R.id.last_updated, "");
views.setTextViewText(R.id.last_updated_time, "");
views.setImageViewResource(R.id.price_arrow, 0);
views.setTextViewText(R.id.previous_price, "");
if (!previousPrice.equals("N/A") && !previousPrice.equals(currentPrice)) {
views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE);
views.setTextViewText(R.id.previous_price, "From " + previousPrice);
try {
double current = Double.parseDouble(currentPrice.replaceAll("[^\\d.]", ""));
double previous = Double.parseDouble(previousPrice.replaceAll("[^\\d.]", ""));
if (current > previous) {
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);
}
} catch (NumberFormatException e) {
Log.e(TAG, "Error parsing prices", e);
}
} else {
views.setViewVisibility(R.id.price_arrow_container, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
views.setViewVisibility(R.id.loading_indicator, View.GONE);
appWidgetManager.updateAppWidget(appWidgetId, views);
Log.e(TAG, "Failed to fetch Bitcoin price");
}
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
public static void savePreferences(Context context, int appWidgetId, String currentPrice, String currentTime, String previousPrice, String previousTime) {
SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_current_price", currentPrice);
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_current_time", currentTime);
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_previous_price", previousPrice);
prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_previous_time", previousTime);
prefs.apply();
}
}

View file

@ -1,5 +0,0 @@
package io.bluewallet.bluewallet;
public class Constants {
public static final int UPDATE_INTERVAL_MINUTES = 3;
}

View file

@ -23,64 +23,59 @@ import java.util.concurrent.TimeUnit;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
return packages;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected boolean isNewArchEnabled() {
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
@Override
protected Boolean isHermesEnabled() {
return BuildConfig.IS_HERMES_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) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
DefaultNewArchitectureEntryPoint.load();
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
SharedPreferences sharedPref = getApplicationContext().getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE);
// Retrieve the "donottrack" value. Default to "0" if not found.
String isDoNotTrackEnabled = sharedPref.getString("donottrack", "0");
@Override
public void onCreate() {
super.onCreate();
I18nUtil sharedI18nUtilInstance = I18nUtil.getInstance();
sharedI18nUtilInstance.allowRTL(getApplicationContext(), true);
SoLoader.init(this, /* native exopackage */ false);
// Check if do not track is not enabled and initialize Bugsnag if so
if (!isDoNotTrackEnabled.equals("1")) {
// Initialize Bugsnag or your error tracking here
Bugsnag.start(this);
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);
}
WidgetUpdateWorker.scheduleWork(this); // Schedule to run every 10 minutes
}
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(UpdateWidgetWorker.class, 10, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(this).enqueueUniquePeriodicWork("UpdateWidgetWork", ExistingPeriodicWorkPolicy.REPLACE, workRequest);
}
}

View file

@ -1,6 +1,7 @@
package io.bluewallet.bluewallet;
import android.content.Context;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
@ -12,24 +13,26 @@ 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 {
// Load the JSON file from assets
InputStreamReader is = new InputStreamReader(context.getAssets().open("fiatUnits.json"));
StringBuilder json = new StringBuilder();
int data = is.read();
while (data != -1) {
json.append((char) data);
data = is.read();
}
is.close();
JSONObject jsonObject = new JSONObject(json.toString());
JSONObject currencyInfo = jsonObject.getJSONObject(currency);
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();
@ -116,4 +119,4 @@ public class MarketAPI {
return null;
}
}
}
}

View file

@ -1,149 +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 androidx.work.WorkManager;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
public class UpdateWidgetWorker extends Worker {
private static final String TAG = "UpdateWidgetWorker";
private static final String PREFS_NAME = "BitcoinPriceWidgetPrefs";
private static final String PREF_PREFIX_KEY = "appwidget_";
private static final int UPDATE_INTERVAL_MINUTES = 10; // Adjustable interval in minutes
public UpdateWidgetWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Context context = getApplicationContext();
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
ComponentName widget = new ComponentName(context, BitcoinPriceWidget.class);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(widget);
for (int appWidgetId : appWidgetIds) {
fetchAndUpdatePrice(context, appWidgetManager, appWidgetId);
}
scheduleNextUpdate(context); // Schedule the next update
return Result.success();
}
private void fetchAndUpdatePrice(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
Log.d(TAG, "Fetching Bitcoin price...");
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
String preferredCurrency = prefs.getString("preferredCurrency", "USD");
String price = MarketAPI.fetchPrice(context, preferredCurrency);
if (price != null) {
updateWidgetWithPrice(context, appWidgetManager, appWidgetId, price);
} else {
handleError(context, appWidgetManager, appWidgetId);
}
}
private void updateWidgetWithPrice(Context context, AppWidgetManager appWidgetManager, int appWidgetId, String price) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
// Fetch current and previous data
String prevPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_prev_price", "N/A");
String prevTime = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_prev_time", "N/A");
String currentPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_current_price", null);
String currentTime = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_current_time", null);
SharedPreferences.Editor editor = prefs.edit();
String newTime = new SimpleDateFormat("hh:mm a", Locale.getDefault()).format(new Date());
Log.d(TAG, "Fetch completed with price: " + price + " at " + newTime + ". Previous price: " + prevPrice + " at " + prevTime);
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.US);
views.setTextViewText(R.id.price_value, currencyFormat.format(Double.parseDouble(price)));
if (currentPrice != null) {
double previousPrice = Double.parseDouble(currentPrice);
double newPrice = Double.parseDouble(price);
if (newPrice > previousPrice) {
views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_up_float);
} else if (newPrice < previousPrice) {
views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_down_float);
} else {
views.setImageViewResource(R.id.price_arrow, 0);
}
if (newPrice != previousPrice) {
views.setTextViewText(R.id.previous_price, "from " + currencyFormat.format(previousPrice));
views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE);
views.setViewVisibility(R.id.previous_price, View.VISIBLE);
} else {
views.setTextViewText(R.id.previous_price, "");
views.setViewVisibility(R.id.price_arrow_container, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
}
} else {
views.setImageViewResource(R.id.price_arrow, 0);
views.setTextViewText(R.id.previous_price, "");
views.setViewVisibility(R.id.price_arrow_container, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
}
// Shift current to previous
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_prev_price", currentPrice);
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_prev_time", currentTime);
// Set new current
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_current_price", price);
editor.putString(PREF_PREFIX_KEY + appWidgetId + "_current_time", newTime);
editor.apply();
views.setTextViewText(R.id.last_updated, "Last Updated");
views.setTextViewText(R.id.last_updated_time, newTime);
views.setViewVisibility(R.id.loading_indicator, View.GONE);
views.setViewVisibility(R.id.price_value, View.VISIBLE);
views.setViewVisibility(R.id.last_updated, View.VISIBLE);
views.setViewVisibility(R.id.last_updated_time, View.VISIBLE);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
private void handleError(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
String errorMessage = "Network Error";
Log.e(TAG, errorMessage);
views.setTextViewText(R.id.price_value, errorMessage);
views.setTextViewText(R.id.last_updated, "");
views.setTextViewText(R.id.last_updated_time, "");
views.setImageViewResource(R.id.price_arrow, 0);
views.setTextViewText(R.id.previous_price, "");
views.setViewVisibility(R.id.price_arrow_container, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
private void scheduleNextUpdate(Context context) {
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(UpdateWidgetWorker.class)
.setInitialDelay(UPDATE_INTERVAL_MINUTES, TimeUnit.MINUTES)
.build();
WorkManager.getInstance(context).enqueue(workRequest);
}
}

View file

@ -0,0 +1,81 @@
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.loading_indicator, View.GONE);
views.setViewVisibility(R.id.price_value, View.VISIBLE);
views.setViewVisibility(R.id.last_updated, 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");
}
}
}

View file

@ -0,0 +1,156 @@
package io.bluewallet.bluewallet;
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.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.text.DateFormat;
import java.text.NumberFormat;
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 WORK_NAME = "WidgetUpdateWorker";
private static final int UPDATE_INTERVAL_MINUTES = 10; // You can adjust this value as needed
public WidgetUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
Context context = getApplicationContext();
SharedPreferences prefs = context.getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE);
// Fetch the preferred currency from shared preferences
String preferredCurrency = prefs.getString("preferredCurrency", "USD");
Log.d(TAG, "Fetching price for currency: " + preferredCurrency);
// Fetch the price using MarketAPI
String price = MarketAPI.fetchPrice(context, preferredCurrency);
String previousPrice = prefs.getString("previous_price", "N/A");
String previousTime = prefs.getString("previous_time", "N/A");
Log.d(TAG, "Fetch completed with price: " + price + " at " + getCurrentTime() + ". Previous price: " + previousPrice + " at " + previousTime);
// Update the widget with the fetched price
if (price != null) {
SharedPreferences.Editor editor = prefs.edit();
editor.putString("previous_price", prefs.getString("current_price", "N/A"));
editor.putString("previous_time", prefs.getString("current_time", "N/A"));
editor.putString("current_price", price);
editor.putString("current_time", getCurrentTime());
editor.apply();
updateWidget(context, price, preferredCurrency, previousPrice);
} else {
handleError(context);
return Result.retry();
}
Log.d(TAG, "Next fetch will occur at: " + getNextFetchTime());
return Result.success();
}
private void updateWidget(Context context, String price, String currency, String previousPrice) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.getDefault());
currencyFormat.setCurrency(java.util.Currency.getInstance(currency));
views.setTextViewText(R.id.price_value, currencyFormat.format(Double.parseDouble(price)));
views.setTextViewText(R.id.last_updated, "Last Updated");
views.setTextViewText(R.id.last_updated_time, getCurrentTime());
SharedPreferences prefs = context.getSharedPreferences("group.io.bluewallet.bluewallet", Context.MODE_PRIVATE);
String currentStoredPrice = prefs.getString("current_price", null);
if (currentStoredPrice == null || currentStoredPrice.isEmpty()) {
views.setViewVisibility(R.id.loading_indicator, View.VISIBLE);
views.setViewVisibility(R.id.price_value, View.GONE);
views.setViewVisibility(R.id.last_updated, View.GONE);
views.setViewVisibility(R.id.last_updated_time, View.GONE);
} else {
views.setViewVisibility(R.id.loading_indicator, View.GONE);
views.setViewVisibility(R.id.price_value, View.VISIBLE);
views.setViewVisibility(R.id.last_updated, View.VISIBLE);
views.setViewVisibility(R.id.last_updated_time, View.VISIBLE);
}
if (!previousPrice.equals("N/A") && !previousPrice.equals(price)) {
views.setViewVisibility(R.id.price_arrow_container, View.VISIBLE);
views.setTextViewText(R.id.previous_price, "From " + currencyFormat.format(Double.parseDouble(previousPrice)));
double current = Double.parseDouble(price);
double previous = Double.parseDouble(previousPrice);
if (current > previous) {
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);
}
// Update the widget
WidgetUtils.updateAppWidget(context, views);
}
private void handleError(Context context) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
views.setTextViewText(R.id.price_value, "Failed to fetch");
views.setTextViewText(R.id.last_updated, "Last Updated");
views.setTextViewText(R.id.last_updated_time, getCurrentTime());
views.setViewVisibility(R.id.loading_indicator, View.GONE);
// Update the widget
WidgetUtils.updateAppWidget(context, views);
}
private String getCurrentTime() {
DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getApplicationContext());
return dateFormat.format(new Date());
}
private String getNextFetchTime() {
long currentTimeMillis = System.currentTimeMillis();
long nextFetchTimeMillis = currentTimeMillis + TimeUnit.MINUTES.toMillis(UPDATE_INTERVAL_MINUTES);
Date nextFetchDate = new Date(nextFetchTimeMillis);
DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getApplicationContext());
return dateFormat.format(nextFetchDate);
}
public static PeriodicWorkRequest createWorkRequest(int intervalMinutes) {
Constraints constraints = new Constraints.Builder()
.setRequiresBatteryNotLow(true)
.build();
return new PeriodicWorkRequest.Builder(WidgetUpdateWorker.class, intervalMinutes, TimeUnit.MINUTES)
.setConstraints(constraints)
.build();
}
public static void scheduleWork(Context context) {
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
WORK_NAME,
ExistingPeriodicWorkPolicy.REPLACE,
createWorkRequest(UPDATE_INTERVAL_MINUTES)
);
}
}

View file

@ -0,0 +1,19 @@
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);
}
}
}

View file

@ -12,7 +12,7 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="end">
<TextView
android:id="@+id/last_updated"
style="@style/WidgetTextSecondary"
@ -21,7 +21,7 @@
android:text=""
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginEnd="8dp" />
android:layout_marginEnd="8dp"/>
<TextView
android:id="@+id/last_updated_time"
@ -31,7 +31,7 @@
android:text=""
android:textSize="12sp"
android:layout_marginEnd="8dp"
android:layout_marginTop="2dp" />
android:layout_marginTop="2dp"/>
</LinearLayout>
<LinearLayout
@ -49,7 +49,7 @@
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp" />
android:layout_marginBottom="8dp"/>
<LinearLayout
android:id="@+id/price_arrow_container"
@ -63,7 +63,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginBottom="8dp" />
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/previous_price"
@ -73,14 +73,15 @@
android:text=""
android:textSize="12sp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp" />
android:layout_marginBottom="8dp"/>
</LinearLayout>
<ProgressBar
android:id="@+id/loading_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
<ProgressBar
android:id="@+id/loading_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone" />
</LinearLayout>