Merge branch 'master' into modal

This commit is contained in:
Marcos Rodriguez Velez 2024-06-30 17:03:24 -04:00
commit dcb940b0c7
No known key found for this signature in database
GPG Key ID: 6030B2F48CCE86D7
13 changed files with 803 additions and 212 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

@ -3,7 +3,6 @@
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

View File

@ -19,10 +19,10 @@
<uses-permission android:name="android.permission.ACTION_OPEN_DOCUMENT" />
<uses-permission android:name="android.permission.ACTION_GET_CONTENT" />
<uses-permission android:name="android.permission.ACTION_CREATE_DOCUMENT" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.ACTION_SCREEN_OFF"/>
<uses-permission android:name="android.permission.ACTION_SCREEN_ON"/>
<application
android:name=".MainApplication"
android:label="@string/app_name"
@ -61,19 +61,18 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name=".BitcoinPriceWidget"
android:exported="true"
android:label="Bitcoin Price Widget">
<receiver android:name=".BitcoinPriceWidget" android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="android.intent.action.SCREEN_ON"/>
<action android:name="android.intent.action.SCREEN_OFF"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/bitcoin_price_widget_info" />
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,189 +1,125 @@
package io.bluewallet.bluewallet;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Application;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.IntentFilter;
import android.os.PowerManager;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
import android.widget.Toast;
import java.lang.ref.WeakReference;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
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 ExecutorService executorService = Executors.newSingleThreadExecutor();
private static final String ACTION_UPDATE = "io.bluewallet.bluewallet.UPDATE_WIDGET";
private static final long UPDATE_INTERVAL_MINUTES = 15; // Update interval in minutes
private static final int MAX_RETRIES = 3;
private static PowerManager.WakeLock wakeLock;
private static int retryCount = 0;
private static boolean isScreenOn = true;
@Override
public void onEnabled(Context context) {
super.onEnabled(context);
scheduleNextUpdate(context);
registerScreenReceiver(context);
schedulePeriodicUpdates(context);
}
@Override
public void onDisabled(Context context) {
super.onDisabled(context);
cancelUpdate(context);
executorService.shutdown();
unregisterScreenReceiver(context);
WorkManager.getInstance(context).cancelUniqueWork("UpdateWidgetWork");
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
Log.d(TAG, "Updating widget ID: " + appWidgetId);
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);
appWidgetManager.updateAppWidget(appWidgetId, views);
executorService.execute(new FetchBitcoinPriceTask(context, appWidgetManager, appWidgetId));
}
scheduleNextUpdate(context);
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (ACTION_UPDATE.equals(intent.getAction())) {
Log.d(TAG, "Received update action");
ComponentName widget = new ComponentName(context, BitcoinPriceWidget.class);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(widget);
onUpdate(context, appWidgetManager, appWidgetIds);
super.onUpdate(context, appWidgetManager, appWidgetIds);
if (isScreenOn) {
scheduleWork(context);
}
}
private void scheduleNextUpdate(Context context) {
Intent intent = new Intent(context, BitcoinPriceWidget.class);
intent.setAction(ACTION_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
private void schedulePeriodicUpdates(Context context) {
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(WidgetUpdateWorker.class,
UPDATE_INTERVAL_MINUTES, TimeUnit.MINUTES)
.setInitialDelay(UPDATE_INTERVAL_MINUTES, TimeUnit.MINUTES)
.build();
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
try {
alarmManager.setExact(AlarmManager.RTC, System.currentTimeMillis() + 300000, pendingIntent);
} catch (SecurityException e) {
Log.e(TAG, "Permission not granted for setting exact alarms", e);
}
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"UpdateWidgetWork",
ExistingPeriodicWorkPolicy.REPLACE,
workRequest
);
}
private void cancelUpdate(Context context) {
Intent intent = new Intent(context, BitcoinPriceWidget.class);
intent.setAction(ACTION_UPDATE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
private void registerScreenReceiver(Context context) {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.getApplicationContext().registerReceiver(screenReceiver, filter);
}
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;
}
private void unregisterScreenReceiver(Context context) {
context.getApplicationContext().unregisterReceiver(screenReceiver);
}
private final BroadcastReceiver screenReceiver = new BroadcastReceiver() {
@Override
public void run() {
Context context = contextRef.get();
if (context == null) return;
Log.d(TAG, "Starting to fetch Bitcoin price...");
String price = MarketAPI.fetchPrice("USD"); // Using hardcoded "USD" for now
if (price != null) {
updateWidgetWithPrice(context, price);
} else {
handleError(context);
public void onReceive(Context context, Intent intent) {
if (intent.getAction() != null) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm != null) {
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_ON:
isScreenOn = true;
Log.d(TAG, "Screen ON");
acquireWakeLock(context);
scheduleWork(context);
break;
case Intent.ACTION_SCREEN_OFF:
isScreenOn = false;
Log.d(TAG, "Screen OFF");
releaseWakeLock();
break;
}
}
}
}
};
private void updateWidgetWithPrice(Context context, String price) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
String prevPrice = prefs.getString(PREF_PREFIX_KEY + appWidgetId, null);
SharedPreferences.Editor editor = prefs.edit();
Log.d(TAG, "Fetch completed with price: " + price);
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.US);
views.setTextViewText(R.id.price_value, currencyFormat.format(Double.parseDouble(price)));
if (prevPrice != null) {
double previousPrice = Double.parseDouble(prevPrice);
double currentPrice = Double.parseDouble(price);
if (currentPrice > previousPrice) {
views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_up_float);
} else if (currentPrice < previousPrice) {
views.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_down_float);
} else {
views.setImageViewResource(R.id.price_arrow, 0);
}
if (currentPrice != previousPrice) {
views.setTextViewText(R.id.previous_price, "from " + currencyFormat.format(previousPrice));
views.setViewVisibility(R.id.price_arrow, View.VISIBLE);
views.setViewVisibility(R.id.previous_price, View.VISIBLE);
} else {
views.setTextViewText(R.id.previous_price, "");
views.setViewVisibility(R.id.price_arrow, 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, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
private void acquireWakeLock(Context context) {
if (wakeLock == null) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (pm != null) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wakeLock.acquire(10 * 60 * 1000L /*10 minutes*/);
}
editor.putString(PREF_PREFIX_KEY + appWidgetId, price);
editor.apply();
String currentTime = new SimpleDateFormat("hh:mm a", Locale.getDefault()).format(new Date());
views.setTextViewText(R.id.last_updated, "Last Updated");
views.setTextViewText(R.id.last_updated_time, currentTime);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
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, "");
views.setViewVisibility(R.id.price_arrow, View.GONE);
views.setViewVisibility(R.id.previous_price, View.GONE);
Toast.makeText(context, "Failed to fetch Bitcoin price", Toast.LENGTH_SHORT).show();
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
}
private void releaseWakeLock() {
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
wakeLock = null;
}
}
private void scheduleWork(Context context) {
Log.d(TAG, "Scheduling work for widget update");
WorkManager.getInstance(context).enqueue(WidgetUpdateWorker.createWorkRequest());
}
}

View File

@ -16,64 +16,70 @@ import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import java.util.List;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;
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);
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);
// 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);
}
}
}

View File

@ -1,5 +1,8 @@
package io.bluewallet.bluewallet;
import android.content.Context;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
@ -10,6 +13,7 @@ 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" +
@ -20,7 +24,7 @@ public class MarketAPI {
" }\n" +
"}";
public static String fetchPrice(String currency) {
public static String fetchPrice(Context context, String currency) {
try {
JSONObject json = new JSONObject(HARD_CODED_JSON);
JSONObject currencyInfo = json.getJSONObject(currency);
@ -28,6 +32,7 @@ public class MarketAPI {
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();

View File

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

View File

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

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

@ -14,11 +14,11 @@
android:gravity="end">
<TextView
android:id="@+id/last_updated"
android:id="@+id/last_updated_label"
style="@style/WidgetTextSecondary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:text="Last Updated:"
android:textSize="12sp"
android:textStyle="bold"
android:layout_marginEnd="8dp"/>
@ -65,6 +65,16 @@
android:layout_marginEnd="4dp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/previous_price_label"
style="@style/WidgetTextPrimary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="From:"
android:textSize="12sp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"/>
<TextView
android:id="@+id/previous_price"
style="@style/WidgetTextPrimary"
@ -76,4 +86,11 @@
android:layout_marginBottom="8dp"/>
</LinearLayout>
</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>

View File

@ -3,7 +3,7 @@
android:initialLayout="@layout/widget_layout"
android:minWidth="160dp"
android:minHeight="140dp"
android:updatePeriodMillis="300000"
android:updatePeriodMillis="0"
android:widgetCategory="home_screen"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal|vertical"

View File

@ -1,7 +1,7 @@
#Tue Jul 21 23:04:55 CDT 2020
#Wed Jun 26 20:30:20 AST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
networkTimeout=10000