favoritelist

This commit is contained in:
danijoo
2015-11-01 13:09:05 +01:00
parent 73445906ed
commit 41abda5031
29 changed files with 327 additions and 24 deletions

View File

@@ -1,5 +1,16 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'android-apt'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
android { android {
compileSdkVersion 23 compileSdkVersion 23
buildToolsVersion "23.0.1" buildToolsVersion "23.0.1"
@@ -39,4 +50,7 @@ dependencies {
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.jakewharton:butterknife:7.0.1' compile 'com.jakewharton:butterknife:7.0.1'
apt 'net.simonvt.schematic:schematic-compiler:0.6.3'
compile 'net.simonvt.schematic:schematic:0.6.3'
} }

View File

@@ -20,6 +20,11 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider
android:authorities="net.headlezz.udacityproject1.FavoriteProvider"
android:name="net.headlezz.udacityproject1.provider.FavoriteProvider"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@@ -5,6 +5,7 @@ import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import net.headlezz.udacityproject1.movielist.MovieListFragment;
import net.headlezz.udacityproject1.tmdbapi.Movie; import net.headlezz.udacityproject1.tmdbapi.Movie;
/** /**

View File

@@ -19,6 +19,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import net.headlezz.udacityproject1.favorites.FavoriteProviderHelper;
import net.headlezz.udacityproject1.tmdbapi.Movie; import net.headlezz.udacityproject1.tmdbapi.Movie;
import net.headlezz.udacityproject1.tmdbapi.TMDBApi; import net.headlezz.udacityproject1.tmdbapi.TMDBApi;
import net.headlezz.udacityproject1.tmdbapi.Video; import net.headlezz.udacityproject1.tmdbapi.Video;
@@ -51,6 +52,9 @@ public class MovieDetailsFragment extends Fragment implements Callback<VideoList
Movie mMovie; Movie mMovie;
VideoList mVideoList; VideoList mVideoList;
FavoriteProviderHelper mProvderHelper;
boolean isFavorite;
/** /**
* Hold a reference to the videolist download call so * Hold a reference to the videolist download call so
* it can be canceled when stopping * it can be canceled when stopping
@@ -69,9 +73,11 @@ public class MovieDetailsFragment extends Fragment implements Callback<VideoList
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
mProvderHelper = new FavoriteProviderHelper(getContext().getContentResolver());
if(getArguments().containsKey(BUNDLE_ARG_MOVIE)) { if(getArguments().containsKey(BUNDLE_ARG_MOVIE)) {
mMovie = getArguments().getParcelable(BUNDLE_ARG_MOVIE); mMovie = getArguments().getParcelable(BUNDLE_ARG_MOVIE);
Log.d(TAG, "Bundled movie: " + mMovie.getTitle()); Log.d(TAG, "Bundled movie: " + mMovie.getTitle());
isFavorite = mProvderHelper.isFavorite(mMovie); // TODO async?
} else } else
throw new RuntimeException(TAG + " opened without bundled movie!"); throw new RuntimeException(TAG + " opened without bundled movie!");
@@ -165,6 +171,8 @@ public class MovieDetailsFragment extends Fragment implements Callback<VideoList
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.movie_details_menu, menu); inflater.inflate(R.menu.movie_details_menu, menu);
MenuItem favItem = menu.findItem(R.id.action_favorite);
favItem.setIcon(isFavorite ? R.drawable.ic_action_star_10 : R.drawable.ic_action_star_0);
} }
@Override @Override
@@ -172,11 +180,26 @@ public class MovieDetailsFragment extends Fragment implements Callback<VideoList
if(item.getItemId() == R.id.action_share) { if(item.getItemId() == R.id.action_share) {
shareTrailer(); shareTrailer();
return true; return true;
} else if(item.getItemId() == R.id.action_favorite) {
switchFavorite();
item.setIcon(isFavorite ? R.drawable.ic_action_star_10 : R.drawable.ic_action_star_0);
return true;
} }
else else
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
/**
* removes or adds the movie to stored favorites
*/
private void switchFavorite() { // TODO async?
if(isFavorite)
mProvderHelper.removeFavorite(mMovie);
else
mProvderHelper.addFavorite(mMovie);
isFavorite = !isFavorite;
}
/** /**
* Fires an intent to share the trailer url of this movie * Fires an intent to share the trailer url of this movie
* Code from http://www.techrepublic.com/blog/software-engineer/get-social-using-android-intents-to-share-a-link/ * Code from http://www.techrepublic.com/blog/software-engineer/get-social-using-android-intents-to-share-a-link/

View File

@@ -0,0 +1,21 @@
package net.headlezz.udacityproject1.favorites;
import net.simonvt.schematic.annotation.DataType;
import net.simonvt.schematic.annotation.NotNull;
import net.simonvt.schematic.annotation.PrimaryKey;
public interface FavoriteColumns {
@DataType(DataType.Type.INTEGER) @PrimaryKey String _ID = "_id";
@DataType(DataType.Type.TEXT) @NotNull String TITLE = "title";
@DataType(DataType.Type.TEXT) @NotNull String POSTERPATH = "posterpath";
@DataType(DataType.Type.REAL) @NotNull String AVRATING = "avrating";
@DataType(DataType.Type.INTEGER) @NotNull String RELEASEDATE = "release";
@DataType(DataType.Type.TEXT) @NotNull String OVERVIEW = "overview";
}

View File

@@ -0,0 +1,12 @@
package net.headlezz.udacityproject1.favorites;
import net.simonvt.schematic.annotation.Database;
import net.simonvt.schematic.annotation.Table;
@Database(version=FavoriteDatabase.VERSION, packageName="net.headlezz.udacityproject1.provider")
public class FavoriteDatabase {
public static final int VERSION = 1;
@Table(FavoriteColumns.class) public static final String favorites = "favorites";
}

View File

@@ -0,0 +1,24 @@
package net.headlezz.udacityproject1.favorites;
import android.net.Uri;
import net.simonvt.schematic.annotation.ContentProvider;
import net.simonvt.schematic.annotation.ContentUri;
import net.simonvt.schematic.annotation.TableEndpoint;
@ContentProvider(authority = FavoriteProvider.AUTHORITY, database = FavoriteDatabase.class, packageName="net.headlezz.udacityproject1.provider")
public class FavoriteProvider {
public static final String AUTHORITY = "net.headlezz.udacityproject1.FavoriteProvider";
static final Uri BASE_CONTENT_URI = Uri.parse("content://" + AUTHORITY);
@TableEndpoint(table = FavoriteDatabase.favorites)
public static class Favorites {
@ContentUri(
path = "favorites",
type = "vnd.android.cursor.dir/list",
defaultSort = FavoriteColumns.TITLE + " ASC")
public static final Uri FAVORITES_URI = Uri.parse("content://" + AUTHORITY + "/favorites");
}
}

View File

@@ -0,0 +1,54 @@
package net.headlezz.udacityproject1.favorites;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import net.headlezz.udacityproject1.tmdbapi.Movie;
public class FavoriteProviderHelper {
private ContentResolver mResolver;
public FavoriteProviderHelper(ContentResolver resolver) {
mResolver = resolver;
}
public Cursor getAllFavorites() {
String[] projection = {
FavoriteColumns._ID,
FavoriteColumns.TITLE,
FavoriteColumns.POSTERPATH,
FavoriteColumns.AVRATING,
FavoriteColumns.RELEASEDATE,
FavoriteColumns.OVERVIEW
};
return mResolver.query(FavoriteProvider.Favorites.FAVORITES_URI, projection, null, null, null);
}
public boolean isFavorite(Movie movie) {
String[] projection = {FavoriteColumns._ID};
String selection = FavoriteColumns.TITLE + "=?";
String[] selectionArgs = new String[]{movie.getTitle()};
Cursor cursor = mResolver.query(FavoriteProvider.Favorites.FAVORITES_URI, projection, selection, selectionArgs, null);
if (cursor != null && cursor.getCount() > 0)
return true;
return false;
}
public void addFavorite(Movie movie) {
ContentValues values = new ContentValues();
values.put(FavoriteColumns._ID, movie.getId());
values.put(FavoriteColumns.TITLE, movie.getTitle());
values.put(FavoriteColumns.OVERVIEW, movie.getOverview());
values.put(FavoriteColumns.POSTERPATH, movie.getPosterPath());
values.put(FavoriteColumns.AVRATING, movie.getAvRating());
values.put(FavoriteColumns.RELEASEDATE, movie.getReleaseDate().getTime());
mResolver.insert(FavoriteProvider.Favorites.FAVORITES_URI, values);
}
public void removeFavorite(Movie movie) {
mResolver.delete(FavoriteProvider.Favorites.FAVORITES_URI, FavoriteColumns._ID + "=?", new String[]{String.valueOf(movie.getId())});
}
}

View File

@@ -1,4 +1,4 @@
package net.headlezz.udacityproject1; package net.headlezz.udacityproject1.movielist;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -6,20 +6,20 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import net.headlezz.udacityproject1.MovieNavigation;
import net.headlezz.udacityproject1.R;
import net.headlezz.udacityproject1.tmdbapi.Movie; import net.headlezz.udacityproject1.tmdbapi.Movie;
import net.headlezz.udacityproject1.tmdbapi.MovieList;
import net.headlezz.udacityproject1.tmdbapi.TMDBApi; import net.headlezz.udacityproject1.tmdbapi.TMDBApi;
import java.util.List; /**
* Abstract class for shwoing a list of movie posters
public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.MovieViewHolder>{ */
public abstract class AbstractMovieListAdapter extends RecyclerView.Adapter<AbstractMovieListAdapter.MovieViewHolder> {
MovieNavigation mMovieNavigation; MovieNavigation mMovieNavigation;
List<Movie> mMovies;
public MovieListAdapter(MovieNavigation mn, MovieList movieList) { public AbstractMovieListAdapter(MovieNavigation mn) {
mMovieNavigation = mn; mMovieNavigation = mn;
mMovies = movieList.getList();
} }
@Override @Override
@@ -30,13 +30,14 @@ public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.Movi
@Override @Override
public void onBindViewHolder(MovieViewHolder holder, int position) { public void onBindViewHolder(MovieViewHolder holder, int position) {
holder.setMovie(mMovies.get(position)); holder.setMovie(getMovieForPosition(position));
} }
@Override /**
public int getItemCount() { * @param position of the list
return mMovies.size(); * @return the movie displayed for the given position
} */
protected abstract Movie getMovieForPosition(int position);
/** /**
* Viewholder for the movie items * Viewholder for the movie items
@@ -62,5 +63,4 @@ public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.Movi
TMDBApi.loadPoster(ivPoster, movie); TMDBApi.loadPoster(ivPoster, movie);
} }
} }
} }

View File

@@ -0,0 +1,82 @@
package net.headlezz.udacityproject1.movielist;
import android.database.Cursor;
import android.database.DataSetObserver;
import net.headlezz.udacityproject1.MovieNavigation;
import net.headlezz.udacityproject1.favorites.FavoriteColumns;
import net.headlezz.udacityproject1.tmdbapi.Movie;
/**
* MovieListAdapter implementation for showing movies from a cursor
* Modified version of https://gist.github.com/skyfishjy/443b7448f59be978bc59
*/
public class CursorMovieListAdapter extends AbstractMovieListAdapter {
private Cursor mCursor;
private boolean mDataValid;
private int mRowIdColumn;
private DataSetObserver mDataSetObserver;
public CursorMovieListAdapter(Cursor cursor, MovieNavigation mn) {
super(mn);
mCursor = cursor;
mDataValid = cursor != null;
mRowIdColumn = mDataValid ? mCursor.getColumnIndex(FavoriteColumns._ID) : -1;
mDataSetObserver = new NotifyingDataSetObserver();
if (mCursor != null) {
mCursor.registerDataSetObserver(mDataSetObserver);
}
}
@Override
protected Movie getMovieForPosition(int position) {
if (!mDataValid) {
throw new IllegalStateException("this should only be called when the cursor is valid");
}
if (!mCursor.moveToPosition(position)) {
throw new IllegalStateException("couldn't move cursor to position " + position);
}
return Movie.fromCurrentCursorPosition(mCursor);
}
@Override
public int getItemCount() {
if (mDataValid && mCursor != null) {
return mCursor.getCount();
}
return 0;
}
@Override
public long getItemId(int position) {
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getLong(mRowIdColumn);
}
return 0;
}
@Override
public void setHasStableIds(boolean hasStableIds) {
super.setHasStableIds(true);
}
private class NotifyingDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
super.onChanged();
mDataValid = true;
notifyDataSetChanged();
}
@Override
public void onInvalidated() {
super.onInvalidated();
mDataValid = false;
notifyDataSetChanged();
//There is no notifyDataSetInvalidated() method in RecyclerView.Adapter
}
}
}

View File

@@ -0,0 +1,30 @@
package net.headlezz.udacityproject1.movielist;
import net.headlezz.udacityproject1.MovieNavigation;
import net.headlezz.udacityproject1.tmdbapi.Movie;
import net.headlezz.udacityproject1.tmdbapi.MovieList;
import java.util.List;
/**
* MovieListAdapter implementation for showing movies from a list of movies
*/
public class MovieListAdapter extends AbstractMovieListAdapter {
List<Movie> mMovies;
public MovieListAdapter(MovieNavigation mn, MovieList movieList) {
super(mn);
mMovies = movieList.getList();
}
@Override
public int getItemCount() {
return mMovies.size();
}
@Override
protected Movie getMovieForPosition(int position) {
return mMovies.get(position);
}
}

View File

@@ -1,7 +1,8 @@
package net.headlezz.udacityproject1; package net.headlezz.udacityproject1.movielist;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.database.Cursor;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.GridLayoutManager;
@@ -16,6 +17,9 @@ import android.view.ViewGroup;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import net.headlezz.udacityproject1.MovieNavigation;
import net.headlezz.udacityproject1.R;
import net.headlezz.udacityproject1.favorites.FavoriteProviderHelper;
import net.headlezz.udacityproject1.tmdbapi.MovieList; import net.headlezz.udacityproject1.tmdbapi.MovieList;
import net.headlezz.udacityproject1.tmdbapi.TMDBApi; import net.headlezz.udacityproject1.tmdbapi.TMDBApi;
@@ -42,7 +46,11 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
* The currently selected sorting order of the list * The currently selected sorting order of the list
* @see TMDBApi * @see TMDBApi
*/ */
private int mSortingOrder = TMDBApi.SORT_ORDER_MOST_POPULAR; private int mSortingOrder = SORT_ORDER_MOST_POPULAR;
public static final int SORT_ORDER_MOST_POPULAR = TMDBApi.SORT_ORDER_MOST_POPULAR;
public static final int SORT_ORDER_HIGHEST_RATED = TMDBApi.SORT_ORDER_HIGHEST_RATED;
public static final int SORT_ORDER_FAVORITES = 3;
private MovieList mMovieList; private MovieList mMovieList;
@@ -120,11 +128,15 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_sort_popularity: case R.id.action_sort_popularity:
mSortingOrder = TMDBApi.SORT_ORDER_MOST_POPULAR; mSortingOrder = SORT_ORDER_MOST_POPULAR;
loadMovieList(); loadMovieList();
return true; return true;
case R.id.action_sort_rating: case R.id.action_sort_rating:
mSortingOrder = TMDBApi.SORT_ORDER_HIGHEST_RATED; mSortingOrder = SORT_ORDER_HIGHEST_RATED;
loadMovieList();
return true;
case R.id.action_sort_favorites:
mSortingOrder = SORT_ORDER_FAVORITES;
loadMovieList(); loadMovieList();
return true; return true;
default: default:
@@ -138,9 +150,18 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
*/ */
private void loadMovieList() { private void loadMovieList() {
stopLoadingMovies(); // stop any running query before starting a new one stopLoadingMovies(); // stop any running query before starting a new one
if(mSortingOrder != SORT_ORDER_FAVORITES) {
mProgressBar.setVisibility(View.VISIBLE); mProgressBar.setVisibility(View.VISIBLE);
mMovieListCall = TMDBApi.discoverMovies(mSortingOrder, getString(R.string.api_key)); mMovieListCall = TMDBApi.discoverMovies(mSortingOrder, getString(R.string.api_key));
mMovieListCall.enqueue(this); mMovieListCall.enqueue(this);
} else
loadMovieListFromFavorites();
}
private void loadMovieListFromFavorites() {
// TODO async?
Cursor cursor = new FavoriteProviderHelper(getContext().getContentResolver()).getAllFavorites();
mMovieGridView.setAdapter(new CursorMovieListAdapter(cursor,(MovieNavigation) getActivity()));
} }
@Override @Override
@@ -190,7 +211,7 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
outState.putInt("sort_order", mSortingOrder); outState.putInt("sort_order", mSortingOrder);
if(mMovieList != null) if(mMovieList != null && mSortingOrder != SORT_ORDER_FAVORITES)
outState.putParcelable("movielist", mMovieList); outState.putParcelable("movielist", mMovieList);
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
} }

View File

@@ -1,10 +1,13 @@
package net.headlezz.udacityproject1.tmdbapi; package net.headlezz.udacityproject1.tmdbapi;
import android.database.Cursor;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import net.headlezz.udacityproject1.favorites.FavoriteColumns;
import java.util.Date; import java.util.Date;
/** /**
@@ -55,6 +58,17 @@ public class Movie implements Parcelable {
return id; return id;
} }
public static Movie fromCurrentCursorPosition(Cursor cursor) {
Movie movie = new Movie();
movie.id = cursor.getLong(cursor.getColumnIndex(FavoriteColumns._ID));
movie.title = cursor.getString(cursor.getColumnIndex(FavoriteColumns.TITLE));
movie.posterPath = cursor.getString(cursor.getColumnIndex(FavoriteColumns.POSTERPATH));
movie.overview = cursor.getString(cursor.getColumnIndex(FavoriteColumns.OVERVIEW));
movie.releaseDate = new Date(cursor.getLong(cursor.getColumnIndex(FavoriteColumns.RELEASEDATE)));
movie.avRating = cursor.getDouble(cursor.getColumnIndex(FavoriteColumns.AVRATING));
return movie;
}
/**** Parcelable stuff *****/ /**** Parcelable stuff *****/
@Override @Override

Binary file not shown.

After

Width:  |  Height:  |  Size: 765 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 539 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 932 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 737 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -2,4 +2,5 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:icon="@drawable/ic_action_share" android:id="@+id/action_share" android:title="Share" app:showAsAction="ifRoom" /> <item android:icon="@drawable/ic_action_share" android:id="@+id/action_share" android:title="Share" app:showAsAction="ifRoom" />
<item android:id="@+id/action_favorite" android:title="Add/Remove Favorite" app:showAsAction="ifRoom" />
</menu> </menu>

View File

@@ -1,6 +1,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity"> xmlns:tools="http://schemas.android.com/tools" tools:context=".MainActivity">
<item android:id="@+id/action_sort_popularity" android:title="Sort by popularity" app:showAsAction="never" /> <item android:id="@+id/action_sort_popularity" android:title="Popular movies" app:showAsAction="never" />
<item android:id="@+id/action_sort_rating" android:title="Sort by av. rating" app:showAsAction="never" /> <item android:id="@+id/action_sort_rating" android:title="Highest av. rating" app:showAsAction="never" />
<item android:id="@+id/action_sort_favorites" android:title="Favorites" app:showAsAction="never" />
</menu> </menu>