response) {
42 | code = response.code();
43 | if(response.isSuccessful()) {
44 | body = response.body();
45 | errorMessage = null;
46 | } else {
47 | String message = null;
48 | if (response.errorBody() != null) {
49 | try {
50 | message = response.errorBody().string();
51 | } catch (IOException ignored) {
52 | Timber.e(ignored, "error while parsing response");
53 | }
54 | }
55 | if (message == null || message.trim().length() == 0) {
56 | message = response.message();
57 | }
58 | errorMessage = message;
59 | body = null;
60 | }
61 | String linkHeader = response.headers().get("link");
62 | if (linkHeader == null) {
63 | links = Collections.emptyMap();
64 | } else {
65 | links = new ArrayMap<>();
66 | Matcher matcher = LINK_PATTERN.matcher(linkHeader);
67 |
68 | while (matcher.find()) {
69 | int count = matcher.groupCount();
70 | if (count == 2) {
71 | links.put(matcher.group(2), matcher.group(1));
72 | }
73 | }
74 | }
75 | }
76 |
77 | public boolean isSuccessful() {
78 | return code >= 200 && code < 300;
79 | }
80 |
81 | public Integer getNextPage() {
82 | String next = links.get(NEXT_LINK);
83 | if (next == null) {
84 | return null;
85 | }
86 | Matcher matcher = PAGE_PATTERN.matcher(next);
87 | if (!matcher.find() || matcher.groupCount() != 1) {
88 | return null;
89 | }
90 | try {
91 | return Integer.parseInt(matcher.group(1));
92 | } catch (NumberFormatException ex) {
93 | Timber.w("cannot parse next page from %s", next);
94 | return null;
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/repository/util/AppExecutors.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.repository.util;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.support.annotation.NonNull;
6 |
7 | import java.util.concurrent.Executor;
8 | import java.util.concurrent.Executors;
9 |
10 | import javax.inject.Inject;
11 | import javax.inject.Singleton;
12 |
13 | /**
14 | * Global executor pools for the whole application.
15 | *
16 | * Grouping tasks like this avoids the effects of task starvation (e.g. disk reads don't wait behind
17 | * webservice requests).
18 | */
19 | @Singleton
20 | public class AppExecutors {
21 |
22 | private final Executor diskIO;
23 |
24 | private final Executor networkIO;
25 |
26 | private final Executor mainThread;
27 |
28 | public AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread) {
29 | this.diskIO = diskIO;
30 | this.networkIO = networkIO;
31 | this.mainThread = mainThread;
32 | }
33 |
34 | @Inject
35 | public AppExecutors() {
36 | this(Executors.newSingleThreadExecutor(), Executors.newFixedThreadPool(3),
37 | new MainThreadExecutor());
38 | }
39 |
40 | public Executor diskIO() {
41 | return diskIO;
42 | }
43 |
44 | public Executor networkIO() {
45 | return networkIO;
46 | }
47 |
48 | public Executor mainThread() {
49 | return mainThread;
50 | }
51 |
52 | private static class MainThreadExecutor implements Executor {
53 | private Handler mainThreadHandler = new Handler(Looper.getMainLooper());
54 | @Override
55 | public void execute(@NonNull Runnable command) {
56 | mainThreadHandler.post(command);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/repository/util/LiveDataCallAdapter.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.repository.util;
2 |
3 |
4 | import android.arch.lifecycle.LiveData;
5 |
6 | import com.thomaskioko.livedatademo.repository.model.ApiResponse;
7 |
8 | import java.lang.reflect.Type;
9 | import java.util.concurrent.atomic.AtomicBoolean;
10 |
11 | import retrofit2.Call;
12 | import retrofit2.CallAdapter;
13 | import retrofit2.Callback;
14 | import retrofit2.Response;
15 |
16 | /**
17 | * A Retrofit adapterthat converts the Call into a LiveData of ApiResponse.
18 | * @param
19 | */
20 | public class LiveDataCallAdapter implements CallAdapter>> {
21 | private final Type responseType;
22 | public LiveDataCallAdapter(Type responseType) {
23 | this.responseType = responseType;
24 | }
25 |
26 | @Override
27 | public Type responseType() {
28 | return responseType;
29 | }
30 |
31 | @Override
32 | public LiveData> adapt(Call call) {
33 | return new LiveData>() {
34 | AtomicBoolean started = new AtomicBoolean(false);
35 | @Override
36 | protected void onActive() {
37 | super.onActive();
38 | if (started.compareAndSet(false, true)) {
39 | call.enqueue(new Callback() {
40 | @Override
41 | public void onResponse(Call call, Response response) {
42 | postValue(new ApiResponse<>(response));
43 | }
44 |
45 | @Override
46 | public void onFailure(Call call, Throwable throwable) {
47 | postValue(new ApiResponse(throwable));
48 | }
49 | });
50 | }
51 | }
52 | };
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/repository/util/LiveDataCallAdapterFactory.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.repository.util;
2 |
3 | import android.arch.lifecycle.LiveData;
4 |
5 | import com.thomaskioko.livedatademo.repository.model.ApiResponse;
6 |
7 | import java.lang.annotation.Annotation;
8 | import java.lang.reflect.ParameterizedType;
9 | import java.lang.reflect.Type;
10 |
11 | import retrofit2.CallAdapter;
12 | import retrofit2.Retrofit;
13 |
14 | public class LiveDataCallAdapterFactory extends CallAdapter.Factory {
15 |
16 | @Override
17 | public CallAdapter, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
18 | if (getRawType(returnType) != LiveData.class) {
19 | return null;
20 | }
21 | Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType);
22 | Class> rawObservableType = getRawType(observableType);
23 | if (rawObservableType != ApiResponse.class) {
24 | throw new IllegalArgumentException("type must be a resource");
25 | }
26 | if (! (observableType instanceof ParameterizedType)) {
27 | throw new IllegalArgumentException("resource must be parameterized");
28 | }
29 | Type bodyType = getParameterUpperBound(0, (ParameterizedType) observableType);
30 | return new LiveDataCallAdapter<>(bodyType);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/repository/util/NetworkBoundResource.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.repository.util;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.MediatorLiveData;
5 | import android.support.annotation.MainThread;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.annotation.WorkerThread;
9 |
10 | import com.thomaskioko.livedatademo.repository.model.ApiResponse;
11 | import com.thomaskioko.livedatademo.utils.Objects;
12 | import com.thomaskioko.livedatademo.vo.Resource;
13 |
14 |
15 | /**
16 | * A generic class that can provide a resource backed by both the sqlite database and the network.
17 | *
18 | * You can read more about it in the Architecture
19 | * Guide.
20 | *
21 | * @param
22 | * @param
23 | */
24 | public abstract class NetworkBoundResource {
25 | private final AppExecutors appExecutors;
26 |
27 | private final MediatorLiveData> result = new MediatorLiveData<>();
28 |
29 | @MainThread
30 | public NetworkBoundResource(AppExecutors appExecutors) {
31 | this.appExecutors = appExecutors;
32 | result.setValue(Resource.loading(null));
33 | //TODO:: Add method to check if data should be saved. This should apply for search data.
34 | LiveData dbSource = loadFromDb();
35 | result.addSource(dbSource, data -> {
36 | result.removeSource(dbSource);
37 | if (shouldFetch(data)) {
38 | fetchFromNetwork(dbSource);
39 | } else {
40 | result.addSource(dbSource, newData -> setValue(Resource.success(newData)));
41 | }
42 | });
43 | }
44 |
45 | @MainThread
46 | private void setValue(Resource newValue) {
47 | if (!Objects.equals(result.getValue(), newValue)) {
48 | result.setValue(newValue);
49 | }
50 | }
51 |
52 | private void fetchFromNetwork(final LiveData dbSource) {
53 | LiveData> apiResponse = createCall();
54 | // we re-attach dbSource as a new source, it will dispatch its latest value quickly
55 | result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
56 | result.addSource(apiResponse, response -> {
57 | result.removeSource(apiResponse);
58 | result.removeSource(dbSource);
59 | //noinspection ConstantConditions
60 | if (response.isSuccessful()) {
61 | appExecutors.diskIO().execute(() -> {
62 | saveCallResult(processResponse(response));
63 | appExecutors.mainThread().execute(() ->
64 | // we specially request a new live data,
65 | // otherwise we will get immediately last cached value,
66 | // which may not be updated with latest results received from network.
67 | result.addSource(loadFromDb(),
68 | newData -> setValue(Resource.success(newData)))
69 | );
70 | });
71 | } else {
72 | onFetchFailed();
73 | result.addSource(dbSource,
74 | newData -> setValue(Resource.error(response.errorMessage, newData)));
75 | }
76 | });
77 | }
78 |
79 | protected void onFetchFailed() {
80 | }
81 |
82 | public LiveData> asLiveData() {
83 | return result;
84 | }
85 |
86 | @WorkerThread
87 | protected RequestType processResponse(ApiResponse response) {
88 | return response.body;
89 | }
90 |
91 | @WorkerThread
92 | protected abstract void saveCallResult(@NonNull RequestType item);
93 |
94 | @MainThread
95 | protected abstract boolean shouldFetch(@Nullable ResultType data);
96 |
97 | @NonNull
98 | @MainThread
99 | protected abstract LiveData loadFromDb();
100 |
101 | @NonNull
102 | @MainThread
103 | protected abstract LiveData> createCall();
104 | }
105 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/utils/AbsentLiveData.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.utils;
2 |
3 | import android.arch.lifecycle.LiveData;
4 |
5 | /**
6 | * A LiveData class that has {@code null} value.
7 | */
8 | public class AbsentLiveData extends LiveData {
9 | private AbsentLiveData() {
10 | postValue(null);
11 | }
12 | public static LiveData create() {
13 | //noinspection unchecked
14 | return new AbsentLiveData();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/utils/AppConstants.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.utils;
2 |
3 |
4 | import android.graphics.Color;
5 |
6 | public class AppConstants {
7 | /**
8 | * Connection timeout duration
9 | */
10 | public static final int CONNECT_TIMEOUT = 60 * 1000;
11 | /**
12 | * Connection Read timeout duration
13 | */
14 | public static final int READ_TIMEOUT = 60 * 1000;
15 | /**
16 | * Connection write timeout duration
17 | */
18 | public static final int WRITE_TIMEOUT = 60 * 1000;
19 | /**
20 | * Endpoint
21 | */
22 | public static final String BASE_URL = "http://api.themoviedb.org/3/";
23 |
24 |
25 | public static boolean DEBUG = true;
26 |
27 | // the dimens unit is dp or sp, not px
28 | public static final float DEFAULT_LINE_MARGIN = 5;
29 | public static final float DEFAULT_TAG_MARGIN = 5;
30 | public static final float DEFAULT_TAG_TEXT_PADDING_LEFT = 8;
31 | public static final float DEFAULT_TAG_TEXT_PADDING_TOP = 5;
32 | public static final float DEFAULT_TAG_TEXT_PADDING_RIGHT = 8;
33 | public static final float DEFAULT_TAG_TEXT_PADDING_BOTTOM = 5;
34 | public static final float LAYOUT_WIDTH_OFFSET = 2;
35 |
36 | public static final float DEFAULT_TAG_TEXT_SIZE = 14f;
37 | public static final float DEFAULT_TAG_DELETE_INDICATOR_SIZE = 14f;
38 | public static final float DEFAULT_TAG_LAYOUT_BORDER_SIZE = 0f;
39 | public static final float DEFAULT_TAG_RADIUS = 100;
40 | public static final int DEFAULT_TAG_LAYOUT_COLOR = Color.parseColor("#00BFFF");
41 | public static final int DEFAULT_TAG_LAYOUT_COLOR_PRESS = Color.parseColor("#88363636");
42 | public static final int DEFAULT_TAG_TEXT_COLOR = Color.parseColor("#ffffff");
43 | public static final int DEFAULT_TAG_DELETE_INDICATOR_COLOR = Color.parseColor("#ffffff");
44 | public static final int DEFAULT_TAG_LAYOUT_BORDER_COLOR = Color.parseColor("#ffffff");
45 | public static final String DEFAULT_TAG_DELETE_ICON = "×";
46 | public static final boolean DEFAULT_TAG_IS_DELETABLE = false;
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/utils/DeviceUtils.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.utils;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Context;
5 | import android.content.res.TypedArray;
6 | import android.os.Build;
7 | import android.view.Window;
8 | import android.view.WindowManager;
9 |
10 | import com.thomaskioko.livedatademo.R;
11 |
12 | /**
13 | *
14 | */
15 |
16 | public class DeviceUtils {
17 |
18 | public static void setTranslucentStatusBar(Window window, int color) {
19 | if (window == null) return;
20 | int sdkInt = Build.VERSION.SDK_INT;
21 | if (sdkInt >= Build.VERSION_CODES.LOLLIPOP) {
22 | setTranslucentStatusBarLollipop(window, color);
23 | } else if (sdkInt >= Build.VERSION_CODES.KITKAT) {
24 | setTranslucentStatusBarKiKat(window);
25 | }
26 | }
27 |
28 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
29 | private static void setTranslucentStatusBarLollipop(Window window, int color) {
30 | window.setStatusBarColor(
31 | window.getContext()
32 | .getResources()
33 | .getColor(color));
34 | }
35 |
36 | @TargetApi(Build.VERSION_CODES.KITKAT)
37 | private static void setTranslucentStatusBarKiKat(Window window) {
38 | window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
39 | }
40 |
41 | public static int getToolbarHeight(Context context) {
42 | final TypedArray styledAttributes = context.getTheme().obtainStyledAttributes(
43 | new int[]{R.attr.actionBarSize});
44 | int toolbarHeight = (int) styledAttributes.getDimension(0, 0);
45 | styledAttributes.recycle();
46 |
47 | return toolbarHeight;
48 | }
49 |
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/utils/Objects.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.utils;
2 |
3 | public class Objects {
4 | public static boolean equals(Object o1, Object o2) {
5 | if (o1 == null) {
6 | return o2 == null;
7 | }
8 | if (o2 == null) {
9 | return false;
10 | }
11 | return o1.equals(o2);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/adapter/MovieListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.adapter;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 |
9 | import com.bumptech.glide.Glide;
10 | import com.thomaskioko.livedatademo.R;
11 | import com.thomaskioko.livedatademo.db.entity.Movie;
12 | import com.thomaskioko.livedatademo.view.callback.MovieCallback;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | import butterknife.BindView;
18 | import butterknife.ButterKnife;
19 |
20 |
21 | public class MovieListAdapter extends RecyclerView.Adapter {
22 |
23 | private List mMovieList = new ArrayList<>();
24 | private MovieCallback mMovieCallback;
25 |
26 | public MovieListAdapter(MovieCallback movieCallback) {
27 | mMovieCallback = movieCallback;
28 | }
29 |
30 | @Override
31 | public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
32 | View view = LayoutInflater.from(parent.getContext())
33 | .inflate(R.layout.item_movie_layout, parent, false);
34 | return new MovieViewHolder(view);
35 | }
36 |
37 | @Override
38 | public void onBindViewHolder(MovieViewHolder holder, int position) {
39 |
40 | Movie movie = mMovieList.get(position);
41 | String posterUrl = holder.ivPoster.getContext().getString(R.string.tmdb_image_url) +
42 | holder.ivPoster.getContext().getString(R.string.image_size_780) + movie.posterUrl;
43 |
44 | Glide.with(holder.ivPoster.getContext())
45 | .load(posterUrl)
46 | .into(holder.ivPoster);
47 |
48 | holder.itemView.setOnClickListener(view -> mMovieCallback.onClick(holder.ivPoster, movie));
49 |
50 | }
51 |
52 | @Override
53 | public int getItemCount() {
54 | return mMovieList.size();
55 | }
56 |
57 | public void setData(List movieList) {
58 | mMovieList = movieList;
59 | notifyDataSetChanged();
60 | }
61 |
62 | public void clearAdapter() {
63 | mMovieList.clear();
64 | notifyDataSetChanged();
65 | }
66 |
67 | /**
68 | * ViewHolder class
69 | */
70 | public class MovieViewHolder extends RecyclerView.ViewHolder {
71 |
72 | @BindView(R.id.ivPoster)
73 | public ImageView ivPoster;
74 | public View itemView;
75 |
76 | public MovieViewHolder(View itemView) {
77 | super(itemView);
78 | ButterKnife.bind(this, itemView);
79 |
80 | this.itemView = itemView;
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/adapter/SearchItemAdapter.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.adapter;
2 |
3 | import android.support.v7.widget.RecyclerView;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import com.bumptech.glide.Glide;
11 | import com.thomaskioko.livedatademo.R;
12 | import com.thomaskioko.livedatademo.db.entity.Movie;
13 | import com.thomaskioko.livedatademo.view.callback.MovieCallback;
14 |
15 | import java.util.ArrayList;
16 | import java.util.List;
17 |
18 | import butterknife.BindView;
19 | import butterknife.ButterKnife;
20 |
21 |
22 | public class SearchItemAdapter extends RecyclerView.Adapter {
23 |
24 | private List mMovieList = new ArrayList<>();
25 | private MovieCallback mMovieClickCallback;
26 |
27 |
28 | public SearchItemAdapter(MovieCallback movieClickCallback) {
29 | mMovieClickCallback = movieClickCallback;
30 | }
31 |
32 | @Override
33 | public SearchItemAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
34 | View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_search_movie_layout, parent, false);
35 | return new ViewHolder(itemView);
36 | }
37 |
38 | @Override
39 | public void onBindViewHolder(SearchItemAdapter.ViewHolder holder, int position) {
40 | final Movie movie = mMovieList.get(position);
41 |
42 | String posterUrl = holder.imageView.getContext().getString(R.string.tmdb_image_url) +
43 | holder.imageView.getContext().getString(R.string.image_size_780) + movie.posterUrl;
44 |
45 | holder.tvName.setText(movie.title);
46 | holder.releaseYear.setText(movie.releaseYear);
47 | holder.itemView.setOnClickListener(view -> mMovieClickCallback.onClick(holder.imageView, movie));
48 | Glide.with(holder.imageView.getContext())
49 | .load(posterUrl)
50 | .into(holder.imageView);
51 | }
52 |
53 | @Override
54 | public int getItemCount() {
55 | return mMovieList.size();
56 | }
57 |
58 | public class ViewHolder extends RecyclerView.ViewHolder {
59 | @BindView(R.id.movie_title)
60 | TextView tvName;
61 | @BindView(R.id.movie_thumb)
62 | ImageView imageView;
63 | @BindView(R.id.movie_release_year)
64 | TextView releaseYear;
65 | View itemView;
66 |
67 |
68 | public ViewHolder(View view) {
69 | super(view);
70 | ButterKnife.bind(this, view);
71 | this.itemView = view;
72 | }
73 | }
74 |
75 |
76 | public void setItems(List movieList) {
77 | mMovieList = movieList;
78 | notifyDataSetChanged();
79 | }
80 |
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/adapter/VideoListAdapter.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.adapter;
2 |
3 | import android.os.Handler;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 |
11 | import com.bumptech.glide.Glide;
12 | import com.thomaskioko.livedatademo.R;
13 | import com.thomaskioko.livedatademo.db.entity.TmdbVideo;
14 | import com.thomaskioko.livedatademo.view.callback.VideoCallback;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 |
19 | import butterknife.BindView;
20 | import butterknife.ButterKnife;
21 |
22 | /**
23 | * @author Thomas Kioko
24 | */
25 |
26 | public class VideoListAdapter extends RecyclerView.Adapter {
27 |
28 | private List mVideoList = new ArrayList<>();
29 | private VideoCallback mVideoClickCallback;
30 |
31 | public VideoListAdapter(VideoCallback videoClickCallback) {
32 | mVideoClickCallback = videoClickCallback;
33 | }
34 |
35 | @Override
36 | public MovieViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
37 | View view = LayoutInflater.from(parent.getContext())
38 | .inflate(R.layout.item_video_trailer, parent, false);
39 | return new MovieViewHolder(view);
40 | }
41 |
42 | @Override
43 | public void onBindViewHolder(MovieViewHolder holder, int position) {
44 |
45 | TmdbVideo video = mVideoList.get(position);
46 |
47 | String videoUrl = holder.ivPoster.getContext()
48 | .getResources().getString(R.string.thumbnail_max_resolution, video.key);
49 |
50 | holder.tvVideoTitle.setText(video.name);
51 | Glide.with(holder.ivPoster.getContext())
52 | .load(videoUrl)
53 | .into(holder.ivPoster);
54 |
55 | holder.itemView.setOnClickListener(view -> {
56 | // <--- Giving time to the ripple effect finish before opening a new activity
57 | new Handler().postDelayed(() -> mVideoClickCallback.onClick(holder.ivPoster, video), 200);
58 | });
59 |
60 | }
61 |
62 | @Override
63 | public int getItemCount() {
64 | return mVideoList.size();
65 | }
66 |
67 | public void setData(List movieList) {
68 | mVideoList = movieList;
69 | notifyDataSetChanged();
70 | }
71 |
72 | public void clearAdapter() {
73 | mVideoList.clear();
74 | notifyDataSetChanged();
75 | }
76 |
77 | /**
78 | * ViewHolder class
79 | */
80 | public class MovieViewHolder extends RecyclerView.ViewHolder {
81 |
82 | @BindView(R.id.iv_video_background)
83 | ImageView ivPoster;
84 | @BindView(R.id.text_view_video_title)
85 | TextView tvVideoTitle;
86 | View itemView;
87 |
88 | public MovieViewHolder(View itemView) {
89 | super(itemView);
90 | ButterKnife.bind(this, itemView);
91 |
92 | this.itemView = itemView;
93 | }
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/callback/MovieCallback.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.callback;
2 |
3 | import android.widget.ImageView;
4 |
5 | import com.thomaskioko.livedatademo.db.entity.Movie;
6 |
7 | /**
8 | *
9 | */
10 |
11 | public interface MovieCallback {
12 | void onClick(ImageView sharedImageView, Movie movie);
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/callback/VideoCallback.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.callback;
2 |
3 | import android.widget.ImageView;
4 |
5 | import com.thomaskioko.livedatademo.db.entity.TmdbVideo;
6 |
7 | /**
8 | *
9 | */
10 |
11 | public interface VideoCallback {
12 | void onClick(ImageView sharedImageView, TmdbVideo tmdbVideo);
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/ui/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.ui;
2 |
3 | import android.os.Bundle;
4 | import android.support.v4.app.Fragment;
5 | import android.support.v7.app.AppCompatActivity;
6 |
7 | import com.thomaskioko.livedatademo.R;
8 | import com.thomaskioko.livedatademo.view.ui.common.NavigationController;
9 |
10 | import javax.inject.Inject;
11 |
12 | import dagger.android.AndroidInjection;
13 | import dagger.android.AndroidInjector;
14 | import dagger.android.DispatchingAndroidInjector;
15 | import dagger.android.support.HasSupportFragmentInjector;
16 |
17 | public class MainActivity extends AppCompatActivity implements HasSupportFragmentInjector {
18 |
19 | @Inject
20 | DispatchingAndroidInjector dispatchingAndroidInjector;
21 |
22 | @Inject
23 | NavigationController navigationController;
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_main);
29 |
30 | AndroidInjection.inject(this);
31 |
32 | // Add project list fragment if this is first creation
33 | if (savedInstanceState == null) {
34 | navigationController.navigateToMovieListFragment();
35 | }
36 |
37 | // Being here means we are in animation mode
38 | supportPostponeEnterTransition();
39 |
40 | }
41 |
42 | @Override
43 | public AndroidInjector supportFragmentInjector() {
44 | return dispatchingAndroidInjector;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/ui/common/NavigationController.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.ui.common;
2 |
3 | import android.os.Build;
4 | import android.support.v4.app.FragmentManager;
5 | import android.support.v4.view.ViewCompat;
6 | import android.transition.Fade;
7 | import android.view.View;
8 |
9 | import com.thomaskioko.livedatademo.R;
10 | import com.thomaskioko.livedatademo.view.ui.MainActivity;
11 | import com.thomaskioko.livedatademo.view.ui.fragment.DetailsTransition;
12 | import com.thomaskioko.livedatademo.view.ui.fragment.MovieDetailFragment;
13 | import com.thomaskioko.livedatademo.view.ui.fragment.MovieListFragment;
14 |
15 | import javax.inject.Inject;
16 |
17 | /**
18 | * A utility class that handles navigation in {@link MainActivity}.
19 | */
20 |
21 | public class NavigationController {
22 | private final int containerId;
23 | private final FragmentManager fragmentManager;
24 |
25 | @Inject
26 | public NavigationController(MainActivity mainActivity) {
27 | containerId = R.id.container;
28 | fragmentManager = mainActivity.getSupportFragmentManager();
29 | }
30 |
31 | public void navigateToMovieListFragment() {
32 | MovieListFragment fragment = new MovieListFragment();
33 | fragmentManager.beginTransaction()
34 | .replace(containerId, fragment)
35 | .commitAllowingStateLoss();
36 | }
37 |
38 | public void navigateToMovieDetailFragment(View sharedImageView, int movieId) {
39 | MovieDetailFragment fragment = MovieDetailFragment.create(movieId);
40 |
41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
42 | fragment.setSharedElementEnterTransition(new DetailsTransition());
43 | fragment.setEnterTransition(new Fade());
44 | fragment.setExitTransition(new Fade());
45 | fragment.setSharedElementReturnTransition(new DetailsTransition());
46 | }
47 | fragmentManager.beginTransaction()
48 | .setReorderingAllowed(true)
49 | .addSharedElement(sharedImageView, ViewCompat.getTransitionName(sharedImageView))
50 | .replace(containerId, fragment)
51 | .addToBackStack(null)
52 | .commitAllowingStateLoss();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/ui/fragment/DetailsTransition.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.ui.fragment;
2 |
3 | import android.os.Build;
4 | import android.support.annotation.RequiresApi;
5 | import android.transition.ChangeBounds;
6 | import android.transition.ChangeImageTransform;
7 | import android.transition.ChangeTransform;
8 | import android.transition.TransitionSet;
9 |
10 | /**
11 | *
12 | */
13 |
14 | @RequiresApi(api = Build.VERSION_CODES.KITKAT)
15 | public class DetailsTransition extends TransitionSet {
16 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
17 | public DetailsTransition() {
18 | setOrdering(ORDERING_TOGETHER);
19 | addTransition(new ChangeBounds())
20 | .addTransition(new ChangeTransform())
21 | .setStartDelay(25)
22 | .setDuration(350)
23 | .addTransition(new ChangeImageTransform());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/view/ui/utils/ScrollingFabBehavior.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.view.ui.utils;
2 |
3 | import android.content.Context;
4 | import android.support.design.widget.AppBarLayout;
5 | import android.support.design.widget.CoordinatorLayout;
6 | import android.support.design.widget.FloatingActionButton;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 |
10 | import com.thomaskioko.livedatademo.utils.DeviceUtils;
11 |
12 | /**
13 | * class to handle displaying of the FloatingActionButton
14 | */
15 |
16 | public class ScrollingFabBehavior extends CoordinatorLayout.Behavior {
17 | private float toolbarHeight;
18 |
19 | public ScrollingFabBehavior(Context context, AttributeSet attrs) {
20 | super(context, attrs);
21 | this.toolbarHeight = DeviceUtils.getToolbarHeight(context);
22 | }
23 |
24 | @Override
25 | public boolean layoutDependsOn(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
26 | return dependency instanceof AppBarLayout;
27 | }
28 |
29 | @Override
30 | public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton fab, View dependency) {
31 | if (dependency instanceof AppBarLayout) {
32 | CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
33 | int fabBottomMargin = lp.bottomMargin;
34 | int distanceToScroll = fab.getHeight() + fabBottomMargin;
35 | float ratio = dependency.getY() / toolbarHeight;
36 | fab.setTranslationY(distanceToScroll * ratio);
37 | }
38 | return true;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/viewmodel/MovieDetailViewModel.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.viewmodel;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.MutableLiveData;
5 | import android.arch.lifecycle.Transformations;
6 | import android.arch.lifecycle.ViewModel;
7 | import android.support.annotation.VisibleForTesting;
8 | import android.support.v7.graphics.Palette;
9 |
10 | import com.thomaskioko.livedatademo.db.entity.Genre;
11 | import com.thomaskioko.livedatademo.db.entity.Movie;
12 | import com.thomaskioko.livedatademo.db.entity.TmdbVideo;
13 | import com.thomaskioko.livedatademo.repository.TmdbRepository;
14 | import com.thomaskioko.livedatademo.utils.AbsentLiveData;
15 | import com.thomaskioko.livedatademo.utils.Objects;
16 | import com.thomaskioko.livedatademo.vo.Resource;
17 |
18 | import java.util.List;
19 |
20 | import javax.inject.Inject;
21 |
22 |
23 | public class MovieDetailViewModel extends ViewModel {
24 |
25 | @VisibleForTesting
26 | final MutableLiveData movieId = new MutableLiveData<>();
27 | private final LiveData> movie;
28 | private final LiveData>> videos;
29 | private final MutableLiveData mPalette = new MutableLiveData<>();
30 | private TmdbRepository tmdbRepository;
31 |
32 | @Inject
33 | MovieDetailViewModel(TmdbRepository tmdbRepository) {
34 | this.tmdbRepository = tmdbRepository;
35 | movie = Transformations.switchMap(movieId, movieId -> {
36 | if (movieId == null) {
37 | return AbsentLiveData.create();
38 | } else {
39 | return tmdbRepository.getMovieById(movieId);
40 | }
41 | });
42 |
43 | videos = Transformations.switchMap(movieId, movieId -> {
44 | if (movieId == null) {
45 | return AbsentLiveData.create();
46 | } else {
47 | return tmdbRepository.getMovieVideo(movieId);
48 | }
49 | });
50 | }
51 |
52 | @VisibleForTesting
53 | public LiveData> getMovie() {
54 | return movie;
55 | }
56 |
57 | @VisibleForTesting
58 | public void setMovieId(int movieId) {
59 | if (Objects.equals(movieId, this.movieId.getValue())) {
60 | return;
61 | }
62 |
63 | this.movieId.setValue(movieId);
64 | }
65 |
66 | @VisibleForTesting
67 | public LiveData>> getVideoMovies() {
68 | return videos;
69 | }
70 |
71 | public LiveData> getMovieGenresById(int genreId) {
72 | return tmdbRepository.getGenresById(genreId);
73 | }
74 |
75 | @VisibleForTesting
76 | public void setPalette(Palette palette) {
77 | if (Objects.equals(palette, mPalette.getValue())) {
78 | return;
79 | }
80 |
81 | mPalette.setValue(palette);
82 | }
83 |
84 | @VisibleForTesting
85 | public LiveData getPalette() {
86 | return mPalette;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/viewmodel/MovieListViewModel.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.viewmodel;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.MutableLiveData;
5 | import android.arch.lifecycle.Observer;
6 | import android.arch.lifecycle.Transformations;
7 | import android.arch.lifecycle.ViewModel;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.support.annotation.VisibleForTesting;
11 |
12 | import com.thomaskioko.livedatademo.repository.TmdbRepository;
13 | import com.thomaskioko.livedatademo.db.entity.Movie;
14 | import com.thomaskioko.livedatademo.utils.AbsentLiveData;
15 | import com.thomaskioko.livedatademo.utils.Objects;
16 | import com.thomaskioko.livedatademo.vo.Resource;
17 |
18 | import java.util.List;
19 | import java.util.Locale;
20 |
21 | import javax.inject.Inject;
22 |
23 | import timber.log.Timber;
24 |
25 |
26 | public class MovieListViewModel extends ViewModel {
27 |
28 | private final LiveData>> moviesLiveData;
29 | private final MutableLiveData query = new MutableLiveData<>();
30 | private final LiveData>> searchResults;
31 |
32 |
33 | @Inject
34 | MovieListViewModel(@NonNull TmdbRepository tmdbRepository) {
35 | moviesLiveData = tmdbRepository.getPopularMovies();
36 | searchResults = Transformations.switchMap(query, search -> {
37 |
38 | if (search == null || search.trim().length() == 0) {
39 | return AbsentLiveData.create();
40 | } else {
41 | return tmdbRepository.searchMovie(search);
42 | }
43 | });
44 | }
45 |
46 | @VisibleForTesting
47 | public LiveData>> getSearchResults() {
48 | return searchResults;
49 | }
50 |
51 | public void setSearchQuery(@NonNull String originalInput) {
52 | String input = originalInput.toLowerCase(Locale.getDefault()).trim();
53 | if (Objects.equals(input, query.getValue())) {
54 | return;
55 | }
56 | query.setValue(input);
57 | }
58 |
59 |
60 | @VisibleForTesting
61 | public LiveData>> getPopularMovies() {
62 | return moviesLiveData;
63 | }
64 |
65 | @VisibleForTesting
66 | static class NextPageHandler implements Observer> {
67 | @Nullable
68 | private LiveData> nextPageLiveData;
69 | private final MutableLiveData loadMoreState = new MutableLiveData<>();
70 | private String query;
71 | private final TmdbRepository repository;
72 | @VisibleForTesting
73 | boolean hasMore;
74 |
75 | @VisibleForTesting
76 | NextPageHandler(TmdbRepository repository) {
77 | this.repository = repository;
78 | reset();
79 | }
80 |
81 | void queryNextPage(String query) {
82 | if (Objects.equals(this.query, query)) {
83 | return;
84 | }
85 | unregister();
86 | this.query = query;
87 | nextPageLiveData = repository.searchNextPage(query);
88 | loadMoreState.setValue(new LoadMoreState(true, null));
89 | //noinspection ConstantConditions
90 | nextPageLiveData.observeForever(this);
91 | }
92 |
93 | @Override
94 | public void onChanged(@Nullable Resource result) {
95 | if (result == null) {
96 | reset();
97 | } else {
98 | switch (result.status) {
99 | case SUCCESS:
100 | hasMore = Boolean.TRUE.equals(result.data);
101 | unregister();
102 | loadMoreState.setValue(new LoadMoreState(false, null));
103 | break;
104 | case ERROR:
105 | hasMore = true;
106 | unregister();
107 | loadMoreState.setValue(new LoadMoreState(false,
108 | result.message));
109 | break;
110 | }
111 | }
112 | }
113 |
114 | private void unregister() {
115 | if (nextPageLiveData != null) {
116 | nextPageLiveData.removeObserver(this);
117 | nextPageLiveData = null;
118 | if (hasMore) {
119 | query = null;
120 | }
121 | }
122 | }
123 |
124 | private void reset() {
125 | unregister();
126 | hasMore = true;
127 | loadMoreState.setValue(new LoadMoreState(false, null));
128 | }
129 |
130 | MutableLiveData getLoadMoreState() {
131 | return loadMoreState;
132 | }
133 | }
134 |
135 | static class LoadMoreState {
136 | private final boolean running;
137 | private final String errorMessage;
138 | private boolean handledError = false;
139 |
140 | LoadMoreState(boolean running, String errorMessage) {
141 | this.running = running;
142 | this.errorMessage = errorMessage;
143 | }
144 |
145 | boolean isRunning() {
146 | return running;
147 | }
148 |
149 | String getErrorMessage() {
150 | return errorMessage;
151 | }
152 |
153 | String getErrorMessageIfNotHandled() {
154 | if (handledError) {
155 | return null;
156 | }
157 | handledError = true;
158 | return errorMessage;
159 | }
160 | }
161 |
162 |
163 | @Override
164 | protected void onCleared() {
165 | super.onCleared();
166 | Timber.d("@onCleared called");
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/viewmodel/ProjectViewModelFactory.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.viewmodel;
2 |
3 | import android.arch.lifecycle.ViewModel;
4 | import android.arch.lifecycle.ViewModelProvider;
5 |
6 | import java.util.Map;
7 |
8 | import javax.inject.Inject;
9 | import javax.inject.Provider;
10 | import javax.inject.Singleton;
11 |
12 | @Singleton
13 | public class ProjectViewModelFactory implements ViewModelProvider.Factory {
14 | private final Map, Provider> creators;
15 |
16 | @Inject
17 | public ProjectViewModelFactory(Map, Provider> creators) {
18 | this.creators = creators;
19 | }
20 |
21 | @SuppressWarnings("unchecked")
22 | @Override
23 | public T create(Class modelClass) {
24 | Provider extends ViewModel> creator = creators.get(modelClass);
25 | if (creator == null) {
26 | for (Map.Entry, Provider> entry : creators.entrySet()) {
27 | if (modelClass.isAssignableFrom(entry.getKey())) {
28 | creator = entry.getValue();
29 | break;
30 | }
31 | }
32 | }
33 | if (creator == null) {
34 | throw new IllegalArgumentException("unknown model class " + modelClass);
35 | }
36 | try {
37 | return (T) creator.get();
38 | } catch (Exception e) {
39 | throw new RuntimeException(e);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/viewmodel/SearchViewModel.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.viewmodel;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.MutableLiveData;
5 | import android.arch.lifecycle.Transformations;
6 | import android.arch.lifecycle.ViewModel;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.VisibleForTesting;
9 |
10 | import com.thomaskioko.livedatademo.repository.TmdbRepository;
11 | import com.thomaskioko.livedatademo.db.entity.Movie;
12 | import com.thomaskioko.livedatademo.vo.Resource;
13 | import com.thomaskioko.livedatademo.utils.AbsentLiveData;
14 | import com.thomaskioko.livedatademo.utils.Objects;
15 |
16 | import java.util.List;
17 | import java.util.Locale;
18 |
19 | import javax.inject.Inject;
20 |
21 | import timber.log.Timber;
22 |
23 |
24 | public class SearchViewModel extends ViewModel {
25 |
26 | private final MutableLiveData query = new MutableLiveData<>();
27 | private final LiveData>> searchResults;
28 |
29 | @Inject
30 | SearchViewModel(@NonNull TmdbRepository tmdbRepository) {
31 | searchResults = Transformations.switchMap(query, search -> {
32 |
33 | if (search == null || search.trim().length() == 0) {
34 | return AbsentLiveData.create();
35 | } else {
36 | return tmdbRepository.searchMovie(search);
37 | }
38 | });
39 | }
40 |
41 | @VisibleForTesting
42 | public LiveData>> getSearchResults() {
43 | return searchResults;
44 | }
45 |
46 | public void setSearchQuery(@NonNull String originalInput) {
47 | String input = originalInput.toLowerCase(Locale.getDefault()).trim();
48 | if (Objects.equals(input, query.getValue())) {
49 | return;
50 | }
51 | query.setValue(input);
52 | }
53 |
54 |
55 | @Override
56 | protected void onCleared() {
57 | super.onCleared();
58 | Timber.d("@onCleared called");
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/vo/Resource.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.vo;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import static com.thomaskioko.livedatademo.vo.Status.ERROR;
7 | import static com.thomaskioko.livedatademo.vo.Status.LOADING;
8 | import static com.thomaskioko.livedatademo.vo.Status.SUCCESS;
9 |
10 |
11 | /**
12 | * A generic class that holds a value with its loading status.
13 | * @param
14 | */
15 | public class Resource {
16 |
17 | @NonNull
18 | public final Status status;
19 |
20 | @Nullable
21 | public final String message;
22 |
23 | @Nullable
24 | public final T data;
25 |
26 | public Resource(@NonNull Status status, @Nullable T data, @Nullable String message) {
27 | this.status = status;
28 | this.data = data;
29 | this.message = message;
30 | }
31 |
32 | public static Resource success(@Nullable T data) {
33 | return new Resource<>(SUCCESS, data, null);
34 | }
35 |
36 | public static Resource error(String msg, @Nullable T data) {
37 | return new Resource<>(ERROR, data, msg);
38 | }
39 |
40 | public static Resource loading(@Nullable T data) {
41 | return new Resource<>(LOADING, data, null);
42 | }
43 |
44 | @Override
45 | public boolean equals(Object o) {
46 | if (this == o) {
47 | return true;
48 | }
49 | if (o == null || getClass() != o.getClass()) {
50 | return false;
51 | }
52 |
53 | Resource> resource = (Resource>) o;
54 |
55 | if (status != resource.status) {
56 | return false;
57 | }
58 | if (message != null ? !message.equals(resource.message) : resource.message != null) {
59 | return false;
60 | }
61 | return data != null ? data.equals(resource.data) : resource.data == null;
62 | }
63 |
64 | @Override
65 | public int hashCode() {
66 | int result = status.hashCode();
67 | result = 31 * result + (message != null ? message.hashCode() : 0);
68 | result = 31 * result + (data != null ? data.hashCode() : 0);
69 | return result;
70 | }
71 |
72 | @Override
73 | public String toString() {
74 | return "Resource{" +
75 | "status=" + status +
76 | ", message='" + message + '\'' +
77 | ", data=" + data +
78 | '}';
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/com/thomaskioko/livedatademo/vo/Status.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.vo;
2 |
3 | /**
4 | * Status of a resource that is provided to the UI.
5 | *
6 | * These are usually created by the Repository classes where they return
7 | * {@code LiveData>} to pass back the latest data to the UI with its fetch status.
8 | */
9 | public enum Status {
10 | SUCCESS,
11 | ERROR,
12 | LOADING
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_10.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_2.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_3.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_4.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_5.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_6.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_7.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_8.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/badge_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/badge_9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/bg_collection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/bg_collection.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/bg_watched.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/bg_watched.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/bg_watchlist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/bg_watchlist.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/fanart_dark.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/fanart_dark.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_audience_rotten.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_audience_rotten.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_audience_unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_audience_unknown.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_generic_man_dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_generic_man_dark.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_score_audience.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_score_audience.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_score_fresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_score_fresh.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_score_imdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_score_imdb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_score_rotten.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_score_rotten.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/ic_score_superfresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/ic_score_superfresh.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/poster_placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/poster_placeholder.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/tmdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/tmdb.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/trakttv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/trakttv.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/user_trakt.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/drawable-nodpi/user_trakt.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/selectable_item_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/black_translucent_gradient.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/custom_cursor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_headerbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_headerbar_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_headerbar_top.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/gradient_light_bottom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
17 |
22 |
27 |
32 |
37 |
42 |
47 |
52 |
57 |
62 |
67 |
72 |
77 |
82 |
87 |
92 |
97 |
102 |
107 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_arrow_.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_play_circle_outline_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search_24dp.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/movie_bage.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/selectable_item_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/include_toolbar.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_movie_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_search_movie_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
35 |
36 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_tagview.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_video_trailer.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
28 |
29 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/movie_detail_fragment.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
15 |
16 |
24 |
25 |
37 |
38 |
44 |
45 |
53 |
54 |
55 |
56 |
63 |
64 |
65 |
66 |
67 |
68 |
81 |
82 |
83 |
84 |
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/movie_list_fragment.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
14 |
15 |
27 |
28 |
29 |
37 |
38 |
39 |
43 |
44 |
55 |
56 |
57 |
65 |
66 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/movie_poster.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
21 |
22 |
29 |
30 |
38 |
39 |
47 |
48 |
54 |
55 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/movie_profile.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
20 |
21 |
30 |
31 |
39 |
40 |
45 |
46 |
54 |
55 |
56 |
57 |
63 |
64 |
65 |
74 |
75 |
76 |
81 |
82 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/movie_profile_header_wrapper.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
19 |
20 |
27 |
28 |
33 |
34 |
35 |
43 |
44 |
61 |
62 |
75 |
76 |
96 |
97 |
102 |
103 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_play_arrow_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-hdpi/ic_play_arrow_white.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_play_arrow_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xhdpi/ic_play_arrow_white.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_play_arrow_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxhdpi/ic_play_arrow_white.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_play_arrow_white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/app/src/main/res/mipmap-xxxhdpi/ic_play_arrow_white.png
--------------------------------------------------------------------------------
/app/src/main/res/transition-v21/details_window_enter_transition.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/transition-v21/details_window_return_transition.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 | #000000
5 | #016d1b
6 | #015415
7 |
8 | #212121
9 | #727272
10 | #ffffff
11 | #8affffff
12 | #FFFFFF
13 | #B6B6B6
14 |
15 | #ff0099cc
16 |
17 | #BB000000
18 | #70000000
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 | 170dp
5 | 48dp
6 |
7 |
8 | 12sp
9 | 14sp
10 | 16sp
11 | 18sp
12 | 21sp
13 | 25sp
14 |
15 | 280dp
16 |
17 |
18 | 2dp
19 |
20 |
21 | 160dp
22 | 220dp
23 | 100dp
24 |
25 | 10dp
26 | 5dp
27 |
28 |
29 | 4dp
30 | 140dp
31 | 180dp
32 | 240dp
33 |
34 |
35 | 20dp
36 | 25dp
37 |
38 |
39 | 90dp
40 | 100dp
41 |
42 |
43 | 4dp
44 | 8dp
45 | 16dp
46 | 24dp
47 |
48 | 240dp
49 | 240dp
50 |
51 | 360dp
52 | 0dp
53 | 16dp
54 | - @dimen/fab_margin
55 | #ffffffff
56 |
57 | 120dp
58 | 115dp
59 | 80dp
60 |
61 | 90dp
62 | 60dp
63 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Tmdb
3 |
4 |
5 |
6 | http://image.tmdb.org/t/p/
7 |
8 | w185
9 | w500
10 | w780
11 | original
12 |
13 |
14 | Could not fetch movies!
15 | No Movie found :(
16 |
17 | Search Movies
18 |
19 |
20 | Release Year:
21 | Plot:
22 | Trailers:
23 | Reviews:
24 | Rating
25 | Popularity:
26 | Vote Count:
27 |
28 | Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
29 |
30 |
31 | %s Ratings
32 |
33 |
34 | http://img.youtube.com/vi/%s/default.jpg
35 | http://img.youtube.com/vi/%s/hqdefault.jpg
36 | http://img.youtube.com/vi/%s/mqdefault.jpg
37 | http://img.youtube.com/vi/%s/sddefault.jpg
38 | http://img.youtube.com/vi/%s/maxresdefault.jpg
39 |
40 |
41 | TN_DetailIcon
42 |
43 | Image
44 |
45 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
39 |
40 |
44 |
45 |
46 |
55 |
56 |
63 |
64 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/searchable.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/test-common/java/com/thomaskioko/livedatademo/util/LiveDataTestUtil.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.util;
2 |
3 | import android.arch.lifecycle.LiveData;
4 | import android.arch.lifecycle.Observer;
5 | import android.support.annotation.Nullable;
6 |
7 | import java.util.concurrent.CountDownLatch;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | public class LiveDataTestUtil {
11 | public static T getValue(LiveData liveData) throws InterruptedException {
12 | final Object[] data = new Object[1];
13 | CountDownLatch latch = new CountDownLatch(1);
14 | Observer observer = new Observer() {
15 | @Override
16 | public void onChanged(@Nullable T o) {
17 | data[0] = o;
18 | latch.countDown();
19 | liveData.removeObserver(this);
20 | }
21 | };
22 | liveData.observeForever(observer);
23 | latch.await(2, TimeUnit.SECONDS);
24 | //noinspection unchecked
25 | return (T) data[0];
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/test/java/com/thomaskioko/livedatademo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/thomaskioko/livedatademo/api/ApiResponseTest.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.api;
2 |
3 | import com.thomaskioko.livedatademo.repository.model.ApiResponse;
4 |
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.junit.runners.JUnit4;
8 |
9 | import okhttp3.MediaType;
10 | import okhttp3.ResponseBody;
11 | import retrofit2.Response;
12 |
13 | import static org.hamcrest.CoreMatchers.is;
14 | import static org.hamcrest.CoreMatchers.notNullValue;
15 | import static org.hamcrest.CoreMatchers.nullValue;
16 | import static org.hamcrest.MatcherAssert.assertThat;
17 |
18 | @RunWith(JUnit4.class)
19 | public class ApiResponseTest {
20 |
21 | @Test
22 | public void exception() {
23 | Exception exception = new Exception("foo");
24 | ApiResponse apiResponse = new ApiResponse<>(exception);
25 | assertThat(apiResponse.links, notNullValue());
26 | assertThat(apiResponse.body, nullValue());
27 | assertThat(apiResponse.code, is(500));
28 | assertThat(apiResponse.errorMessage, is("foo"));
29 | }
30 |
31 | @Test
32 | public void success() {
33 | ApiResponse apiResponse = new ApiResponse<>(Response.success("foo"));
34 | assertThat(apiResponse.errorMessage, nullValue());
35 | assertThat(apiResponse.code, is(200));
36 | assertThat(apiResponse.body, is("foo"));
37 | assertThat(apiResponse.getNextPage(), is(nullValue()));
38 | }
39 |
40 | @Test
41 | public void error() {
42 | ApiResponse response = new ApiResponse<>(Response.error(400,
43 | ResponseBody.create(MediaType.parse("application/txt"), "blah")));
44 | assertThat(response.code, is(400));
45 | assertThat(response.errorMessage, is("blah"));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/test/java/com/thomaskioko/livedatademo/api/TmdbServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.api;
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule;
4 |
5 | import com.thomaskioko.livedatademo.db.entity.Movie;
6 | import com.thomaskioko.livedatademo.db.entity.TmdbVideo;
7 | import com.thomaskioko.livedatademo.repository.api.MovieResult;
8 | import com.thomaskioko.livedatademo.repository.api.TmdbService;
9 | import com.thomaskioko.livedatademo.repository.api.VideoResult;
10 | import com.thomaskioko.livedatademo.repository.util.LiveDataCallAdapterFactory;
11 |
12 | import org.junit.After;
13 | import org.junit.Before;
14 | import org.junit.Rule;
15 | import org.junit.Test;
16 | import org.junit.runner.RunWith;
17 | import org.junit.runners.JUnit4;
18 |
19 | import java.io.IOException;
20 | import java.io.InputStream;
21 | import java.nio.charset.StandardCharsets;
22 | import java.util.Collections;
23 | import java.util.List;
24 | import java.util.Map;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | import okhttp3.OkHttpClient;
28 | import okhttp3.mockwebserver.MockResponse;
29 | import okhttp3.mockwebserver.MockWebServer;
30 | import okhttp3.mockwebserver.RecordedRequest;
31 | import okio.BufferedSource;
32 | import okio.Okio;
33 | import retrofit2.Retrofit;
34 | import retrofit2.converter.gson.GsonConverterFactory;
35 |
36 | import static com.thomaskioko.livedatademo.util.LiveDataTestUtil.getValue;
37 | import static com.thomaskioko.livedatademo.utils.AppConstants.CONNECT_TIMEOUT;
38 | import static com.thomaskioko.livedatademo.utils.AppConstants.READ_TIMEOUT;
39 | import static com.thomaskioko.livedatademo.utils.AppConstants.WRITE_TIMEOUT;
40 | import static junit.framework.Assert.assertNotNull;
41 | import static junit.framework.Assert.assertTrue;
42 | import static org.hamcrest.CoreMatchers.is;
43 | import static org.junit.Assert.assertThat;
44 |
45 | @RunWith(JUnit4.class)
46 | public class TmdbServiceTest {
47 |
48 | @Rule
49 | public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
50 | private TmdbService service;
51 | private MockWebServer mockWebServer;
52 |
53 | @Before
54 | public void createdService() throws IOException {
55 | mockWebServer = new MockWebServer();
56 | service = new Retrofit.Builder()
57 | .baseUrl(mockWebServer.url("/"))
58 | .client(getHttpClient())
59 | .addConverterFactory(GsonConverterFactory.create())
60 | .addCallAdapterFactory(new LiveDataCallAdapterFactory())
61 | .build()
62 | .create(TmdbService.class);
63 | }
64 |
65 | @After
66 | public void stopService() throws IOException {
67 | mockWebServer.shutdown();
68 | }
69 |
70 | @Test
71 | public void testGetPopularMovies() throws IOException, InterruptedException {
72 | enqueueResponse("popular-movies.json");
73 | MovieResult movieResult = getValue(service.getPopularMovies()).body;
74 |
75 | RecordedRequest request = mockWebServer.takeRequest();
76 | assertThat(request.getPath(), is("/movie/popular?"));
77 |
78 | assertNotNull(movieResult);
79 |
80 | List movieList = movieResult.getResults();
81 | assertTrue(movieList.size()> 0);
82 | }
83 |
84 | @Test
85 | public void testGetMovieVideos() throws IOException, InterruptedException {
86 | enqueueResponse("videos-by-movie-id.json");
87 | VideoResult videoResult = getValue(service.getMovieVideos(354912)).body;
88 |
89 | RecordedRequest request = mockWebServer.takeRequest();
90 | assertThat(request.getPath(), is("/movie/354912/videos"));
91 |
92 | assertNotNull(videoResult);
93 |
94 | List results = videoResult.getResults();
95 | assertTrue(results.size()> 0);
96 | }
97 |
98 | @Test
99 | public void testGetSearchedMovie() throws IOException, InterruptedException {
100 | enqueueResponse("search-movie.json");
101 | MovieResult movieResult = getValue(service.searchMovies("hitman")).body;
102 |
103 | RecordedRequest request = mockWebServer.takeRequest();
104 | assertThat(request.getPath(), is("/search/movie?&query=hitman"));
105 |
106 | assertNotNull(movieResult);
107 |
108 | List movieList = movieResult.getResults();
109 | assertTrue(movieList.size()> 0);
110 | }
111 |
112 | @Test
113 | public void testGetSearchedMovieById() throws IOException, InterruptedException {
114 | enqueueResponse("search-movie-id.json");
115 | Movie movieResult = getValue(service.getMovieById(354912)).body;
116 |
117 | RecordedRequest request = mockWebServer.takeRequest();
118 | assertThat(request.getPath(), is("/movie/354912"));
119 |
120 | assertNotNull(movieResult);
121 |
122 | assertThat(movieResult.title, is("Coco"));
123 | }
124 |
125 |
126 | private void enqueueResponse(String fileName) throws IOException {
127 | enqueueResponse(fileName, Collections.emptyMap());
128 | }
129 |
130 | private void enqueueResponse(String fileName, Map headers) throws IOException {
131 | InputStream inputStream = getClass().getClassLoader()
132 | .getResourceAsStream("api-response/" + fileName);
133 | BufferedSource source = Okio.buffer(Okio.source(inputStream));
134 | MockResponse mockResponse = new MockResponse();
135 | for (Map.Entry header : headers.entrySet()) {
136 | mockResponse.addHeader(header.getKey(), header.getValue());
137 | }
138 | mockWebServer.enqueue(mockResponse
139 | .setBody(source.readString(StandardCharsets.UTF_8)));
140 | }
141 |
142 | private OkHttpClient getHttpClient(){
143 | return new OkHttpClient.Builder()
144 | .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
145 | .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
146 | .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
147 | .build();
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/app/src/test/java/com/thomaskioko/livedatademo/util/ApiUtil.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.util;
2 |
3 |
4 | import android.arch.lifecycle.LiveData;
5 | import android.arch.lifecycle.MutableLiveData;
6 |
7 | import com.thomaskioko.livedatademo.repository.model.ApiResponse;
8 |
9 | import retrofit2.Response;
10 |
11 | public class ApiUtil {
12 | public static LiveData> successCall(T data) {
13 | return createCall(Response.success(data));
14 | }
15 | public static LiveData> createCall(Response response) {
16 | MutableLiveData> data = new MutableLiveData<>();
17 | data.setValue(new ApiResponse<>(response));
18 | return data;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/test/java/com/thomaskioko/livedatademo/util/CountingAppExecutors.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.util;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.thomaskioko.livedatademo.repository.util.AppExecutors;
6 |
7 | import java.util.concurrent.Executor;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.concurrent.TimeoutException;
11 |
12 | public class CountingAppExecutors {
13 |
14 | private final Object LOCK = new Object();
15 |
16 | private int taskCount = 0;
17 |
18 | private AppExecutors appExecutors;
19 |
20 | public CountingAppExecutors() {
21 | Runnable increment = () -> {
22 | synchronized (LOCK) {
23 | taskCount--;
24 | if (taskCount == 0) {
25 | LOCK.notifyAll();
26 | }
27 | }
28 | };
29 | Runnable decrement = () -> {
30 | synchronized (LOCK) {
31 | taskCount++;
32 | }
33 | };
34 | appExecutors = new AppExecutors(
35 | new CountingExecutor(increment, decrement),
36 | new CountingExecutor(increment, decrement),
37 | new CountingExecutor(increment, decrement));
38 | }
39 |
40 | public AppExecutors getAppExecutors() {
41 | return appExecutors;
42 | }
43 |
44 | public void drainTasks(int time, TimeUnit timeUnit)
45 | throws InterruptedException, TimeoutException {
46 | long end = System.currentTimeMillis() + timeUnit.toMillis(time);
47 | while (true) {
48 | synchronized (LOCK) {
49 | if (taskCount == 0) {
50 | return;
51 | }
52 | long now = System.currentTimeMillis();
53 | long remaining = end - now;
54 | if (remaining > 0) {
55 | LOCK.wait(remaining);
56 | } else {
57 | throw new TimeoutException("could not drain tasks");
58 | }
59 | }
60 | }
61 | }
62 |
63 | private static class CountingExecutor implements Executor {
64 |
65 | private final Executor delegate = Executors.newSingleThreadExecutor();
66 |
67 | private final Runnable increment;
68 |
69 | private final Runnable decrement;
70 |
71 | public CountingExecutor(Runnable increment, Runnable decrement) {
72 | this.increment = increment;
73 | this.decrement = decrement;
74 | }
75 |
76 | @Override
77 | public void execute(@NonNull Runnable command) {
78 | increment.run();
79 | delegate.execute(() -> {
80 | try {
81 | command.run();
82 | } finally {
83 | decrement.run();
84 | }
85 | });
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/test/java/com/thomaskioko/livedatademo/util/InstantAppExecutors.java:
--------------------------------------------------------------------------------
1 | package com.thomaskioko.livedatademo.util;
2 |
3 |
4 | import com.thomaskioko.livedatademo.repository.util.AppExecutors;
5 |
6 | import java.util.concurrent.Executor;
7 |
8 | public class InstantAppExecutors extends AppExecutors {
9 | private static Executor instant = command -> command.run();
10 |
11 | public InstantAppExecutors() {
12 | super(instant, instant, instant);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/test/resources/api-response/search-movie-id.json:
--------------------------------------------------------------------------------
1 | {
2 | "adult": false,
3 | "backdrop_path": "/askg3SMvhqEl4OL52YuvdtY40Yb.jpg",
4 | "belongs_to_collection": null,
5 | "budget": 175000000,
6 | "genres": [
7 | {
8 | "id": 10751,
9 | "name": "Family"
10 | },
11 | {
12 | "id": 16,
13 | "name": "Animation"
14 | },
15 | {
16 | "id": 12,
17 | "name": "Adventure"
18 | },
19 | {
20 | "id": 35,
21 | "name": "Comedy"
22 | }
23 | ],
24 | "homepage": "",
25 | "id": 354912,
26 | "imdb_id": "tt2380307",
27 | "original_language": "en",
28 | "original_title": "Coco",
29 | "overview": "Despite his family’s baffling generations-old ban on music, Miguel dreams of becoming an accomplished musician like his idol, Ernesto de la Cruz. Desperate to prove his talent, Miguel finds himself in the stunning and colorful Land of the Dead following a mysterious chain of events. Along the way, he meets charming trickster Hector, and together, they set off on an extraordinary journey to unlock the real story behind Miguel's family history.",
30 | "popularity": 427.64022,
31 | "poster_path": "/eKi8dIrr8voobbaGzDpe8w0PVbC.jpg",
32 | "production_companies": [
33 | {
34 | "name": "Disney Pixar",
35 | "id": 6806
36 | }
37 | ],
38 | "production_countries": [
39 | {
40 | "iso_3166_1": "US",
41 | "name": "United States of America"
42 | }
43 | ],
44 | "release_date": "2017-10-27",
45 | "revenue": 700920729,
46 | "runtime": 109,
47 | "spoken_languages": [
48 | {
49 | "iso_639_1": "en",
50 | "name": "English"
51 | },
52 | {
53 | "iso_639_1": "es",
54 | "name": "Español"
55 | }
56 | ],
57 | "status": "Released",
58 | "tagline": "The celebration of a lifetime",
59 | "title": "Coco",
60 | "video": false,
61 | "vote_average": 7.7,
62 | "vote_count": 1952
63 | }
--------------------------------------------------------------------------------
/app/src/test/resources/api-response/videos-by-movie-id.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": 188927,
3 | "results": [
4 | {
5 | "id": "571bf094c3a368525f006b86",
6 | "iso_639_1": "en",
7 | "iso_3166_1": "US",
8 | "key": "XRVD32rnzOw",
9 | "name": "Official Trailer",
10 | "site": "YouTube",
11 | "size": 1080,
12 | "type": "Trailer"
13 | },
14 | {
15 | "id": "585032ad92514175ad00021e",
16 | "iso_639_1": "en",
17 | "iso_3166_1": "US",
18 | "key": "Tvq3y8BhZ2s",
19 | "name": "Official Trailer #2",
20 | "site": "YouTube",
21 | "size": 1080,
22 | "type": "Trailer"
23 | },
24 | {
25 | "id": "585033299251416fa100b49d",
26 | "iso_639_1": "en",
27 | "iso_3166_1": "US",
28 | "key": "QSYLhl57pqM",
29 | "name": "Captain Kirk Featurette",
30 | "site": "YouTube",
31 | "size": 1080,
32 | "type": "Featurette"
33 | },
34 | {
35 | "id": "58503359c3a3682fb800d603",
36 | "iso_639_1": "en",
37 | "iso_3166_1": "US",
38 | "key": "cr6bYn2EzvQ",
39 | "name": "Jaylah Featurette",
40 | "site": "YouTube",
41 | "size": 1080,
42 | "type": "Featurette"
43 | },
44 | {
45 | "id": "5850338e9251416cd000b415",
46 | "iso_639_1": "en",
47 | "iso_3166_1": "US",
48 | "key": "kfPKpVYFSnU",
49 | "name": "Krall Featurette",
50 | "site": "YouTube",
51 | "size": 1080,
52 | "type": "Featurette"
53 | },
54 | {
55 | "id": "585033b592514175ad0002db",
56 | "iso_639_1": "en",
57 | "iso_3166_1": "US",
58 | "key": "3MBXBMkcUNo",
59 | "name": "Official Trailer #3",
60 | "site": "YouTube",
61 | "size": 1080,
62 | "type": "Trailer"
63 | },
64 | {
65 | "id": "585033e1c3a368315000afc5",
66 | "iso_639_1": "en",
67 | "iso_3166_1": "US",
68 | "key": "NwpvjQKdpvI",
69 | "name": "Official Trailer #4",
70 | "site": "YouTube",
71 | "size": 1080,
72 | "type": "Trailer"
73 | }
74 | ]
75 | }
--------------------------------------------------------------------------------
/art/HomeScreen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/art/HomeScreen.png
--------------------------------------------------------------------------------
/art/MovieDetails.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/art/MovieDetails.png
--------------------------------------------------------------------------------
/art/archtiture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/art/archtiture.png
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 |
5 | repositories {
6 | google()
7 | jcenter()
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.1.0'
11 |
12 |
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | ext.supportLibVersion = '27.1.0'
19 | ext.archVersion = '1.0.0'
20 | ext.archRuntimeVersion = '1.1.1'
21 | ext.archExtensionVersion = '1.1.1'
22 | ext.retrofitVersion = '2.3.0'
23 | ext.okhttpVersion = '3.4.1'
24 | ext.daggerVersion = '2.11'
25 | ext.constraintLayoutVersion = "1.0.2"
26 | ext.mockitoVersion = "2.7.19"
27 | ext.timberVersion = "4.6.1"
28 | ext.glideVersion = "3.7.0"
29 | ext.butterKnifeVersion = "8.8.1"
30 | ext.junitVersion = "4.12"
31 | ext.coreTestingVersion = "1.0.0"
32 | ext.supportTestVersion = "1.0.0"
33 | ext.espressoCoreVersion = "3.0.1"
34 | ext.mockitoVersion = "2.7.19"
35 | ext.materialSearchViewVersion = "1.4.0"
36 | ext.circularProgressbarVersion = "1.1.1"
37 | ext.jodaTimeVersion = "2.9.9"
38 | ext.mockWebServerVersion = "3.8.1"
39 |
40 |
41 | allprojects {
42 | repositories {
43 | google()
44 | jcenter()
45 | }
46 | }
47 |
48 | task clean(type: Delete) {
49 | delete rootProject.buildDir
50 | }
51 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 | TMDB_API_KEY="PUT_API_KEY"
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thomaskioko/android-liveData-viewModel/dd45b74e1e6d67fea4b10af0fe8eb3c098bde76b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Apr 04 21:49:01 EAT 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------