Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue-657: Forecast Widget Type B #704

Merged
merged 17 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0a0dd48
issue-657: First draft of large widget layout
stembitf Nov 25, 2024
93917bd
Merge branch 'feature/issue-660-Forecast-widget-Type-A' into feature/…
stembitf Nov 25, 2024
f395e03
issue-657: Needed files added for large widget.
stembitf Nov 25, 2024
9c712b4
issue-657: Large widget basically implemented.
stembitf Nov 25, 2024
4116b1d
issue-657: Some code cleanup.
stembitf Nov 25, 2024
05c8cf4
issue-657: Large widget updates periodically now. Layout changes. Bac…
stembitf Nov 26, 2024
e405e9a
issue-657: Small layout updates. Unused layout file removed.
stembitf Nov 26, 2024
a5c914c
Merge branch 'android-main' into feature/issue-657-Forecast-widget-Ty…
stembitf Nov 26, 2024
0f90b2e
issue-657: Translations added. Widget update period set to 30 minutes…
stembitf Nov 26, 2024
5bcbf77
issue-657: Time at selected destination shown now for weather.
stembitf Nov 28, 2024
53da96b
issue-657: Now checked that weather forecasts have a future timestamp.
stembitf Nov 28, 2024
459414d
issue-657: onPostExecute methods simplified
stembitf Nov 28, 2024
f3370f2
issue-657: Color handling added for large widget.
stembitf Nov 28, 2024
da267a2
issue-657: Update period set to 15 minutes
stembitf Nov 28, 2024
6f0bf66
issue-624: Some restructuring in base widget provider. Some comments …
stembitf Nov 28, 2024
0226560
issue-657: new setWidgetData method after initWidget. Some variables …
stembitf Nov 29, 2024
6fe4bc5
issue-657: Widget update repeat interval time taken from widget confi…
stembitf Nov 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@
</intent-filter>
</activity>

<receiver android:name=".LargeWidgetProvider" android:label="@string/large_widget" android:exported="false">
geosaaga marked this conversation as resolved.
Show resolved Hide resolved
<intent-filter>
<action android:name="fi.fmi.mobileweather.AUTO_UPDATE" />
</intent-filter>
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider" android:resource="@xml/large_widget_provider_info" />
</receiver>
<activity android:name=".LargeWidgetConfigurationActivity" android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,17 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider {
// crisis data valid for 12 hours
private static final long CRISIS_DATA_VALIDITY = 12 * 60 * 60 * 1000;

private Context context;
private int appWidgetId;
private AppWidgetManager appWidgetManager;
Context context;
int appWidgetId;
AppWidgetManager appWidgetManager;

private Handler timeoutHandler;
private Runnable timeoutRunnable;
private static String weatherUrl;
private static String announcementsUrl;

protected static String immediateBackgroundSetting;

protected abstract int getLayoutResourceId();

@Override
Expand Down Expand Up @@ -88,23 +91,30 @@ private void updateAfterReceive(Context context) {
announcementsUrl = setup.getAnnouncements().getApi().getFi();
}

// Get the list of appWidgetIds that have been bound to the given AppWidget provider.
AppWidgetManager appWidgetManager =
AppWidgetManager.getInstance(context);
ComponentName thisAppWidgetComponentName = new ComponentName(context.getPackageName(),getClass().getName());
ComponentName thisAppWidgetComponentName = new ComponentName(context.getPackageName(), getClass().getName());
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName);

Log.d("Widget Update","thisAppWidgetComponentName: " + thisAppWidgetComponentName);

onUpdate(context, appWidgetManager, appWidgetIds);
}

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
Log.d("Widget Update","onUpdate");

// if no widgets, log and return
if (appWidgetIds.length == 0) {
Log.w("Widget Update","No widgets to update");
return;
}

// There may be multiple widgets active, so update all
final int N = appWidgetIds.length;
for (int i = 0; i < N; i++) {
updateAppWidget(context, appWidgetManager, appWidgetIds[i], null);
for (int widgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, widgetId, null);
}
}

Expand Down Expand Up @@ -367,8 +377,15 @@ protected void onPostExecute(JSONObject json, JSONArray json2, RemoteViews main,
Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);

// Get settings
String background = pref.getString("background", "transparent");
// Get background setting
String background;
if (immediateBackgroundSetting == null) {
background = pref.getString("background", "transparent");
} else {
background = immediateBackgroundSetting;
immediateBackgroundSetting = null;
}

Log.d("Download json", "Background: " + background);

// Get the layout for the App Widget now if needed
Expand Down Expand Up @@ -446,12 +463,7 @@ else if (background.equals("light")) {
main.setTextViewText(R.id.locationRegionTextView, region);

String temperature = first.getString("temperature");
// temperature string to float
float tempFloat = Float.parseFloat(temperature);
// if temperature is positive, add plus sign to temperature string
if (tempFloat > 0) {
temperature = "+" + temperature;
}
temperature = addPlusIfNeeded(temperature);
main.setTextViewText(R.id.temperatureTextView, temperature);
main.setTextViewText(R.id.temperatureUnitTextView, "°C");

Expand Down Expand Up @@ -512,8 +524,18 @@ else if (background.equals("light")) {
appWidgetManager.updateAppWidget(appWidgetId, main);
}

protected String addPlusIfNeeded(String temperature) {
// temperature string to float
float tempFloat = Float.parseFloat(temperature);
// if temperature is positive, add plus sign to temperature string
if (tempFloat > 0) {
temperature = "+" + temperature;
}
return temperature;
}

@Nullable
private JSONObject useNewOrStoredJsonObject(JSONObject json, SharedPreferencesHelper pref) {
JSONObject useNewOrStoredJsonObject(JSONObject json, SharedPreferencesHelper pref) {
Date now = new Date();

if (json == null) {
Expand Down Expand Up @@ -554,7 +576,7 @@ private JSONObject useNewOrStoredJsonObject(JSONObject json, SharedPreferencesHe
}

@Nullable
private JSONArray useNewOrStoredCrisisJsonObject(JSONArray json, SharedPreferencesHelper pref) {
JSONArray useNewOrStoredCrisisJsonObject(JSONArray json, SharedPreferencesHelper pref) {
Date now = new Date();

if (json == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fi.fmi.mobileweather;

public class LargeWidgetConfigurationActivity extends BaseWidgetConfigurationActivity {
@Override
protected Class<?> getWidgetProviderClass() {
return LargeWidgetProvider.class;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package fi.fmi.mobileweather;

import static android.view.View.GONE;
import static android.view.View.VISIBLE;

import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.util.Log;
import android.widget.RemoteViews;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.text.DateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Iterator;

public class LargeWidgetProvider extends BaseWidgetProvider {
@Override
protected int getLayoutResourceId() {
return R.layout.large_widget_layout;
}

@Override
protected void onPostExecute(JSONObject json, JSONArray json2, RemoteViews main, SharedPreferencesHelper pref) {

Intent intent = new Intent(context, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE);

// Get settings
String background = pref.getString("background", "transparent");
Log.d("Download json", "Background: " + background);

main = new RemoteViews(context.getPackageName(), getLayoutResourceId());

// Show normal view
main.setInt(R.id.normalLayout, "setVisibility", VISIBLE);
// Hide error view
main.setInt(R.id.errorLayout, "setVisibility", GONE);

main.setOnClickPendingIntent(R.id.mainLinearLayout, pendingIntent);

json = useNewOrStoredJsonObject(json, pref);
if (json == null) return;

if (background.equals("dark")) {
geosaaga marked this conversation as resolved.
Show resolved Hide resolved
setColors(main,
Color.parseColor("#191B22"),
Color.rgb(255, 255, 255));
}
else if (background.equals("light")) {
setColors(main,
Color.rgb(255, 255, 255),
Color.rgb(48, 49, 147));
} else {
setColors(main,
Color.TRANSPARENT,
Color.rgb(48, 49, 147));
}
Log.d("Download json", "Forecast json: " + json);
Log.d("Download json", "Crisis json: " + json2);


try {
// Get the keys of the JSONObject
Iterator<String> keys = json.keys();

// Retrieve the first key
if (!keys.hasNext()) {
return;
}
String firstKey = keys.next();
Log.d("Download json", "First key (geoid): " + firstKey);

// Extract the JSONArray associated with the first key
JSONArray data = json.getJSONArray(firstKey);

// get the first 5 JsonObject from the JSONArray
geosaaga marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < 5; i++) {
JSONObject forecast = data.getJSONObject(i);

if (i == 0) {
// set the location name and region
String name = forecast.getString("name");
String region = forecast.getString("region");
main.setTextViewText(R.id.locationNameTextView, name + ", ");
main.setTextViewText(R.id.locationRegionTextView, region);
}


long epochTime = forecast.getLong("epochtime");
String temperature = forecast.getString("temperature");
String weathersymbol = forecast.getString("smartSymbol");

// get timeTextView0 or timeTextView1 etc. based on i from main
int timeTextViewId = context.getResources().getIdentifier("timeTextView" + i, "id", context.getPackageName());
int temperatureTextViewId = context.getResources().getIdentifier("temperatureTextView" + i, "id", context.getPackageName());
int weatherIconImageViewId = context.getResources().getIdentifier("weatherIconImageView" + i, "id", context.getPackageName());

// ** set the time, temperature and weather icon

LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(epochTime), ZoneId.systemDefault());
geosaaga marked this conversation as resolved.
Show resolved Hide resolved
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
String formattedTime = localDateTime.format(formatter);
main.setTextViewText(timeTextViewId, formattedTime);

temperature = addPlusIfNeeded(temperature);
main.setTextViewText(temperatureTextViewId, temperature + "°");

Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
context.getResources().getIdentifier("s" + weathersymbol + (background.equals("light") ? "_light" : "_dark"), "drawable", context.getPackageName()));
main.setImageViewBitmap(weatherIconImageViewId, icon);
}

// Update time TODO: should be hidden for release
main.setTextViewText(R.id.updateTimeTextView, DateFormat.getTimeInstance().format(new Date()));


// *** Crisis view
// example json: [{"type":"Crisis","content":"Varoitusnauha -testi EN","link":"https://www.fmi.fi"}]

json2 = useNewOrStoredCrisisJsonObject(json2, pref);

if (json2 != null) {
boolean crisisFound = false;
try {
for (int i = 0; i < json2.length(); i++) {
JSONObject jsonObject = json2.getJSONObject(i);
String type = jsonObject.getString("type");
if (type.equals("Crisis")) {
String content = jsonObject.getString("content");
main.setViewVisibility(R.id.crisisTextView, VISIBLE);
main.setTextViewText(R.id.crisisTextView, content);
crisisFound = true;
// if a crisis found, exit the loop
break;
}
}
} catch (JSONException e) {
Log.e("Download json", "Crisis Json parsing error: " + e.getMessage());
}
if (!crisisFound) {
main.setViewVisibility(R.id.crisisTextView, GONE);
}
} else {
main.setViewVisibility(R.id.crisisTextView, GONE);
}

appWidgetManager.updateAppWidget(appWidgetId, main);
return;

} catch (final JSONException e) {
Log.e("Download json", "Exception Json parsing error: " + e.getMessage());
showErrorView(
context,
pref,
"(parsing error) " + context.getResources().getString(R.string.update_failed),
context.getResources().getString(R.string.check_internet_connection)
);
}

appWidgetManager.updateAppWidget(appWidgetId, main);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,19 @@ public Result doWork() {

private void broadcastUpdate() {
Log.d("Widget Update", "Broadcasting widget update");
Intent intent = new Intent(getApplicationContext(), SmallWidgetProvider.class)

// ** broadcast to all widget providers which can receive ACTION_APPWIDGET_AUTO_UPDATE
// (SmallWidgetProvider and LargeWidgetProvider)

// Create intents for each widget provider class
Intent smallWidgetIntent = new Intent(getApplicationContext(), SmallWidgetProvider.class)
.setAction(ACTION_APPWIDGET_AUTO_UPDATE);
getApplicationContext().sendBroadcast(intent);
Intent largeWidgetIntent = new Intent(getApplicationContext(), LargeWidgetProvider.class)
.setAction(ACTION_APPWIDGET_AUTO_UPDATE);

// Send broadcasts
getApplicationContext().sendBroadcast(smallWidgetIntent);
getApplicationContext().sendBroadcast(largeWidgetIntent);
}

}
Expand Down
Loading