From b762aa62e0024d4156fcafeb47d75688babc90ee Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Tue, 25 Jun 2024 23:45:46 -0400 Subject: [PATCH 1/9] FIX: Android widget updated only once --- android/app/src/main/AndroidManifest.xml | 3 -- .../bluewallet/BitcoinPriceWidget.java | 41 ++++++++----------- .../res/xml/bitcoin_price_widget_info.xml | 2 +- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 751587def..99628da25 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,9 +19,6 @@ - - - Date: Wed, 26 Jun 2024 00:00:29 -0400 Subject: [PATCH 2/9] Update BitcoinPriceWidget.java --- .../main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java index 817e806ef..834317053 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java @@ -7,6 +7,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.icu.text.NumberFormat; import android.os.Handler; import android.util.Log; import android.view.View; From 8f3506379179c3c6aef2a5af1c9799cbe08fe80a Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Thu, 27 Jun 2024 21:21:12 -0400 Subject: [PATCH 3/9] Fix --- android/app/src/debug/AndroidManifest.xml | 1 - android/app/src/main/AndroidManifest.xml | 22 +-- .../bluewallet/BitcoinPriceWidget.java | 148 +++++++++-------- .../io/bluewallet/bluewallet/Constants.java | 5 + .../bluewallet/MainApplication.java | 7 + .../io/bluewallet/bluewallet/MarketAPI.java | 33 ++-- .../bluewallet/UpdateWidgetWorker.java | 149 ++++++++++++++++++ .../app/src/main/res/layout/widget_layout.xml | 19 ++- .../gradle/wrapper/gradle-wrapper.properties | 6 +- 9 files changed, 292 insertions(+), 98 deletions(-) create mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/Constants.java create mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index 0a64ad4a7..0c4927bcc 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -3,7 +3,6 @@ xmlns:tools="http://schemas.android.com/tools"> - diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 99628da25..b07ba48de 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,7 +19,7 @@ - + - - - - - + android:name=".BitcoinPriceWidget" + android:exported="true" + android:label="Bitcoin Price Widget"> + + + + + 0) { + for (int appWidgetId : appWidgetIds) { + Log.d(TAG, "Updating widget ID: " + appWidgetId); - RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout); + 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 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); + // 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); - executorService.execute(new FetchBitcoinPriceTask(context, appWidgetManager, appWidgetId)); - } + appWidgetManager.updateAppWidget(appWidgetId, views); - 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); - } - } - - private void scheduleNextUpdate(final Context context) { - handler.postDelayed(new Runnable() { - @Override - public void run() { - Intent intent = new Intent(context, BitcoinPriceWidget.class); - intent.setAction(ACTION_UPDATE); - context.sendBroadcast(intent); - scheduleNextUpdate(context); + executorService.execute(new FetchBitcoinPriceTask(context, appWidgetManager, appWidgetId)); } - }, 10 * 60 * 1000); // Update every 10 minutes + + scheduleNextUpdate(context); + } + } + + private void initializeExecutorService() { + if (executorService == null || executorService.isShutdown()) { + executorService = Executors.newSingleThreadExecutor(); + } + } + + 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 { @@ -106,7 +109,9 @@ public class BitcoinPriceWidget extends AppWidgetProvider { Log.d(TAG, "Starting to fetch Bitcoin price..."); - String price = MarketAPI.fetchPrice("USD"); // Using hardcoded "USD" for now + 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); @@ -118,47 +123,63 @@ public class BitcoinPriceWidget extends AppWidgetProvider { 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); + + // 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(); - Log.d(TAG, "Fetch completed with price: " + price); + 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 (prevPrice != null) { - double previousPrice = Double.parseDouble(prevPrice); - double currentPrice = Double.parseDouble(price); - if (currentPrice > previousPrice) { + 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 (currentPrice < previousPrice) { + } 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 (currentPrice != previousPrice) { + if (newPrice != previousPrice) { views.setTextViewText(R.id.previous_price, "from " + currencyFormat.format(previousPrice)); - views.setViewVisibility(R.id.price_arrow, View.VISIBLE); + 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, View.GONE); + 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, View.GONE); + views.setViewVisibility(R.id.price_arrow_container, View.GONE); views.setViewVisibility(R.id.previous_price, View.GONE); } - editor.putString(PREF_PREFIX_KEY + appWidgetId, price); + // 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(); - DateFormat timeFormat = DateFormat.getTimeInstance(DateFormat.SHORT, Locale.getDefault()); - String currentTime = timeFormat.format(new Date()); views.setTextViewText(R.id.last_updated, "Last Updated"); - views.setTextViewText(R.id.last_updated_time, currentTime); + 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); } @@ -172,10 +193,13 @@ public class BitcoinPriceWidget extends AppWidgetProvider { 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.price_arrow_container, View.GONE); views.setViewVisibility(R.id.previous_price, View.GONE); - Toast.makeText(context, "Failed to fetch Bitcoin price", Toast.LENGTH_SHORT).show(); + views.setViewVisibility(R.id.loading_indicator, View.GONE); + appWidgetManager.updateAppWidget(appWidgetId, views); + + Log.e(TAG, "Failed to fetch Bitcoin price"); } } } diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/Constants.java b/android/app/src/main/java/io/bluewallet/bluewallet/Constants.java new file mode 100644 index 000000000..693b34878 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/Constants.java @@ -0,0 +1,5 @@ +package io.bluewallet.bluewallet; + +public class Constants { + public static final int UPDATE_INTERVAL_MINUTES = 3; +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java index fc2c3976c..1c0886f73 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java @@ -16,6 +16,10 @@ 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 { @@ -75,5 +79,8 @@ public class MainApplication extends Application implements ReactApplication { // Initialize Bugsnag or your error tracking here Bugsnag.start(this); } + PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(UpdateWidgetWorker.class, 10, TimeUnit.MINUTES) + .build(); +WorkManager.getInstance(this).enqueueUniquePeriodicWork("UpdateWidgetWork", ExistingPeriodicWorkPolicy.REPLACE, workRequest); } } diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java index 69e6ff968..bda8a3241 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java @@ -1,5 +1,7 @@ package io.bluewallet.bluewallet; +import android.content.Context; + import org.json.JSONArray; import org.json.JSONObject; @@ -10,19 +12,20 @@ import java.net.URL; public class 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(String currency) { + public static String fetchPrice(Context context, String currency) { try { - JSONObject json = new JSONObject(HARD_CODED_JSON); + // Load JSON from assets + InputStreamReader isr = new InputStreamReader(context.getAssets().open("fiatUnits.json")); + StringBuilder jsonBuilder = new StringBuilder(); + char[] buffer = new char[1024]; + int length; + while ((length = isr.read(buffer)) != -1) { + jsonBuilder.append(buffer, 0, length); + } + isr.close(); + + String jsonString = jsonBuilder.toString(); + JSONObject json = new JSONObject(jsonString); JSONObject currencyInfo = json.getJSONObject(currency); String source = currencyInfo.getString("source"); String endPointKey = currencyInfo.getString("endPointKey"); @@ -42,9 +45,9 @@ public class MarketAPI { 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); + char[] buffer2 = new char[1024]; + while ((read = reader.read(buffer2)) != -1) { + jsonResponse.append(buffer2, 0, read); } return parseJSONBasedOnSource(jsonResponse.toString(), source, endPointKey); diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java b/android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java new file mode 100644 index 000000000..272098960 --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java @@ -0,0 +1,149 @@ +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); + } +} \ No newline at end of file diff --git a/android/app/src/main/res/layout/widget_layout.xml b/android/app/src/main/res/layout/widget_layout.xml index 9f63ed75b..f30c0392f 100644 --- a/android/app/src/main/res/layout/widget_layout.xml +++ b/android/app/src/main/res/layout/widget_layout.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:gravity="end"> - + + android:layout_marginEnd="8dp" /> + android:layout_marginTop="2dp" /> + android:layout_marginBottom="8dp" /> + android:layout_marginBottom="8dp" /> + android:layout_marginBottom="8dp" /> + + diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 43148ce9c..bb89cac3b 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -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 \ No newline at end of file From d4ac2e2be9a5cb8d888756a57c3d36783493703f Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Thu, 27 Jun 2024 21:37:59 -0400 Subject: [PATCH 4/9] wip --- .../bluewallet/BitcoinPriceWidget.java | 254 +++++++----------- .../io/bluewallet/bluewallet/MarketAPI.java | 29 +- 2 files changed, 110 insertions(+), 173 deletions(-) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java index 1160469f0..927b44c1e 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java @@ -1,113 +1,123 @@ package io.bluewallet.bluewallet; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.ComponentName; +import android.app.Application; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; 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 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 java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; -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 { +public class BitcoinPriceWidget extends Application implements ReactApplication { 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; + private Timer timer; + + private final ReactNativeHost mReactNativeHost = + new DefaultReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + @SuppressWarnings("UnnecessaryLocalVariable") + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + 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 void onEnabled(Context context) { - super.onEnabled(context); - initializeExecutorService(); + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; } @Override - public void onDisabled(Context context) { - super.onDisabled(context); - if (executorService != null) { - executorService.shutdown(); + public void onCreate() { + super.onCreate(); + 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(); } + + 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"); + + // 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); + } + + // Schedule the first update + scheduleUpdate(); } - @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); - - 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)); + private void scheduleUpdate() { + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + // Ensure this runs on the main thread + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(() -> { + Context context = getApplicationContext(); + // Trigger the update worker here + FetchBitcoinPriceTask fetchTask = new FetchBitcoinPriceTask(context); + fetchTask.run(); + }); } - - scheduleNextUpdate(context); - } - } - - private void initializeExecutorService() { - if (executorService == null || executorService.isShutdown()) { - executorService = Executors.newSingleThreadExecutor(); - } - } - - private void scheduleNextUpdate(Context context) { - OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(UpdateWidgetWorker.class) - .setInitialDelay(UPDATE_INTERVAL_MINUTES, TimeUnit.MINUTES) - .build(); - - WorkManager.getInstance(context).enqueue(workRequest); + }, 0, UPDATE_INTERVAL_MINUTES * 60 * 1000); // Update interval in milliseconds } private static class FetchBitcoinPriceTask implements Runnable { - private final WeakReference 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 final Context context; + + FetchBitcoinPriceTask(Context context) { + this.context = context; } @Override public void run() { - Context context = contextRef.get(); - if (context == null) return; - - Log.d(TAG, "Starting to fetch Bitcoin price..."); + Log.d(TAG, "Fetching Bitcoin price..."); SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String preferredCurrency = prefs.getString("preferredCurrency", "USD"); @@ -121,85 +131,13 @@ public class BitcoinPriceWidget extends AppWidgetProvider { } 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(); - - 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); + // Update the widget with the fetched price + // Add your implementation here } 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_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"); + // Handle the error + // Add your implementation here } } -} +} \ 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 index bda8a3241..c7c87a9f7 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java @@ -14,19 +14,18 @@ public class MarketAPI { public static String fetchPrice(Context context, String currency) { try { - // Load JSON from assets - InputStreamReader isr = new InputStreamReader(context.getAssets().open("fiatUnits.json")); - StringBuilder jsonBuilder = new StringBuilder(); - char[] buffer = new char[1024]; - int length; - while ((length = isr.read(buffer)) != -1) { - jsonBuilder.append(buffer, 0, length); + // 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(); } - isr.close(); + is.close(); - String jsonString = jsonBuilder.toString(); - JSONObject json = new JSONObject(jsonString); - JSONObject currencyInfo = json.getJSONObject(currency); + JSONObject jsonObject = new JSONObject(json.toString()); + JSONObject currencyInfo = jsonObject.getJSONObject(currency); String source = currencyInfo.getString("source"); String endPointKey = currencyInfo.getString("endPointKey"); @@ -45,9 +44,9 @@ public class MarketAPI { InputStreamReader reader = new InputStreamReader(urlConnection.getInputStream()); StringBuilder jsonResponse = new StringBuilder(); int read; - char[] buffer2 = new char[1024]; - while ((read = reader.read(buffer2)) != -1) { - jsonResponse.append(buffer2, 0, read); + char[] buffer = new char[1024]; + while ((read = reader.read(buffer)) != -1) { + jsonResponse.append(buffer, 0, read); } return parseJSONBasedOnSource(jsonResponse.toString(), source, endPointKey); @@ -117,4 +116,4 @@ public class MarketAPI { return null; } } -} +} \ No newline at end of file From e60b5c71446323dcfaa4ab7d3edf44d1152c1b51 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Thu, 27 Jun 2024 22:18:37 -0400 Subject: [PATCH 5/9] Update BitcoinPriceWidget.java --- .../main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java index 927b44c1e..5130c4aec 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Timer; import java.util.TimerTask; -public class BitcoinPriceWidget extends Application implements ReactApplication { +public class MainApplication extends Application implements ReactApplication { private static final String TAG = "BitcoinPriceWidget"; private static final String PREFS_NAME = "BitcoinPriceWidgetPrefs"; From d29fde0431533f572e0c2dbc33d3574571fb7a32 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Thu, 27 Jun 2024 23:15:34 -0400 Subject: [PATCH 6/9] Update BitcoinPriceWidget.java --- .../bluewallet/BitcoinPriceWidget.java | 254 +++++++++++------- 1 file changed, 158 insertions(+), 96 deletions(-) diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java index 5130c4aec..1160469f0 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java @@ -1,123 +1,113 @@ package io.bluewallet.bluewallet; -import android.app.Application; +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.os.Handler; -import android.os.Looper; import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; -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 java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; +import androidx.annotation.NonNull; +import androidx.work.Constraints; +import androidx.work.OneTimeWorkRequest; +import androidx.work.WorkManager; -public class MainApplication extends Application implements ReactApplication { +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 Timer timer; - - private final ReactNativeHost mReactNativeHost = - new DefaultReactNativeHost(this) { - @Override - public boolean getUseDeveloperSupport() { - return BuildConfig.DEBUG; - } - - @Override - protected List getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - 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; - } - }; + private static ExecutorService executorService; @Override - public ReactNativeHost getReactNativeHost() { - return mReactNativeHost; + public void onEnabled(Context context) { + super.onEnabled(context); + initializeExecutorService(); } @Override - public void onCreate() { - super.onCreate(); - 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(); + public void onDisabled(Context context) { + super.onDisabled(context); + if (executorService != null) { + executorService.shutdown(); } - - 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"); - - // 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); - } - - // Schedule the first update - scheduleUpdate(); } - private void scheduleUpdate() { - timer = new Timer(); - timer.schedule(new TimerTask() { - @Override - public void run() { - // Ensure this runs on the main thread - Handler handler = new Handler(Looper.getMainLooper()); - handler.post(() -> { - Context context = getApplicationContext(); - // Trigger the update worker here - FetchBitcoinPriceTask fetchTask = new FetchBitcoinPriceTask(context); - fetchTask.run(); - }); + @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); + + 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)); } - }, 0, UPDATE_INTERVAL_MINUTES * 60 * 1000); // Update interval in milliseconds + + scheduleNextUpdate(context); + } + } + + private void initializeExecutorService() { + if (executorService == null || executorService.isShutdown()) { + executorService = Executors.newSingleThreadExecutor(); + } + } + + 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 contextRef; + private final AppWidgetManager appWidgetManager; + private final int appWidgetId; - private final Context context; - - FetchBitcoinPriceTask(Context context) { - this.context = context; + FetchBitcoinPriceTask(Context context, AppWidgetManager appWidgetManager, int appWidgetId) { + this.contextRef = new WeakReference<>(context); + this.appWidgetManager = appWidgetManager; + this.appWidgetId = appWidgetId; } @Override public void run() { - Log.d(TAG, "Fetching Bitcoin price..."); + 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"); @@ -131,13 +121,85 @@ public class MainApplication extends Application implements ReactApplication { } private void updateWidgetWithPrice(Context context, String price) { - // Update the widget with the fetched price - // Add your implementation here + 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) { - // Handle the error - // Add your implementation here + 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); + views.setViewVisibility(R.id.loading_indicator, View.GONE); + + appWidgetManager.updateAppWidget(appWidgetId, views); + + Log.e(TAG, "Failed to fetch Bitcoin price"); } } -} \ No newline at end of file +} From 29aa934f8d7ee8d437a9d936dd35f25b6d4257ca Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 29 Jun 2024 17:42:14 -0400 Subject: [PATCH 7/9] wip --- android/app/build.gradle | 14 + android/app/src/main/AndroidManifest.xml | 5 +- android/app/src/main/assets/fiatUnits.json | 408 ++++++++++++++++++ .../bluewallet/BitcoinPriceWidget.java | 221 ++-------- .../io/bluewallet/bluewallet/Constants.java | 5 - .../bluewallet/MainApplication.java | 97 ++--- .../io/bluewallet/bluewallet/MarketAPI.java | 29 +- .../bluewallet/UpdateWidgetWorker.java | 149 ------- .../bluewallet/WidgetUpdateManager.java | 81 ++++ .../bluewallet/WidgetUpdateWorker.java | 156 +++++++ .../io/bluewallet/bluewallet/WidgetUtils.java | 19 + .../app/src/main/res/layout/widget_layout.xml | 29 +- 12 files changed, 805 insertions(+), 408 deletions(-) create mode 100644 android/app/src/main/assets/fiatUnits.json delete mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/Constants.java delete mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java create mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java create mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java create mode 100644 android/app/src/main/java/io/bluewallet/bluewallet/WidgetUtils.java diff --git a/android/app/build.gradle b/android/app/build.gradle index 100c8631c..11d82526f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b07ba48de..2fe078f1b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ + - @@ -70,7 +72,6 @@ android:resource="@xml/bitcoin_price_widget_info" /> - diff --git a/android/app/src/main/assets/fiatUnits.json b/android/app/src/main/assets/fiatUnits.json new file mode 100644 index 000000000..c14837051 --- /dev/null +++ b/android/app/src/main/assets/fiatUnits.json @@ -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)" + } +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java index 1160469f0..f9dbf9e7c 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/BitcoinPriceWidget.java @@ -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 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(); + } +} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/Constants.java b/android/app/src/main/java/io/bluewallet/bluewallet/Constants.java deleted file mode 100644 index 693b34878..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/Constants.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.bluewallet.bluewallet; - -public class Constants { - public static final int UPDATE_INTERVAL_MINUTES = 3; -} \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java index 1c0886f73..789eb48c7 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java @@ -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 getPackages() { - @SuppressWarnings("UnnecessaryLocalVariable") - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - return packages; - } + @Override + protected List getPackages() { + @SuppressWarnings("UnnecessaryLocalVariable") + List 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); - } } diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java index c7c87a9f7..3a35aa2d8 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MarketAPI.java @@ -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; } } -} \ No newline at end of file +} diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java b/android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java deleted file mode 100644 index 272098960..000000000 --- a/android/app/src/main/java/io/bluewallet/bluewallet/UpdateWidgetWorker.java +++ /dev/null @@ -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); - } -} \ 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 new file mode 100644 index 000000000..50f0d104c --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java @@ -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"); + } + } +} \ 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 new file mode 100644 index 000000000..872feb49b --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java @@ -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) + ); + } +} \ 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 new file mode 100644 index 000000000..3b1a128fa --- /dev/null +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUtils.java @@ -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); + } + } +} diff --git a/android/app/src/main/res/layout/widget_layout.xml b/android/app/src/main/res/layout/widget_layout.xml index f30c0392f..cc45e32fa 100644 --- a/android/app/src/main/res/layout/widget_layout.xml +++ b/android/app/src/main/res/layout/widget_layout.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" android:orientation="vertical" android:gravity="end"> - + + android:layout_marginEnd="8dp"/> + android:layout_marginTop="2dp"/> + android:layout_marginBottom="8dp"/> + android:layout_marginBottom="8dp"/> + android:layout_marginBottom="8dp"/> - - - + + + + \ No newline at end of file From fb5066568238da12166d6a9d430550630bbaf2a5 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 29 Jun 2024 18:06:49 -0400 Subject: [PATCH 8/9] Update widget_layout.xml --- .../app/src/main/res/layout/widget_layout.xml | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/android/app/src/main/res/layout/widget_layout.xml b/android/app/src/main/res/layout/widget_layout.xml index cc45e32fa..6790c09ef 100644 --- a/android/app/src/main/res/layout/widget_layout.xml +++ b/android/app/src/main/res/layout/widget_layout.xml @@ -40,6 +40,15 @@ android:orientation="vertical" android:gravity="end"> + + + android:gravity="end" + android:visibility="gone"> - - - \ No newline at end of file From ae088e92aa264f63cfa651e8a45124d444346509 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Sat, 29 Jun 2024 19:31:44 -0400 Subject: [PATCH 9/9] wip --- android/app/src/main/AndroidManifest.xml | 27 +-- .../bluewallet/BitcoinPriceWidget.java | 145 ++++++++++----- .../bluewallet/MainApplication.java | 6 +- .../bluewallet/WidgetUpdateManager.java | 3 +- .../bluewallet/WidgetUpdateWorker.java | 176 +++++++----------- .../app/src/main/res/layout/widget_layout.xml | 33 ++-- 6 files changed, 200 insertions(+), 190 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2fe078f1b..121d60b5a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -19,8 +19,10 @@ - - + + + + - - - - - - + + + + + + + + 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); + private final BroadcastReceiver screenReceiver = new BroadcastReceiver() { + @Override + 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; + } } - } catch (NumberFormatException e) { - Log.e(TAG, "Error parsing prices", e); } - - } else { - views.setViewVisibility(R.id.price_arrow_container, View.GONE); } + }; - appWidgetManager.updateAppWidget(appWidgetId, views); + 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*/); + } + } } - 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(); + 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()); } } \ No newline at end of file diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java index 789eb48c7..4e724af8d 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/MainApplication.java @@ -76,6 +76,10 @@ public class MainApplication extends Application implements ReactApplication { Bugsnag.start(this); } - WidgetUpdateWorker.scheduleWork(this); // Schedule to run every 10 minutes + + // 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/WidgetUpdateManager.java b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java index 50f0d104c..3e06a53b2 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateManager.java @@ -61,9 +61,8 @@ public class WidgetUpdateManager extends Worker { 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_label, View.VISIBLE); views.setViewVisibility(R.id.last_updated_time, View.VISIBLE); if (!"N/A".equals(previousPrice)) { diff --git a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java index 872feb49b..d62aecd1a 100644 --- a/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java +++ b/android/app/src/main/java/io/bluewallet/bluewallet/WidgetUpdateWorker.java @@ -1,5 +1,7 @@ package io.bluewallet.bluewallet; +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.util.Log; @@ -7,15 +9,12 @@ 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 androidx.work.OneTimeWorkRequest; -import java.text.DateFormat; import java.text.NumberFormat; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.concurrent.TimeUnit; @@ -23,134 +22,87 @@ 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 + 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 workerParams) { - super(context, workerParams); + public WidgetUpdateWorker(@NonNull Context context, @NonNull WorkerParameters params) { + super(context, params); } @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(); + 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); } - - Log.d(TAG, "Next fetch will occur at: " + getNextFetchTime()); - return Result.success(); } - private void updateWidget(Context context, String price, String currency, String previousPrice) { + 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); - NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(Locale.getDefault()); - currencyFormat.setCurrency(java.util.Currency.getInstance(currency)); + // 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()); - 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); + // 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 (!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); + 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.setImageViewResource(R.id.price_arrow, android.R.drawable.arrow_down_float); + 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 { - views.setViewVisibility(R.id.price_arrow_container, View.GONE); + Log.e(TAG, "Failed to fetch Bitcoin price"); + views.setTextViewText(R.id.price_value, "Error"); + appWidgetManager.updateAppWidget(appWidgetId, views); } - - // 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) - ); + public static OneTimeWorkRequest createWorkRequest() { + return new OneTimeWorkRequest.Builder(WidgetUpdateWorker.class).build(); } } \ No newline at end of file diff --git a/android/app/src/main/res/layout/widget_layout.xml b/android/app/src/main/res/layout/widget_layout.xml index 6790c09ef..4ed4f9fc0 100644 --- a/android/app/src/main/res/layout/widget_layout.xml +++ b/android/app/src/main/res/layout/widget_layout.xml @@ -14,11 +14,11 @@ android:gravity="end"> @@ -40,15 +40,6 @@ android:orientation="vertical" android:gravity="end"> - - + android:gravity="end"> + + + + \ No newline at end of file