Compare commits
21 Commits
submission
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
69baf54b46 | ||
|
|
ca930b8f88 | ||
|
|
710ffe1307 | ||
|
|
b29fb68caa | ||
|
|
ecc6bafb31 | ||
|
|
425140cdd5 | ||
|
|
6edffb2944 | ||
|
|
8ea8a27d74 | ||
|
|
6d7427e50b | ||
|
|
0c35ab6f10 | ||
|
|
35def383d8 | ||
|
|
4eedae1dd0 | ||
|
|
f9fcb4d4ff | ||
|
|
a9a7710141 | ||
|
|
41abda5031 | ||
|
|
73445906ed | ||
|
|
f8cf189d70 | ||
|
|
e88513b4d8 | ||
|
|
1f1d3ee57b | ||
|
|
c7e2afb783 | ||
|
|
f76bb32e5c |
@@ -1,5 +1,16 @@
|
||||
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 {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
@@ -17,6 +28,14 @@ android {
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'InvalidPackage'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/services/javax.annotation.processing.Processor'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -29,4 +48,9 @@ dependencies {
|
||||
compile 'com.squareup.picasso:picasso:2.5.2'
|
||||
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
|
||||
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
|
||||
|
||||
compile 'com.jakewharton:butterknife:7.0.1'
|
||||
|
||||
apt 'net.simonvt.schematic:schematic-compiler:0.6.3'
|
||||
compile 'net.simonvt.schematic:schematic:0.6.3'
|
||||
}
|
||||
|
||||
1
app/build.sbt
Normal file
@@ -0,0 +1 @@
|
||||
protifySettings
|
||||
@@ -20,6 +20,11 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:authorities="net.headlezz.udacityproject1.FavoriteProvider"
|
||||
android:name="net.headlezz.udacityproject1.provider.FavoriteProvider"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -4,7 +4,10 @@ import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
|
||||
import net.headlezz.udacityproject1.moviedetails.MovieDetailsFragment;
|
||||
import net.headlezz.udacityproject1.movielist.MovieListFragment;
|
||||
import net.headlezz.udacityproject1.tmdbapi.Movie;
|
||||
|
||||
/**
|
||||
@@ -12,10 +15,13 @@ import net.headlezz.udacityproject1.tmdbapi.Movie;
|
||||
*/
|
||||
public class MainActivity extends AppCompatActivity implements MovieNavigation, FragmentManager.OnBackStackChangedListener {
|
||||
|
||||
View detailsPlaceholderView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
detailsPlaceholderView = findViewById(R.id.main_details_placeHolderText);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
setTitle(getString(R.string.app_name));
|
||||
@@ -37,8 +43,11 @@ public class MainActivity extends AppCompatActivity implements MovieNavigation,
|
||||
*/
|
||||
public void showDetailsFragment(Movie movie) {
|
||||
MovieDetailsFragment frag = MovieDetailsFragment.newInstance(movie);
|
||||
int holderId = getResources().getBoolean(R.bool.master_detail) ? R.id.fragmentHolderDetails : R.id.fragmentHolder;
|
||||
if(detailsPlaceholderView != null)
|
||||
detailsPlaceholderView.setVisibility(View.GONE);
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.fragmentHolder, frag)
|
||||
.replace(holderId, frag)
|
||||
.addToBackStack(MovieDetailsFragment.TAG)
|
||||
.commit();
|
||||
}
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package net.headlezz.udacityproject1;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.headlezz.udacityproject1.tmdbapi.Movie;
|
||||
import net.headlezz.udacityproject1.tmdbapi.TMDBApi;
|
||||
|
||||
/**
|
||||
* Fragment to show the details of a movie
|
||||
*/
|
||||
public class MovieDetailsFragment extends Fragment {
|
||||
|
||||
public static final String TAG = MovieDetailsFragment.class.getSimpleName();
|
||||
public static final String BUNDLE_ARG_MOVIE = "movie";
|
||||
|
||||
ImageView ivPoster;
|
||||
TextView tvTitle;
|
||||
TextView tvOverview;
|
||||
TextView tvReleaseDate;
|
||||
TextView tvRating;
|
||||
|
||||
Movie mMovie;
|
||||
|
||||
public static MovieDetailsFragment newInstance(Movie movie) {
|
||||
MovieDetailsFragment frag = new MovieDetailsFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(BUNDLE_ARG_MOVIE, movie);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if(getArguments().containsKey(BUNDLE_ARG_MOVIE)) {
|
||||
mMovie = getArguments().getParcelable(BUNDLE_ARG_MOVIE);
|
||||
Log.d(TAG, "Bundled movie: " + mMovie.getTitle());
|
||||
} else
|
||||
throw new RuntimeException(TAG + " opened without bundled movie!");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_movie_details, container, false);
|
||||
ivPoster = (ImageView) view.findViewById(R.id.movie_details_ivPoster);
|
||||
tvTitle = (TextView) view.findViewById(R.id.movie_details_tvTitle);
|
||||
tvOverview = (TextView) view.findViewById(R.id.movie_details_tvOverview);
|
||||
tvReleaseDate = (TextView) view.findViewById(R.id.movie_details_tvReleaseDate);
|
||||
tvRating = (TextView) view.findViewById(R.id.movie_details_tvRating);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
TMDBApi.loadPoster(ivPoster, mMovie);
|
||||
tvTitle.setText(mMovie.getTitle());
|
||||
tvOverview.setText(mMovie.getOverview());
|
||||
String formatedDate = DateUtils.formatDateTime(tvReleaseDate.getContext(), mMovie.getReleaseDate().getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR);
|
||||
tvReleaseDate.setText(getString(R.string.movie_release_date, formatedDate));
|
||||
tvRating.setText(getString(R.string.movie_rating, mMovie.getAvRating()));
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package net.headlezz.udacityproject1.favorites;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
import net.headlezz.udacityproject1.tmdbapi.Movie;
|
||||
|
||||
/**
|
||||
* Helper class for accessing the Favorite database
|
||||
*/
|
||||
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._ID + "=?";
|
||||
String[] selectionArgs = new String[]{String.valueOf(movie.getId())};
|
||||
Cursor cursor = mResolver.query(FavoriteProvider.Favorites.FAVORITES_URI, projection, selection, selectionArgs, null);
|
||||
boolean isFavourite = (cursor != null && cursor.getCount() > 0);
|
||||
if(cursor != null)
|
||||
cursor.close();
|
||||
return isFavourite;
|
||||
}
|
||||
|
||||
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())});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package net.headlezz.udacityproject1.moviedetails;
|
||||
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.headlezz.udacityproject1.R;
|
||||
import net.headlezz.udacityproject1.favorites.FavoriteProviderHelper;
|
||||
import net.headlezz.udacityproject1.tmdbapi.Movie;
|
||||
import net.headlezz.udacityproject1.tmdbapi.ReviewList;
|
||||
import net.headlezz.udacityproject1.tmdbapi.TMDBApi;
|
||||
import net.headlezz.udacityproject1.tmdbapi.Video;
|
||||
import net.headlezz.udacityproject1.tmdbapi.VideoList;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
import retrofit.Call;
|
||||
import retrofit.Callback;
|
||||
import retrofit.Response;
|
||||
import retrofit.Retrofit;
|
||||
|
||||
/**
|
||||
* Fragment to show the details of a movie
|
||||
*/
|
||||
public class MovieDetailsFragment extends Fragment implements Callback<VideoList> {
|
||||
|
||||
public static final String TAG = MovieDetailsFragment.class.getSimpleName();
|
||||
public static final String BUNDLE_ARG_MOVIE = "movie";
|
||||
|
||||
@Bind(R.id.movie_details_ivPoster) ImageView ivPoster;
|
||||
@Bind(R.id.movie_details_tvTitle) TextView tvTitle;
|
||||
@Bind(R.id.movie_details_tvOverview) TextView tvOverview;
|
||||
@Bind(R.id.movie_details_tvReleaseDate) TextView tvReleaseDate;
|
||||
@Bind(R.id.movie_details_tvRating) TextView tvRating;
|
||||
@Bind(R.id.movie_details_trailerHolder) ViewGroup trailerHolder;
|
||||
|
||||
Movie mMovie;
|
||||
VideoList mVideoList;
|
||||
|
||||
FavoriteProviderHelper mProvderHelper;
|
||||
boolean isFavorite;
|
||||
|
||||
/**
|
||||
* Hold a reference to the videolist download call so
|
||||
* it can be canceled when stopping
|
||||
*/
|
||||
Call<VideoList> mVideoListCall;
|
||||
|
||||
public static MovieDetailsFragment newInstance(Movie movie) {
|
||||
MovieDetailsFragment frag = new MovieDetailsFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(BUNDLE_ARG_MOVIE, movie);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
mProvderHelper = new FavoriteProviderHelper(getContext().getContentResolver());
|
||||
if(getArguments().containsKey(BUNDLE_ARG_MOVIE)) {
|
||||
mMovie = getArguments().getParcelable(BUNDLE_ARG_MOVIE);
|
||||
Log.d(TAG, "Bundled movie: " + mMovie.getTitle());
|
||||
isFavorite = mProvderHelper.isFavorite(mMovie);
|
||||
} else
|
||||
throw new RuntimeException(TAG + " opened without bundled movie!");
|
||||
|
||||
if(savedInstanceState != null && savedInstanceState.containsKey("videolist"))
|
||||
mVideoList = savedInstanceState.getParcelable("videolist");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_movie_details, container, false);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
TMDBApi.loadPoster(ivPoster, mMovie);
|
||||
tvTitle.setText(mMovie.getTitle());
|
||||
tvOverview.setText(mMovie.getOverview());
|
||||
String formatedDate = DateUtils.formatDateTime(tvReleaseDate.getContext(), mMovie.getReleaseDate().getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR);
|
||||
tvReleaseDate.setText(getString(R.string.movie_release_date, formatedDate));
|
||||
tvRating.setText(getString(R.string.movie_rating, mMovie.getAvRating()));
|
||||
|
||||
if(mVideoList == null)
|
||||
loadVideoList();
|
||||
else
|
||||
showVideoList(mVideoList);
|
||||
}
|
||||
|
||||
private void loadVideoList() {
|
||||
if(mVideoListCall != null)
|
||||
mVideoListCall.cancel();
|
||||
mVideoListCall = TMDBApi.getVideosForMovie(mMovie, getString(R.string.api_key));
|
||||
mVideoListCall.enqueue(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Response<VideoList> response, Retrofit retrofit) {
|
||||
if(response.isSuccess()) {
|
||||
mVideoList = response.body();
|
||||
if(getActivity() != null)
|
||||
showVideoList(mVideoList);
|
||||
} else {
|
||||
trailerHolder.getChildAt(0).setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
trailerHolder.getChildAt(0).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@OnClick(R.id.movie_details_btReviews)
|
||||
public void onReviewButtonClick(View view) {
|
||||
final ProgressDialog dialog = new ProgressDialog(getContext());
|
||||
dialog.setIndeterminate(true);
|
||||
dialog.setMessage(getString(R.string.movie_download_review_progress));
|
||||
dialog.show();
|
||||
TMDBApi.getReviewsForMovie(mMovie, getString(R.string.api_key)).enqueue(new Callback<ReviewList>() {
|
||||
@Override
|
||||
public void onResponse(Response<ReviewList> response, Retrofit retrofit) {
|
||||
if(response.body().getReviews().size() == 0)
|
||||
Toast.makeText(getContext(), R.string.movie_download_review_not_found, Toast.LENGTH_SHORT).show();
|
||||
else {
|
||||
ReviewDialogFragment frag = ReviewDialogFragment.newInstance(response.body());
|
||||
frag.show(getChildFragmentManager(), ReviewDialogFragment.TAG);
|
||||
}
|
||||
if(dialog.isShowing())
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
if(dialog.isShowing())
|
||||
dialog.dismiss();
|
||||
Toast.makeText(getContext(), R.string.movie_download_review_fail, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* shows a list of links to youtube videos for the selected movie
|
||||
* if the videolist has no videos, the user sees a message that there are no.
|
||||
* @param videoList VideoList to show
|
||||
*/
|
||||
private void showVideoList(@NonNull VideoList videoList) {
|
||||
List<Video> videos = videoList.getVideos();
|
||||
if(videos.size() > 0) {
|
||||
trailerHolder.getChildAt(0).setVisibility(View.GONE);
|
||||
for (Video video : videoList.getVideos()) {
|
||||
View view = LayoutInflater.from(getActivity()).inflate(R.layout.movie_details_trailer_item, trailerHolder, false);
|
||||
TextView tv = (TextView) view.findViewById(R.id.trailer_item_tvName);
|
||||
tv.setText(video.getName());
|
||||
view.setTag(video);
|
||||
view.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String videoId = ((Video) view.getTag()).getKey();
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("vnd.youtube:" + videoId));
|
||||
intent.putExtra("VIDEO_ID", videoId);
|
||||
try {
|
||||
startActivity(intent);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(getActivity(), R.string.movie_share_fail, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
trailerHolder.addView(view);
|
||||
}
|
||||
} else {
|
||||
trailerHolder.getChildAt(0).setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
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
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(item.getItemId() == R.id.action_share) {
|
||||
shareTrailer();
|
||||
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
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* removes or adds the movie to stored favorites
|
||||
*/
|
||||
private void switchFavorite() {
|
||||
if(isFavorite)
|
||||
mProvderHelper.removeFavorite(mMovie);
|
||||
else
|
||||
mProvderHelper.addFavorite(mMovie);
|
||||
isFavorite = !isFavorite;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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/
|
||||
*/
|
||||
private void shareTrailer() {
|
||||
if(mVideoList == null || mVideoList.getVideos().size() == 0)
|
||||
Toast.makeText(getActivity(), R.string.movie_share_no_trailer_found, Toast.LENGTH_SHORT).show();
|
||||
else {
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.setType("text/plain");
|
||||
i.putExtra(Intent.EXTRA_TEXT, "https://youtu.be/" + mVideoList.getVideos().get(0).getKey());
|
||||
i.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.movie_share_title));
|
||||
startActivity(Intent.createChooser(i, "Share"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
if(mVideoList != null)
|
||||
outState.putParcelable("videolist", mVideoList);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
if(mVideoListCall != null)
|
||||
mVideoListCall.cancel();
|
||||
super.onStop();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package net.headlezz.udacityproject1.moviedetails;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.headlezz.udacityproject1.R;
|
||||
import net.headlezz.udacityproject1.tmdbapi.Review;
|
||||
import net.headlezz.udacityproject1.tmdbapi.ReviewList;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.OnClick;
|
||||
|
||||
public class ReviewDialogFragment extends DialogFragment {
|
||||
|
||||
public static final String TAG = ReviewDialogFragment.class.getSimpleName();
|
||||
public static final String BUNDLE_ARG_REVIEWS = "reviews";
|
||||
|
||||
ReviewList mReviews;
|
||||
int mCurrentPosition;
|
||||
|
||||
@Bind(R.id.review_tvReview)
|
||||
TextView tvReview;
|
||||
|
||||
@Bind(R.id.review_tvReviewUser)
|
||||
TextView tvReviewUser;
|
||||
|
||||
@Bind(R.id.review_btNext)
|
||||
ImageButton btNext;
|
||||
|
||||
@Bind(R.id.review_btPrevious)
|
||||
ImageButton btPrev;
|
||||
|
||||
@Bind(R.id.review_tvPosition)
|
||||
TextView tvPosition;
|
||||
|
||||
|
||||
public static ReviewDialogFragment newInstance(ReviewList reviews) {
|
||||
ReviewDialogFragment frag = new ReviewDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putParcelable(BUNDLE_ARG_REVIEWS, reviews);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if(getArguments() != null && getArguments().containsKey(BUNDLE_ARG_REVIEWS))
|
||||
mReviews = getArguments().getParcelable(BUNDLE_ARG_REVIEWS);
|
||||
else
|
||||
throw new RuntimeException("No arguments found");
|
||||
|
||||
if(savedInstanceState != null)
|
||||
mCurrentPosition = savedInstanceState.getInt("position", 0);
|
||||
else
|
||||
mCurrentPosition = 0;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View dialogView = inflater.inflate(R.layout.movie_details_review_dialog, container, false);
|
||||
ButterKnife.bind(this, dialogView);
|
||||
updateReview();
|
||||
checkButtonState();
|
||||
return dialogView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@OnClick(R.id.review_btNext)
|
||||
public void onNextClick(View view) {
|
||||
mCurrentPosition += 1;
|
||||
updateReview();
|
||||
checkButtonState();
|
||||
}
|
||||
|
||||
@OnClick(R.id.review_btPrevious)
|
||||
public void onPreviousClick(View view) {
|
||||
mCurrentPosition -= 1;
|
||||
updateReview();
|
||||
checkButtonState();
|
||||
}
|
||||
|
||||
private void updateReview() {
|
||||
Review rv = mReviews.getReviews().get(mCurrentPosition);
|
||||
tvReview.setText(rv.getContent());
|
||||
tvReviewUser.setText(rv.getAuthor());
|
||||
tvPosition.setText(String.format("%1$s/%2$s", mCurrentPosition + 1, mReviews.getReviews().size()));
|
||||
}
|
||||
|
||||
private void checkButtonState() {
|
||||
if(mCurrentPosition < 1)
|
||||
btPrev.setVisibility(View.GONE);
|
||||
else
|
||||
btPrev.setVisibility(View.VISIBLE);
|
||||
|
||||
if(mCurrentPosition >= mReviews.getReviews().size() - 1)
|
||||
btNext.setVisibility(View.GONE);
|
||||
else
|
||||
btNext.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putInt("position", mCurrentPosition);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package net.headlezz.udacityproject1;
|
||||
package net.headlezz.udacityproject1.movielist;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -6,20 +6,20 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
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.MovieList;
|
||||
import net.headlezz.udacityproject1.tmdbapi.TMDBApi;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.MovieViewHolder>{
|
||||
/**
|
||||
* Abstract class for shwoing a list of movie posters
|
||||
*/
|
||||
public abstract class AbstractMovieListAdapter extends RecyclerView.Adapter<AbstractMovieListAdapter.MovieViewHolder> {
|
||||
|
||||
MovieNavigation mMovieNavigation;
|
||||
List<Movie> mMovies;
|
||||
|
||||
public MovieListAdapter(MovieNavigation mn, MovieList movieList) {
|
||||
public AbstractMovieListAdapter(MovieNavigation mn) {
|
||||
mMovieNavigation = mn;
|
||||
mMovies = movieList.getList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -30,13 +30,14 @@ public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.Movi
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(MovieViewHolder holder, int position) {
|
||||
holder.setMovie(mMovies.get(position));
|
||||
holder.setMovie(getMovieForPosition(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mMovies.size();
|
||||
}
|
||||
/**
|
||||
* @param position of the list
|
||||
* @return the movie displayed for the given position
|
||||
*/
|
||||
protected abstract Movie getMovieForPosition(int position);
|
||||
|
||||
/**
|
||||
* Viewholder for the movie items
|
||||
@@ -62,5 +63,4 @@ public class MovieListAdapter extends RecyclerView.Adapter<MovieListAdapter.Movi
|
||||
TMDBApi.loadPoster(ivPoster, movie);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package net.headlezz.udacityproject1;
|
||||
package net.headlezz.udacityproject1.movielist;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.GridLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -16,9 +18,14 @@ import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
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.TMDBApi;
|
||||
|
||||
import butterknife.Bind;
|
||||
import butterknife.ButterKnife;
|
||||
import retrofit.Call;
|
||||
import retrofit.Callback;
|
||||
import retrofit.Response;
|
||||
@@ -38,11 +45,20 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
private static final int NUM_COLUMNS_LAND = 5;
|
||||
/**
|
||||
* The currently selected sorting order of the list
|
||||
*
|
||||
* @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;
|
||||
|
||||
@Bind(R.id.movie_list_grid)
|
||||
RecyclerView mMovieGridView;
|
||||
@Bind(R.id.movie_list_progressBar)
|
||||
ProgressBar mProgressBar;
|
||||
|
||||
/**
|
||||
@@ -60,8 +76,7 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_movie_list, container, false);
|
||||
mMovieGridView = (RecyclerView) view.findViewById(R.id.movie_list_grid);
|
||||
mProgressBar = (ProgressBar) view.findViewById(R.id.movie_list_progressBar);
|
||||
ButterKnife.bind(this, view);
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -69,9 +84,15 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
mMovieGridView.setLayoutManager(new GridLayoutManager(getActivity(), getNumColumns()));
|
||||
|
||||
if (savedInstanceState != null)
|
||||
restoreState(savedInstanceState);
|
||||
}
|
||||
|
||||
private void restoreState(@NonNull Bundle savedInstanceState) {
|
||||
mSortingOrder = savedInstanceState.getInt("sort_order");
|
||||
if (savedInstanceState.containsKey("movielist"))
|
||||
mMovieList = savedInstanceState.getParcelable("movielist");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +111,18 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
initMovieList();
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the adapter from the restored movie list if the instance state is restored
|
||||
* starts the download of the new movie list if no restored one is present
|
||||
*/
|
||||
private void initMovieList() {
|
||||
if (mMovieList == null)
|
||||
loadMovieList();
|
||||
else
|
||||
mMovieGridView.setAdapter(new MovieListAdapter((MovieNavigation) getActivity(), mMovieList));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -103,11 +135,15 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_sort_popularity:
|
||||
mSortingOrder = TMDBApi.SORT_ORDER_MOST_POPULAR;
|
||||
mSortingOrder = SORT_ORDER_MOST_POPULAR;
|
||||
loadMovieList();
|
||||
return true;
|
||||
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();
|
||||
return true;
|
||||
default:
|
||||
@@ -121,15 +157,26 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
*/
|
||||
private void loadMovieList() {
|
||||
stopLoadingMovies(); // stop any running query before starting a new one
|
||||
if (mSortingOrder != SORT_ORDER_FAVORITES) {
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mMovieListCall = TMDBApi.discoverMovies(mSortingOrder, getString(R.string.api_key));
|
||||
mMovieListCall.enqueue(this);
|
||||
} else
|
||||
loadMovieListFromFavorites();
|
||||
}
|
||||
|
||||
private void loadMovieListFromFavorites() {
|
||||
mMovieList = null;
|
||||
Cursor cursor = new FavoriteProviderHelper(getContext().getContentResolver()).getAllFavorites();
|
||||
mMovieGridView.setAdapter(new CursorMovieListAdapter(cursor, (MovieNavigation) getActivity()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Response<MovieList> response, Retrofit retrofit) {
|
||||
if(response.isSuccess())
|
||||
setListToGrid(response.body());
|
||||
if (response.isSuccess()) {
|
||||
mMovieList = response.body();
|
||||
setListToGrid(mMovieList);
|
||||
}
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@@ -171,6 +218,8 @@ public class MovieListFragment extends Fragment implements Callback<MovieList> {
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
outState.putInt("sort_order", mSortingOrder);
|
||||
if (mMovieList != null && mSortingOrder != SORT_ORDER_FAVORITES)
|
||||
outState.putParcelable("movielist", mMovieList);
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import net.headlezz.udacityproject1.favorites.FavoriteColumns;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -16,6 +19,8 @@ public class Movie implements Parcelable {
|
||||
// empty constructor for gson/retrofit
|
||||
public Movie() {}
|
||||
|
||||
long id;
|
||||
|
||||
String title;
|
||||
|
||||
@SerializedName("poster_path")
|
||||
@@ -49,6 +54,21 @@ public class Movie implements Parcelable {
|
||||
return releaseDate;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
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 *****/
|
||||
|
||||
@Override
|
||||
@@ -62,7 +82,8 @@ public class Movie implements Parcelable {
|
||||
dest.writeString(posterPath);
|
||||
dest.writeString(overview);
|
||||
dest.writeDouble(avRating);
|
||||
dest.writeLong(releaseDate.getTime());
|
||||
dest.writeLong( (releaseDate == null) ? -1 : releaseDate.getTime());
|
||||
dest.writeLong(id);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Movie> CREATOR = new Parcelable.Creator<Movie>() {
|
||||
@@ -80,7 +101,10 @@ public class Movie implements Parcelable {
|
||||
posterPath = src.readString();
|
||||
overview = src.readString();
|
||||
avRating = src.readDouble();
|
||||
releaseDate = new Date(src.readLong());
|
||||
long dateTime = src.readLong();
|
||||
if(dateTime != -1)
|
||||
releaseDate = new Date(dateTime);
|
||||
id = src.readLong();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Wrapper class for list of movies
|
||||
*/
|
||||
public class MovieList {
|
||||
public class MovieList implements Parcelable {
|
||||
|
||||
public MovieList(List<Movie> movieList) {
|
||||
this.movies = movieList;
|
||||
@@ -15,4 +20,29 @@ public class MovieList {
|
||||
|
||||
public List<Movie> getList() { return movies; }
|
||||
|
||||
/***** Parcelable implementation ******/
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeList(movies);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<MovieList> CREATOR = new Parcelable.Creator<MovieList>() {
|
||||
public MovieList createFromParcel(Parcel src) {
|
||||
return new MovieList(src);
|
||||
}
|
||||
|
||||
public MovieList[] newArray(int size) {
|
||||
return new MovieList[size];
|
||||
}
|
||||
};
|
||||
|
||||
private MovieList(Parcel src) {
|
||||
movies = src.readArrayList(Movie.class.getClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class Review implements Parcelable {
|
||||
|
||||
String author;
|
||||
String content;
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(author);
|
||||
parcel.writeString(content);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Review> CREATOR = new Parcelable.Creator<Review>() {
|
||||
public Review createFromParcel(Parcel src) {
|
||||
return new Review(src);
|
||||
}
|
||||
|
||||
public Review[] newArray(int size) {
|
||||
return new Review[size];
|
||||
}
|
||||
};
|
||||
|
||||
Review(Parcel in) {
|
||||
this.author = in.readString();
|
||||
this.content = in.readString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ReviewList implements Parcelable {
|
||||
|
||||
List<Review> reviews;
|
||||
|
||||
public ReviewList(List<Review> reviews) {
|
||||
this.reviews = reviews;
|
||||
}
|
||||
|
||||
public List<Review> getReviews() {
|
||||
return reviews;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeList(reviews);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<ReviewList> CREATOR = new Parcelable.Creator<ReviewList>() {
|
||||
public ReviewList createFromParcel(Parcel src) {
|
||||
return new ReviewList(src);
|
||||
}
|
||||
|
||||
public ReviewList[] newArray(int size) {
|
||||
return new ReviewList[size];
|
||||
}
|
||||
};
|
||||
|
||||
private ReviewList(Parcel src) {
|
||||
this.reviews = src.readArrayList(Review.class.getClassLoader());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ReviewListDeserializer implements JsonDeserializer<ReviewList> {
|
||||
@Override
|
||||
public ReviewList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
Gson gson = new Gson();
|
||||
List<Review> reviews = new ArrayList<>();
|
||||
for(JsonElement elem : json.getAsJsonObject().getAsJsonArray("results")) {
|
||||
reviews.add(gson.fromJson(elem, Review.class));
|
||||
}
|
||||
return new ReviewList(reviews);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,14 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.squareup.picasso.Picasso;
|
||||
|
||||
import net.headlezz.udacityproject1.R;
|
||||
|
||||
import retrofit.Call;
|
||||
import retrofit.GsonConverterFactory;
|
||||
import retrofit.Retrofit;
|
||||
import retrofit.http.GET;
|
||||
import retrofit.http.Headers;
|
||||
import retrofit.http.Path;
|
||||
import retrofit.http.Query;
|
||||
|
||||
/**
|
||||
@@ -37,6 +40,8 @@ public class TMDBApi {
|
||||
private static TMDBApiService buildApiService() {
|
||||
Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(MovieList.class, new MovieListDeserializer())
|
||||
.registerTypeAdapter(VideoList.class, new VideoListDeserializer())
|
||||
.registerTypeAdapter(ReviewList.class, new ReviewListDeserializer())
|
||||
.create();
|
||||
Retrofit retrofit = new Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
@@ -46,7 +51,7 @@ public class TMDBApi {
|
||||
}
|
||||
|
||||
public static void loadPoster(ImageView imageView, Movie movie) {
|
||||
Picasso.with(imageView.getContext()).load(IMAGE_BASE_URL + movie.getPosterPath()).into(imageView);
|
||||
Picasso.with(imageView.getContext()).load(IMAGE_BASE_URL + movie.getPosterPath()).error(R.drawable.poster_notfound).into(imageView);
|
||||
}
|
||||
|
||||
public static Call<MovieList> discoverMovies(int sortOrder, String apiKey) {
|
||||
@@ -61,6 +66,14 @@ public class TMDBApi {
|
||||
return apiService.discoverMovies(sort, apiKey);
|
||||
}
|
||||
|
||||
public static Call<VideoList> getVideosForMovie(Movie movie, String apiKey) {
|
||||
return apiService.getVideosForMovie(movie.getId(), apiKey);
|
||||
}
|
||||
|
||||
public static Call<ReviewList> getReviewsForMovie(Movie movie, String apiKey) {
|
||||
return apiService.getReviewsForMovie(movie.getId(), apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Api interface for retrofit
|
||||
*/
|
||||
@@ -68,6 +81,14 @@ public class TMDBApi {
|
||||
@Headers("Accept: application/json")
|
||||
@GET("discover/movie")
|
||||
Call<MovieList> discoverMovies(@Query("sort_by") String sortOrder, @Query("api_key") String apiKey);
|
||||
|
||||
@Headers("Accept: application/json")
|
||||
@GET("movie/{id}/videos")
|
||||
Call<VideoList> getVideosForMovie(@Path("id") long id, @Query("api_key") String apiKey);
|
||||
|
||||
@Headers("Accept: application/json")
|
||||
@GET("movie/{id}/reviews")
|
||||
Call<ReviewList> getReviewsForMovie(@Path("id") long id, @Query("api_key") String apiKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
public class Video implements Parcelable {
|
||||
|
||||
private String name;
|
||||
private String key;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeString(name);
|
||||
parcel.writeString(key);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<Video> CREATOR = new Parcelable.Creator<Video>() {
|
||||
public Video createFromParcel(Parcel src) {
|
||||
return new Video(src);
|
||||
}
|
||||
|
||||
public Video[] newArray(int size) {
|
||||
return new Video[size];
|
||||
}
|
||||
};
|
||||
|
||||
private Video(Parcel src) {
|
||||
name = src.readString();
|
||||
key = src.readString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A list of trailers for a movie
|
||||
*/
|
||||
public class VideoList implements Parcelable {
|
||||
|
||||
private long id;
|
||||
private List<Video> videos;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public List<Video> getVideos() {
|
||||
return videos;
|
||||
}
|
||||
|
||||
public VideoList(long id, List<Video> videos) {
|
||||
this.id = id;
|
||||
this.videos = videos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel parcel, int i) {
|
||||
parcel.writeLong(id);
|
||||
parcel.writeList(videos);
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator<VideoList> CREATOR = new Parcelable.Creator<VideoList>() {
|
||||
public VideoList createFromParcel(Parcel src) {
|
||||
return new VideoList(src);
|
||||
}
|
||||
|
||||
public VideoList[] newArray(int size) {
|
||||
return new VideoList[size];
|
||||
}
|
||||
};
|
||||
|
||||
private VideoList(Parcel src) {
|
||||
id = src.readLong();
|
||||
videos = src.readArrayList(Video.class.getClassLoader());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package net.headlezz.udacityproject1.tmdbapi;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class VideoListDeserializer implements JsonDeserializer<VideoList> {
|
||||
@Override
|
||||
public VideoList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
Gson gson = new Gson();
|
||||
List<Video> videos = new ArrayList<>();
|
||||
for(JsonElement elem : json.getAsJsonObject().getAsJsonArray("results")) {
|
||||
if(elem.getAsJsonObject().get("site").getAsString().equals("YouTube")) // filter non youtube videos out
|
||||
videos.add(gson.fromJson(elem, Video.class));
|
||||
}
|
||||
long id = json.getAsJsonObject().get("id").getAsLong();
|
||||
return new VideoList(id, videos);
|
||||
}
|
||||
}
|
||||
BIN
app/src/main/res/drawable-hdpi/filmstrip.png
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 343 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 538 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 765 B |
BIN
app/src/main/res/drawable-hdpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 539 B |
BIN
app/src/main/res/drawable-ldpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 247 B |
BIN
app/src/main/res/drawable-ldpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 246 B |
BIN
app/src/main/res/drawable-ldpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
app/src/main/res/drawable-ldpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 381 B |
BIN
app/src/main/res/drawable-ldpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 301 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 289 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 295 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 402 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
app/src/main/res/drawable-mdpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 385 B |
BIN
app/src/main/res/drawable-tvdpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
app/src/main/res/drawable-tvdpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 505 B |
BIN
app/src/main/res/drawable-tvdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
app/src/main/res/drawable-tvdpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-tvdpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 932 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 432 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 441 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 717 B |
BIN
app/src/main/res/drawable-xhdpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xhdpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 737 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 563 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_arrow_left.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_arrow_right.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_share.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_star_0.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/drawable-xxxhdpi/ic_action_star_10.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
2
app/src/main/res/drawable/orange_ripple.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/colorAccent"/>
|
||||
BIN
app/src/main/res/drawable/poster_notfound.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
47
app/src/main/res/layout-sw600dp-land/activity_main.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
tools:ignore="InconsistentLayout"
|
||||
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"
|
||||
tools:context=".MainActivity"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent" android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/fragmentHolder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_weight="1"
|
||||
android:id="@+id/fragmentHolderDetails"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/main_details_placeHolderText"
|
||||
android:layout_gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="@string/details_frag_placeholder_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
@@ -57,5 +57,39 @@
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
tools:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." />
|
||||
|
||||
<View
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="@color/colorAccent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/movie_details_tvOverview"
|
||||
android:id="@+id/view" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/movie_details_trailerHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/view"
|
||||
android:layout_alignParentStart="true">
|
||||
|
||||
<TextView
|
||||
android:visibility="gone"
|
||||
android:text="@string/details_no_movies_found"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
style="@style/Widget.AppCompat.Button.Colored"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Reviews"
|
||||
android:id="@+id/movie_details_btReviews"
|
||||
android:layout_alignBottom="@+id/movie_details_ivPoster"
|
||||
android:layout_alignStart="@+id/movie_details_tvRating" />
|
||||
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
||||
74
app/src/main/res/layout/movie_details_review_dialog.xml
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<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">
|
||||
|
||||
|
||||
<TextView
|
||||
android:paddingTop="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:id="@+id/review_tvReviewUser"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
tools:text="Username" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/review_tvReview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
tools:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet." />
|
||||
|
||||
</ScrollView>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/review_btPrevious"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="left"
|
||||
android:background="@android:color/transparent"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_action_arrow_left"
|
||||
android:tint="@android:color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/review_tvPosition"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
tools:text="5/10" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/review_btNext"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="right"
|
||||
android:background="@android:color/transparent"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_action_arrow_right"
|
||||
android:tint="@color/colorAccent" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
23
app/src/main/res/layout/movie_details_trailer_item.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="@drawable/orange_ripple">
|
||||
|
||||
<ImageView
|
||||
android:tint="@android:color/white"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:id="@+id/imageView"
|
||||
android:src="@drawable/filmstrip" />
|
||||
<TextView
|
||||
android:id="@+id/trailer_item_tvName"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:text="This is a trailer title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
6
app/src/main/res/menu/movie_details_menu.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:icon="@drawable/ic_action_share" android:id="@+id/action_share" android:title="@string/details_action_share" app:showAsAction="ifRoom" />
|
||||
<item android:id="@+id/action_favorite" android:title="@string/details_action_favorite" app:showAsAction="ifRoom" />
|
||||
</menu>
|
||||
@@ -1,6 +1,7 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
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_rating" android:title="Sort by av. rating" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_sort_popularity" android:title="@string/list_action_popular" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_sort_rating" android:title="@string/list_action_rating" app:showAsAction="never" />
|
||||
<item android:id="@+id/action_sort_favorites" android:title="@string/list_action_favorites" app:showAsAction="never" />
|
||||
</menu>
|
||||
|
||||
3
app/src/main/res/values-sw600dp-land/dimens.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<bool name="master_detail">true</bool>
|
||||
</resources>
|
||||
@@ -1,6 +0,0 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
||||
@@ -1,6 +1,3 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
<bool name="master_detail">false</bool>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,17 @@
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="movie_release_date">Release: %1$s</string>
|
||||
<string name="movie_rating">Rating: %1$s/10.0</string>
|
||||
<string name="details_action_share">Share</string>
|
||||
<string name="details_action_favorite">Add/Remove Favorite</string>
|
||||
<string name="list_action_popular">Popular movies</string>
|
||||
<string name="list_action_rating">Highest av. rating</string>
|
||||
<string name="list_action_favorites">Favorites</string>
|
||||
<string name="details_no_movies_found">No videos found.</string>
|
||||
<string name="details_frag_placeholder_text">Please select a movie</string>
|
||||
<string name="movie_download_review_progress">Downloading...</string>
|
||||
<string name="movie_download_review_not_found">No reviews found</string>
|
||||
<string name="movie_download_review_fail">Review download failed.</string>
|
||||
<string name="movie_share_fail">Youtube app not found.</string>
|
||||
<string name="movie_share_no_trailer_found">Nothing to share. :(</string>
|
||||
<string name="movie_share_title">Check out this movie!</string>
|
||||
</resources>
|
||||
|
||||
1
project/build.scala
Normal file
@@ -0,0 +1 @@
|
||||
object Build extends android.GradleBuild
|
||||
2
project/plugins.sbt
Normal file
@@ -0,0 +1,2 @@
|
||||
addSbtPlugin("com.hanhuy.sbt" % "android-gradle-build" % "1.1.6")
|
||||
addSbtPlugin("com.hanhuy.sbt" % "android-protify" % "1.1.12")
|
||||