Skip to content

Master #63

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

Open
wants to merge 29 commits into
base: 7.01_Sunshines_First_Widget
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
7ec7108
Load real data into the 1x1 Today widget, moving our work to an Inten…
DAGalpin May 25, 2015
5628b0b
Allow the Today widget to resize horizontally, change the default siz…
DAGalpin May 25, 2015
52efa53
Integrate Detail widget, showing a full list of weather information a…
DAGalpin May 25, 2015
089e1a7
Add a Muzei extension, changing the wallpaper with the weather
DAGalpin May 25, 2015
7c67c40
"Merged from 7.01_Sunshines_First_Widget"
DAGalpin Aug 26, 2015
f9ea707
"Merged from 7.02_Get_Real_Data"
DAGalpin Aug 26, 2015
c6e7897
"Merged from 7.03_Choose_Your_Size"
DAGalpin Aug 26, 2015
90ae8a3
"Merged from 7.04_Integrating_the_Detail_Widget"
DAGalpin Aug 26, 2015
f662d3f
"Merged from 7.01_Sunshines_First_Widget"
DAGalpin Aug 27, 2015
4ff9eaa
"Merged from 7.02_Get_Real_Data"
DAGalpin Aug 27, 2015
638194f
"Merged from 7.03_Choose_Your_Size"
DAGalpin Aug 27, 2015
3adc6bd
"Merged from 7.04_Integrating_the_Detail_Widget"
DAGalpin Aug 27, 2015
6752c12
Creating a new widget layout for the location text preference
joannasmith Jun 16, 2015
5ac9eb2
Setting up the required framework for the PlacePicker integration
joannasmith Jun 16, 2015
cc998e7
Providing the answer for the PlacePicker implementation quiz
joannasmith Jun 17, 2015
fac9721
Updating the syncadapter to use the latlng variable stored in sharedp…
joannasmith Jun 17, 2015
03184fd
Solution to syncadapter quiz
joannasmith Jun 16, 2015
3610a56
Moving the latlng wipe to the correct location
joannasmith Jun 16, 2015
2e8fe23
Adding attributions
joannasmith Jun 16, 2015
e678406
Merge pull request #42 from DAGalpin/8.00_Places_API_Start
cdlei Dec 2, 2015
b1fe6b6
Merge pull request #43 from DAGalpin/8.01_New_Location_Widget_Layout
cdlei Dec 2, 2015
07334f9
Merge pull request #44 from DAGalpin/8.02_PlacePicker_Integration_Fra…
cdlei Dec 2, 2015
d0eed2e
Merge pull request #49 from DAGalpin/8.03_PlacePicker_Implementation_…
cdlei Dec 3, 2015
3b0bf95
Merge pull request #45 from DAGalpin/8.04_Updating_Syncadapter_With_L…
cdlei Dec 3, 2015
f4eb6ec
Merge pull request #46 from DAGalpin/8.05_Updating_SyncAdapter_Solution
cdlei Dec 3, 2015
92291cb
Merge pull request #47 from DAGalpin/8.06_Moving_The_LatLng_Wipe
cdlei Dec 3, 2015
9c5b0a2
Merge pull request #48 from DAGalpin/8.07_Adding_Attributions
cdlei Dec 3, 2015
745380e
Add API Key Parameter to OpenWeatherMap API Call
cdlei Oct 28, 2015
b503732
Create CODEOWNERS
SudKul Oct 21, 2021
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
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @udacity/active-public-content
5 changes: 5 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildTypes.each {
it.buildConfigField 'String', 'OPEN_WEATHER_MAP_API_KEY', MyOpenWeatherMapApiKey
}
}

dependencies {
Expand All @@ -29,5 +32,7 @@ dependencies {
compile 'com.android.support:appcompat-v7:22.2.0'
compile 'com.android.support:design:22.2.0'
compile 'com.android.support:recyclerview-v7:22.2.0'
compile 'com.google.android.apps.muzei:muzei-api:2.0'
compile 'com.google.android.gms:play-services-gcm:7.5.0'
compile 'com.google.android.gms:play-services-location:7.5.0'
}
32 changes: 32 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
android:protectionLevel="signature" />
<uses-permission android:name="com.example.android.sunshine.app.permission.C2D_MESSAGE" />

<!-- Permissions required to use the Place Picker -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand Down Expand Up @@ -113,16 +116,45 @@
<category android:name="com.example.android.sunshine.app" />
</intent-filter>
</receiver>
<!-- Muzei Extension -->
<service android:name=".muzei.WeatherMuzeiSource"
android:icon="@drawable/ic_muzei"
android:label="@string/app_name"
android:description="@string/muzei_description" >
<intent-filter>
<action android:name="com.google.android.apps.muzei.api.MuzeiArtSource" />
</intent-filter>
<meta-data android:name="color" android:value="@color/primary" />
</service>
<!-- Today Widget -->
<receiver
android:name=".widget.TodayWidgetProvider"
android:label="@string/title_widget_today" >
<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_today" />
</receiver>
<service android:name=".widget.TodayWidgetIntentService" />
<!-- Detail Widget -->
<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" />
<service
android:name="gcm.MyGcmListenerService"
android:exported="false" >
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,16 @@ public void onBindViewHolder(ForecastAdapterViewHolder forecastAdapterViewHolder
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) ) {
Expand All @@ -154,7 +157,7 @@ public void onBindViewHolder(ForecastAdapterViewHolder forecastAdapterViewHolder
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
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;
Expand Down Expand Up @@ -55,12 +54,11 @@
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";

Expand Down Expand Up @@ -196,7 +194,6 @@ public void onClick(Long date, ForecastAdapter.ForecastAdapterViewHolder vh) {
locationSetting, date),
vh
);
mPosition = vh.getAdapterPosition();
}
}, emptyView, mChoiceMode);

Expand Down Expand Up @@ -246,11 +243,6 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
// 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);
}

Expand Down Expand Up @@ -304,11 +296,6 @@ private void openPreferredLocationInMap() {
@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);
}
Expand Down Expand Up @@ -340,11 +327,6 @@ public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
@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();
Expand All @@ -356,11 +338,27 @@ public boolean onPreDraw() {
// 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();
Expand Down Expand Up @@ -396,6 +394,10 @@ public void setUseTodayLayout(boolean useTodayLayout) {
}
}

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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.example.android.sunshine.app;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
Expand All @@ -24,9 +25,17 @@
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import com.google.android.gms.common.GooglePlayServicesRepairableException;
import com.google.android.gms.location.places.ui.PlacePicker;

public class LocationEditTextPreference extends EditTextPreference {
static final private int DEFAULT_MINIMUM_LOCATION_LENGTH = 2;
private int mMinLength;
Expand All @@ -42,8 +51,57 @@ public LocationEditTextPreference(Context context, AttributeSet attrs) {
} finally {
a.recycle();
}

// Check to see if Google Play services is available. The Place Picker API is available
// through Google Play services, so if this is false, we'll just carry on as though this
// feature does not exist. If it is true, however, we can add a widget to our preference.
GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
int resultCode = apiAvailability.isGooglePlayServicesAvailable(getContext());
if (resultCode == ConnectionResult.SUCCESS) {
// Add the get current location widget to our location preference
setWidgetLayoutResource(R.layout.pref_current_location);
}
}

@Override
protected View onCreateView(ViewGroup parent) {
View view = super.onCreateView(parent);
View currentLocation = view.findViewById(R.id.current_location);
currentLocation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Context context = getContext();

// Launch the Place Picker so that the user can specify their location, and then
// return the result to SettingsActivity.
PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();


// We are in a view right now, not an activity. So we need to get ourselves
// an activity that we can use to start our Place Picker intent. By using
// SettingsActivity in this way, we can ensure the result of the Place Picker
// intent comes to the right place for us to process it.
Activity settingsActivity = (SettingsActivity) context;
try {
settingsActivity.startActivityForResult(
builder.build(context), SettingsActivity.PLACE_PICKER_REQUEST);

} catch (GooglePlayServicesNotAvailableException
| GooglePlayServicesRepairableException e) {
// What did you do?? This is why we check Google Play services in onResume!!!
// The difference in these exception types is the difference between pausing
// for a moment to prompt the user to update/install/enable Play services vs
// complete and utter failure.
// If you prefer to manage Google Play services dynamically, then you can do so
// by responding to these exceptions in the right moment. But I prefer a cleaner
// user experience, which is why you check all of this when the app resumes,
// and then disable/enable features based on that availability.
}
}
});

return view;
}

@Override
protected void showDialog(Bundle state) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import android.view.MenuItem;
import android.view.View;

import com.example.android.sunshine.app.data.WeatherContract;
import com.example.android.sunshine.app.gcm.RegistrationIntentService;
import com.example.android.sunshine.app.sync.SunshineSyncAdapter;
import com.google.android.gms.common.ConnectionResult;
Expand All @@ -49,6 +50,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);
Expand All @@ -64,8 +66,14 @@ protected void onCreate(Bundle savedInstanceState) {
// 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 {
Expand All @@ -76,6 +84,10 @@ protected void onCreate(Bundle savedInstanceState) {
ForecastFragment forecastFragment = ((ForecastFragment)getSupportFragmentManager()
.findFragmentById(R.id.fragment_forecast));
forecastFragment.setUseTodayLayout(!mTwoPane);
if (contentUri != null) {
forecastFragment.setInitialSelectedDate(
WeatherContract.WeatherEntry.getDateFromUri(contentUri));
}

SunshineSyncAdapter.initializeSyncAdapter(this);

Expand Down
Loading