Integrate Detail widget, showing a full list of weather information and allowing clicking through to individual days
This commit is contained in:
@@ -122,6 +122,23 @@
|
||||
android:resource="@xml/widget_info_today" />
|
||||
</receiver>
|
||||
<service android:name=".widget.TodayWidgetIntentService" />
|
||||
|
||||
<receiver
|
||||
android:name=".widget.DetailWidgetProvider"
|
||||
android:label="@string/title_widget_detail"
|
||||
android:enabled="@bool/widget_detail_enabled" >
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
<action android:name="com.example.android.sunshine.app.ACTION_DATA_UPDATED" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info_detail" />
|
||||
</receiver>
|
||||
<service
|
||||
android:name=".widget.DetailWidgetRemoteViewsService"
|
||||
android:enabled="@bool/widget_detail_enabled"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -127,13 +127,16 @@ public class ForecastAdapter extends RecyclerView.Adapter<ForecastAdapter.Foreca
|
||||
mCursor.moveToPosition(position);
|
||||
int weatherId = mCursor.getInt(ForecastFragment.COL_WEATHER_CONDITION_ID);
|
||||
int defaultImage;
|
||||
boolean useLongToday;
|
||||
|
||||
switch (getItemViewType(position)) {
|
||||
case VIEW_TYPE_TODAY:
|
||||
defaultImage = Utility.getArtResourceForWeatherCondition(weatherId);
|
||||
useLongToday = true;
|
||||
break;
|
||||
default:
|
||||
defaultImage = Utility.getIconResourceForWeatherCondition(weatherId);
|
||||
useLongToday = false;
|
||||
}
|
||||
|
||||
if ( Utility.usingLocalGraphics(mContext) ) {
|
||||
@@ -154,7 +157,7 @@ public class ForecastAdapter extends RecyclerView.Adapter<ForecastAdapter.Foreca
|
||||
long dateInMillis = mCursor.getLong(ForecastFragment.COL_WEATHER_DATE);
|
||||
|
||||
// Find TextView and set formatted date on it
|
||||
forecastAdapterViewHolder.mDateView.setText(Utility.getFriendlyDayString(mContext, dateInMillis));
|
||||
forecastAdapterViewHolder.mDateView.setText(Utility.getFriendlyDayString(mContext, dateInMillis, useLongToday));
|
||||
|
||||
// Read weather forecast from cursor
|
||||
String description = Utility.getStringForWeatherCondition(mContext, weatherId);
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.design.widget.AppBarLayout;
|
||||
import android.support.design.widget.CoordinatorLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
@@ -55,12 +54,11 @@ import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
||||
public class ForecastFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public static final String LOG_TAG = ForecastFragment.class.getSimpleName();
|
||||
private ForecastAdapter mForecastAdapter;
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private int mPosition = RecyclerView.NO_POSITION;
|
||||
private boolean mUseTodayLayout, mAutoSelectView;
|
||||
private int mChoiceMode;
|
||||
private boolean mHoldForTransition;
|
||||
private long mInitialSelectedDate = -1;
|
||||
|
||||
private static final String SELECTED_KEY = "selected_position";
|
||||
|
||||
@@ -196,7 +194,6 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
locationSetting, date),
|
||||
vh
|
||||
);
|
||||
mPosition = vh.getAdapterPosition();
|
||||
}
|
||||
}, emptyView, mChoiceMode);
|
||||
|
||||
@@ -246,11 +243,6 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
// or magically appeared to take advantage of room, but data or place in the app was never
|
||||
// actually *lost*.
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.containsKey(SELECTED_KEY)) {
|
||||
// The Recycler View probably hasn't even been populated yet. Actually perform the
|
||||
// swapout in onLoadFinished.
|
||||
mPosition = savedInstanceState.getInt(SELECTED_KEY);
|
||||
}
|
||||
mForecastAdapter.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
|
||||
@@ -304,11 +296,6 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
// When tablets rotate, the currently selected list item needs to be saved.
|
||||
// When no item is selected, mPosition will be set to RecyclerView.NO_POSITION,
|
||||
// so check for that before storing.
|
||||
if (mPosition != RecyclerView.NO_POSITION) {
|
||||
outState.putInt(SELECTED_KEY, mPosition);
|
||||
}
|
||||
mForecastAdapter.onSaveInstanceState(outState);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
@@ -340,11 +327,6 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
@Override
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
mForecastAdapter.swapCursor(data);
|
||||
if (mPosition != RecyclerView.NO_POSITION) {
|
||||
// If we don't need to restart the loader, and there's a desired position to restore
|
||||
// to, do so now.
|
||||
mRecyclerView.smoothScrollToPosition(mPosition);
|
||||
}
|
||||
updateEmptyView();
|
||||
if ( data.getCount() == 0 ) {
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
@@ -356,11 +338,27 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
// we see Children.
|
||||
if (mRecyclerView.getChildCount() > 0) {
|
||||
mRecyclerView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
int itemPosition = mForecastAdapter.getSelectedItemPosition();
|
||||
if ( RecyclerView.NO_POSITION == itemPosition ) itemPosition = 0;
|
||||
RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(itemPosition);
|
||||
if ( null != vh && mAutoSelectView ) {
|
||||
mForecastAdapter.selectView( vh );
|
||||
int position = mForecastAdapter.getSelectedItemPosition();
|
||||
if (position == RecyclerView.NO_POSITION &&
|
||||
-1 != mInitialSelectedDate) {
|
||||
Cursor data = mForecastAdapter.getCursor();
|
||||
int count = data.getCount();
|
||||
int dateColumn = data.getColumnIndex(WeatherContract.WeatherEntry.COLUMN_DATE);
|
||||
for ( int i = 0; i < count; i++ ) {
|
||||
data.moveToPosition(i);
|
||||
if ( data.getLong(dateColumn) == mInitialSelectedDate ) {
|
||||
position = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (position == RecyclerView.NO_POSITION) position = 0;
|
||||
// If we don't need to restart the loader, and there's a desired position to restore
|
||||
// to, do so now.
|
||||
mRecyclerView.smoothScrollToPosition(position);
|
||||
RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(position);
|
||||
if (null != vh && mAutoSelectView) {
|
||||
mForecastAdapter.selectView(vh);
|
||||
}
|
||||
if ( mHoldForTransition ) {
|
||||
getActivity().supportStartPostponedEnterTransition();
|
||||
@@ -396,6 +394,10 @@ public class ForecastFragment extends Fragment implements LoaderManager.LoaderCa
|
||||
}
|
||||
}
|
||||
|
||||
public void setInitialSelectedDate(long initialSelectedDate) {
|
||||
mInitialSelectedDate = initialSelectedDate;
|
||||
}
|
||||
|
||||
/*
|
||||
Updates the empty list view with contextually relevant information that the user can
|
||||
use to determine why they aren't seeing weather.
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.example.android.sunshine.app.data.WeatherContract;
|
||||
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
||||
import com.google.android.gms.common.ConnectionResult;
|
||||
import com.google.android.gms.common.GooglePlayServicesUtil;
|
||||
@@ -63,6 +64,7 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
mLocation = Utility.getPreferredLocation(this);
|
||||
Uri contentUri = getIntent() != null ? getIntent().getData() : null;
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
Toolbar toolbar = (Toolbar)findViewById(R.id.toolbar);
|
||||
@@ -78,8 +80,14 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
if (savedInstanceState == null) {
|
||||
DetailFragment fragment = new DetailFragment();
|
||||
if (contentUri != null) {
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(DetailFragment.DETAIL_URI, contentUri);
|
||||
fragment.setArguments(args);
|
||||
}
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.weather_detail_container, new DetailFragment(), DETAILFRAGMENT_TAG)
|
||||
.replace(R.id.weather_detail_container, fragment, DETAILFRAGMENT_TAG)
|
||||
.commit();
|
||||
}
|
||||
} else {
|
||||
@@ -90,6 +98,10 @@ public class MainActivity extends AppCompatActivity implements ForecastFragment.
|
||||
ForecastFragment forecastFragment = ((ForecastFragment)getSupportFragmentManager()
|
||||
.findFragmentById(R.id.fragment_forecast));
|
||||
forecastFragment.setUseTodayLayout(!mTwoPane);
|
||||
if (contentUri != null) {
|
||||
forecastFragment.setInitialSelectedDate(
|
||||
WeatherContract.WeatherEntry.getDateFromUri(contentUri));
|
||||
}
|
||||
|
||||
SunshineSyncAdapter.initializeSyncAdapter(this);
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ public class Utility {
|
||||
* @param dateInMillis The date in milliseconds
|
||||
* @return a user-friendly representation of the date.
|
||||
*/
|
||||
public static String getFriendlyDayString(Context context, long dateInMillis) {
|
||||
public static String getFriendlyDayString(Context context, long dateInMillis, boolean displayLongToday) {
|
||||
// The day string for forecast uses the following logic:
|
||||
// For today: "Today, June 8"
|
||||
// For tomorrow: "Tomorrow"
|
||||
@@ -87,7 +87,7 @@ public class Utility {
|
||||
|
||||
// If the date we're building the String for is today's date, the format
|
||||
// is "Today, June 24"
|
||||
if (julianDay == currentJulianDay) {
|
||||
if (displayLongToday && julianDay == currentJulianDay) {
|
||||
String today = context.getString(R.string.today);
|
||||
int formatId = R.string.format_full_friendly_date;
|
||||
return String.format(context.getString(
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.example.android.sunshine.app.widget;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
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.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import com.example.android.sunshine.app.DetailActivity;
|
||||
import com.example.android.sunshine.app.MainActivity;
|
||||
import com.example.android.sunshine.app.R;
|
||||
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
|
||||
|
||||
/**
|
||||
* Provider for a scrollable weather detail widget
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class DetailWidgetProvider extends AppWidgetProvider {
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
// Perform this loop procedure for each App Widget that belongs to this provider
|
||||
for (int appWidgetId : appWidgetIds) {
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_detail);
|
||||
|
||||
// Create an Intent to launch MainActivity
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
|
||||
views.setOnClickPendingIntent(R.id.widget, pendingIntent);
|
||||
|
||||
// Set up the collection
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
setRemoteAdapter(context, views);
|
||||
} else {
|
||||
setRemoteAdapterV11(context, views);
|
||||
}
|
||||
boolean useDetailActivity = context.getResources()
|
||||
.getBoolean(R.bool.use_detail_activity);
|
||||
Intent clickIntentTemplate = useDetailActivity
|
||||
? new Intent(context, DetailActivity.class)
|
||||
: new Intent(context, MainActivity.class);
|
||||
PendingIntent clickPendingIntentTemplate = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(clickIntentTemplate)
|
||||
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setPendingIntentTemplate(R.id.widget_list, clickPendingIntentTemplate);
|
||||
views.setEmptyView(R.id.widget_list, R.id.widget_empty);
|
||||
|
||||
// Tell the AppWidgetManager to perform an update on the current app widget
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
if (SunshineSyncAdapter.ACTION_DATA_UPDATED.equals(intent.getAction())) {
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(
|
||||
new ComponentName(context, getClass()));
|
||||
appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.widget_list);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote adapter used to fill in the list items
|
||||
*
|
||||
* @param views RemoteViews to set the RemoteAdapter
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
private void setRemoteAdapter(Context context, @NonNull final RemoteViews views) {
|
||||
views.setRemoteAdapter(R.id.widget_list,
|
||||
new Intent(context, DetailWidgetRemoteViewsService.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the remote adapter used to fill in the list items
|
||||
*
|
||||
* @param views RemoteViews to set the RemoteAdapter
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setRemoteAdapterV11(Context context, @NonNull final RemoteViews views) {
|
||||
views.setRemoteAdapter(0, R.id.widget_list,
|
||||
new Intent(context, DetailWidgetRemoteViewsService.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package com.example.android.sunshine.app.widget;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.RemoteViews;
|
||||
import android.widget.RemoteViewsService;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.example.android.sunshine.app.R;
|
||||
import com.example.android.sunshine.app.Utility;
|
||||
import com.example.android.sunshine.app.data.WeatherContract;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* RemoteViewsService controlling the data being shown in the scrollable weather detail widget
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
public class DetailWidgetRemoteViewsService extends RemoteViewsService {
|
||||
public final String LOG_TAG = DetailWidgetRemoteViewsService.class.getSimpleName();
|
||||
private static final String[] FORECAST_COLUMNS = {
|
||||
WeatherContract.WeatherEntry.TABLE_NAME + "." + WeatherContract.WeatherEntry._ID,
|
||||
WeatherContract.WeatherEntry.COLUMN_DATE,
|
||||
WeatherContract.WeatherEntry.COLUMN_WEATHER_ID,
|
||||
WeatherContract.WeatherEntry.COLUMN_SHORT_DESC,
|
||||
WeatherContract.WeatherEntry.COLUMN_MAX_TEMP,
|
||||
WeatherContract.WeatherEntry.COLUMN_MIN_TEMP
|
||||
};
|
||||
// these indices must match the projection
|
||||
static final int INDEX_WEATHER_ID = 0;
|
||||
static final int INDEX_WEATHER_DATE = 1;
|
||||
static final int INDEX_WEATHER_CONDITION_ID = 2;
|
||||
static final int INDEX_WEATHER_DESC = 3;
|
||||
static final int INDEX_WEATHER_MAX_TEMP = 4;
|
||||
static final int INDEX_WEATHER_MIN_TEMP = 5;
|
||||
|
||||
@Override
|
||||
public RemoteViewsFactory onGetViewFactory(Intent intent) {
|
||||
return new RemoteViewsFactory() {
|
||||
private Cursor data = null;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDataSetChanged() {
|
||||
if (data != null) {
|
||||
data.close();
|
||||
}
|
||||
// This method is called by the app hosting the widget (e.g., the launcher)
|
||||
// However, our ContentProvider is not exported so it doesn't have access to the
|
||||
// data. Therefore we need to clear (and finally restore) the calling identity so
|
||||
// that calls use our process and permission
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
String location = Utility.getPreferredLocation(DetailWidgetRemoteViewsService.this);
|
||||
Uri weatherForLocationUri = WeatherContract.WeatherEntry
|
||||
.buildWeatherLocationWithStartDate(location, System.currentTimeMillis());
|
||||
data = getContentResolver().query(weatherForLocationUri,
|
||||
FORECAST_COLUMNS,
|
||||
null,
|
||||
null,
|
||||
WeatherContract.WeatherEntry.COLUMN_DATE + " ASC");
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (data != null) {
|
||||
data.close();
|
||||
data = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return data == null ? 0 : data.getCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteViews getViewAt(int position) {
|
||||
if (position == AdapterView.INVALID_POSITION ||
|
||||
data == null || !data.moveToPosition(position)) {
|
||||
return null;
|
||||
}
|
||||
RemoteViews views = new RemoteViews(getPackageName(),
|
||||
R.layout.widget_detail_list_item);
|
||||
int weatherId = data.getInt(INDEX_WEATHER_CONDITION_ID);
|
||||
int weatherArtResourceId = Utility.getIconResourceForWeatherCondition(weatherId);
|
||||
Bitmap weatherArtImage = null;
|
||||
if ( !Utility.usingLocalGraphics(DetailWidgetRemoteViewsService.this) ) {
|
||||
String weatherArtResourceUrl = Utility.getArtUrlForWeatherCondition(
|
||||
DetailWidgetRemoteViewsService.this, weatherId);
|
||||
try {
|
||||
weatherArtImage = Glide.with(DetailWidgetRemoteViewsService.this)
|
||||
.load(weatherArtResourceUrl)
|
||||
.asBitmap()
|
||||
.error(weatherArtResourceId)
|
||||
.into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
Log.e(LOG_TAG, "Error retrieving large icon from " + weatherArtResourceUrl, e);
|
||||
}
|
||||
}
|
||||
String description = data.getString(INDEX_WEATHER_DESC);
|
||||
long dateInMillis = data.getLong(INDEX_WEATHER_DATE);
|
||||
String formattedDate = Utility.getFriendlyDayString(
|
||||
DetailWidgetRemoteViewsService.this, dateInMillis, false);
|
||||
double maxTemp = data.getDouble(INDEX_WEATHER_MAX_TEMP);
|
||||
double minTemp = data.getDouble(INDEX_WEATHER_MIN_TEMP);
|
||||
String formattedMaxTemperature =
|
||||
Utility.formatTemperature(DetailWidgetRemoteViewsService.this, maxTemp);
|
||||
String formattedMinTemperature =
|
||||
Utility.formatTemperature(DetailWidgetRemoteViewsService.this, minTemp);
|
||||
if (weatherArtImage != null) {
|
||||
views.setImageViewBitmap(R.id.widget_icon, weatherArtImage);
|
||||
} else {
|
||||
views.setImageViewResource(R.id.widget_icon, weatherArtResourceId);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
|
||||
setRemoteContentDescription(views, description);
|
||||
}
|
||||
views.setTextViewText(R.id.widget_date, formattedDate);
|
||||
views.setTextViewText(R.id.widget_description, description);
|
||||
views.setTextViewText(R.id.widget_high_temperature, formattedMaxTemperature);
|
||||
views.setTextViewText(R.id.widget_low_temperature, formattedMinTemperature);
|
||||
|
||||
final Intent fillInIntent = new Intent();
|
||||
String locationSetting =
|
||||
Utility.getPreferredLocation(DetailWidgetRemoteViewsService.this);
|
||||
Uri weatherUri = WeatherContract.WeatherEntry.buildWeatherLocationWithDate(
|
||||
locationSetting,
|
||||
dateInMillis);
|
||||
fillInIntent.setData(weatherUri);
|
||||
views.setOnClickFillInIntent(R.id.widget_list_item, fillInIntent);
|
||||
return views;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
|
||||
private void setRemoteContentDescription(RemoteViews views, String description) {
|
||||
views.setContentDescription(R.id.widget_icon, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteViews getLoadingView() {
|
||||
return new RemoteViews(getPackageName(), R.layout.widget_detail_list_item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (data.moveToPosition(position))
|
||||
return data.getLong(INDEX_WEATHER_ID);
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStableIds() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable-nodpi/widget_preview_detail.png
Normal file
BIN
app/src/main/res/drawable-nodpi/widget_preview_detail.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 99 KiB |
58
app/src/main/res/layout/widget_detail.xml
Normal file
58
app/src/main/res/layout/widget_detail.xml
Normal file
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/widget_margin">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/widget"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/abc_action_bar_default_height_material"
|
||||
android:background="@color/primary">
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:layout_gravity="center"
|
||||
android:src="@drawable/ic_logo"
|
||||
/>
|
||||
</FrameLayout>
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@color/primary_light">
|
||||
<ListView
|
||||
android:id="@+id/widget_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:divider="@null"
|
||||
android:dividerHeight="0dp"
|
||||
tools:listitem="@layout/widget_detail_list_item"/>
|
||||
<TextView
|
||||
android:id="@+id/widget_empty"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:fontFamily="sans-serif-condensed"
|
||||
android:textAppearance="?android:textAppearanceLarge"
|
||||
android:text="@string/empty_forecast_list"/>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
93
app/src/main/res/layout/widget_detail_list_item.xml
Normal file
93
app/src/main/res/layout/widget_detail_list_item.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<!-- Layout for weather forecast list item for future day (not today) -->
|
||||
<!-- Make the background of our selector a non-transparent color -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_list_item"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/touch_selector_white"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:paddingLeft="@dimen/abc_list_item_padding_horizontal_material"
|
||||
android:paddingRight="@dimen/abc_list_item_padding_horizontal_material"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_icon"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="@dimen/list_icon"
|
||||
android:layout_height="@dimen/list_icon"
|
||||
android:layout_marginRight="@dimen/abc_list_item_padding_horizontal_material"
|
||||
android:layout_marginEnd="@dimen/abc_list_item_padding_horizontal_material"
|
||||
tools:src="@drawable/ic_clear"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="7"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||
android:textColor="@color/primary_text"
|
||||
android:text="Today, May 21"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="@color/secondary_text"
|
||||
tools:text="@string/condition_500"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_high_temperature"
|
||||
android:layout_width="@dimen/forecast_widget_text_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="right"
|
||||
android:layout_marginRight="@dimen/forecast_temperature_space"
|
||||
android:layout_marginEnd="@dimen/forecast_temperature_space"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="@color/primary_text"
|
||||
android:textSize="@dimen/forecast_widget_text_size"
|
||||
tools:text="10"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_low_temperature"
|
||||
android:layout_width="@dimen/forecast_widget_text_width"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="right"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="@color/forecast_low_text"
|
||||
android:textSize="@dimen/forecast_widget_text_size"
|
||||
tools:text="7"/>
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
4
app/src/main/res/values-sw600dp/bools.xml
Normal file
4
app/src/main/res/values-sw600dp/bools.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="use_detail_activity">false</bool>
|
||||
</resources>
|
||||
4
app/src/main/res/values-v11/bools.xml
Normal file
4
app/src/main/res/values-v11/bools.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="widget_detail_enabled">true</bool>
|
||||
</resources>
|
||||
5
app/src/main/res/values/bools.xml
Normal file
5
app/src/main/res/values/bools.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="widget_detail_enabled">false</bool>
|
||||
<bool name="use_detail_activity">true</bool>
|
||||
</resources>
|
||||
@@ -59,4 +59,19 @@
|
||||
<dimen name="widget_today_min_resize_width">40dp</dimen>
|
||||
<dimen name="widget_today_min_resize_height">@dimen/widget_today_default_height</dimen>
|
||||
<dimen name="widget_today_large_width">220dp</dimen>
|
||||
|
||||
<dimen name="widget_detail_default_width">250dp</dimen>
|
||||
<dimen name="widget_detail_default_height">180dp</dimen>
|
||||
<dimen name="widget_detail_min_resize_width">220dp</dimen>
|
||||
<dimen name="widget_detail_min_resize_height">@dimen/widget_detail_default_height</dimen>
|
||||
|
||||
<!-- Text Sizes - We are using DP here rather than SP because these are already large
|
||||
font sizes, and going larger will cause lots of view problems. This is only for
|
||||
the large forecast numbers in the forecast list -->
|
||||
<dimen name="forecast_widget_text_size">24dp</dimen>
|
||||
|
||||
<!-- This is an odd width, but we're trying to match the font closely to keep things working
|
||||
on devices that don't yet have Roboto -->
|
||||
<dimen name="forecast_widget_text_width">38dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
|
||||
<!-- Strings related to Widgets -->
|
||||
<string name="title_widget_today">Sunshine Today</string>
|
||||
<string name="title_widget_detail">Sunshine Details</string>
|
||||
|
||||
<!-- Empty Weather Database -->
|
||||
<string name="empty_forecast_list">No Weather Information Available</string>
|
||||
|
||||
29
app/src/main/res/xml/widget_info_detail.xml
Normal file
29
app/src/main/res/xml/widget_info_detail.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2015 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:initialKeyguardLayout="@layout/widget_detail"
|
||||
android:initialLayout="@layout/widget_detail"
|
||||
android:minHeight="@dimen/widget_detail_default_height"
|
||||
android:minResizeHeight="@dimen/widget_detail_min_resize_height"
|
||||
android:minResizeWidth="@dimen/widget_detail_min_resize_width"
|
||||
android:minWidth="@dimen/widget_detail_default_width"
|
||||
android:previewImage="@drawable/widget_preview_detail"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="0"
|
||||
android:widgetCategory="home_screen|keyguard"
|
||||
tools:ignore="UnusedAttribute" />
|
||||
Reference in New Issue
Block a user