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