aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
authorAlex Ning <chineseperson5@gmail.com>2018-07-26 15:04:44 +0000
committerAlex Ning <chineseperson5@gmail.com>2018-07-26 15:04:44 +0000
commit1f2137dd365bc07b5fd38df22e54379933585994 (patch)
treef9ef585dda71db8bc809120761cac42e22dd1193 /app/src/main/java
downloadinfinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.gz
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.bz2
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.lz
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.xz
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.zst
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.zip
First commit
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/AcquireAccessToken.java86
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostData.java232
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostFragment.java245
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostPaginationScrollListener.java161
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostRecyclerViewAdapter.java527
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/CommentData.java84
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/CommentRecyclerViewAdapter.java79
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/FetchComment.java42
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubscribedSubreddits.java5
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/FetchUserInfo.java69
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java59
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/LastItemSynchronizer.java5
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/LoginActivity.java147
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java232
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationNotifier.java6
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRequestQueueSynchronizer.java7
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRetryNotifier.java5
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationSynchronizer.java96
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseBestPost.java304
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java105
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubscribedSubreddits.java4
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseUserInfo.java70
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/RedditUtils.java73
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/SharedPreferencesUtils.java20
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ViewImageActivity.java425
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java342
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ViewVideoActivity.java406
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/VoteThing.java88
28 files changed, 3924 insertions, 0 deletions
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AcquireAccessToken.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AcquireAccessToken.java
new file mode 100644
index 00000000..969a9680
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AcquireAccessToken.java
@@ -0,0 +1,86 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by alex on 3/13/18.
+ */
+
+class AcquireAccessToken {
+
+ interface AcquireAccessTokenListener {
+ void onAcquireAccessTokenSuccess();
+ void onAcquireAccessTokenFail();
+ }
+
+ private Context mContext;
+ private AcquireAccessTokenListener mAcquireAccessTokenListener;
+
+ AcquireAccessToken(Context context) {
+ mContext = context;
+ }
+
+ void refreshAccessToken(RequestQueue refreshQueue, AcquireAccessTokenListener acquireAccessTokenListener) {
+ if(mContext != null) {
+ mAcquireAccessTokenListener = acquireAccessTokenListener;
+ final String refreshToken = mContext.getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.REFRESH_TOKEN_KEY, "");
+ StringRequest newTokenRequest = new StringRequest(Request.Method.POST, RedditUtils.ACQUIRE_ACCESS_TOKEN_URL, new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ try {
+ JSONObject jsonObject = new JSONObject(response);
+ String newAccessToken = jsonObject.getString(RedditUtils.ACCESS_TOKEN_KEY);
+
+ SharedPreferences.Editor editor = mContext.getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).edit();
+ editor.putString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, newAccessToken);
+ editor.apply();
+
+ mAcquireAccessTokenListener.onAcquireAccessTokenSuccess();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ mAcquireAccessTokenListener.onAcquireAccessTokenFail();
+ Toast.makeText(mContext, "Error parsing JSON object when getting the access token", Toast.LENGTH_SHORT).show();
+ Log.i("main activity", "Error parsing JSON object when getting the access token");
+ }
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ Toast.makeText(mContext, "Error getting the new access token", Toast.LENGTH_SHORT).show();
+ mAcquireAccessTokenListener.onAcquireAccessTokenFail();
+ Log.i("error get access token", error.getMessage());
+ }
+ }) {
+ @Override
+ protected Map<String, String> getParams() {
+ Map<String, String> params = new HashMap<>();
+ params.put(RedditUtils.GRANT_TYPE_KEY, RedditUtils.GRANT_TYPE_REFRESH_TOKEN);
+ params.put(RedditUtils.REFRESH_TOKEN_KEY, refreshToken);
+ return params;
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ return RedditUtils.getHttpBasicAuthHeader();
+ }
+ };
+ newTokenRequest.setTag(AcquireAccessToken.class);
+ refreshQueue.add(newTokenRequest);
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostData.java
new file mode 100644
index 00000000..8c08680c
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostData.java
@@ -0,0 +1,232 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Created by alex on 3/1/18.
+ */
+
+class BestPostData implements Parcelable {
+ static final int TEXT_TYPE = 0;
+ static final int IMAGE_TYPE = 1;
+ static final int LINK_TYPE = 2;
+ static final int VIDEO_TYPE = 3;
+ static final int GIF_VIDEO_TYPE = 4;
+ static final int NO_PREVIEW_LINK_TYPE = 5;
+
+ private String id;
+ private String fullName;
+ private String subredditName;
+ private String postTime;
+ private String title;
+ private String selfText;
+ private String previewUrl;
+ private String url;
+ private String videoUrl;
+ private String gifOrVideoDownloadUrl;
+ private String permalink;
+ private int score;
+ private int postType;
+ private int voteType;
+ private boolean nsfw;
+ private boolean isDashVideo;
+ private boolean isDownloadableGifOrVideo;
+
+ BestPostData(String id, String fullName, String subredditName, String postTime, String title, String previewUrl, String permalink, int score, int postType, int voteType, boolean nsfw, boolean isDashVideo) {
+ this.id = id;
+ this.fullName = fullName;
+ this.subredditName = subredditName;
+ this.postTime = postTime;
+ this.title = title;
+ this.previewUrl = previewUrl;
+ this.permalink = RedditUtils.API_BASE_URI + permalink;
+ this.score = score;
+ this.postType = postType;
+ this.voteType = voteType;
+ this.nsfw = nsfw;
+ this.isDashVideo = isDashVideo;
+ }
+
+ BestPostData(String id, String fullName, String subredditName, String postTime, String title, String previewUrl, String url, String permalink, int score, int postType, int voteType, boolean nsfw) {
+ this.id = id;
+ this.fullName = fullName;
+ this.subredditName = subredditName;
+ this.postTime = postTime;
+ this.title = title;
+ this.previewUrl = previewUrl;
+ this.url = url;
+ this.permalink = RedditUtils.API_BASE_URI + permalink;
+ this.score = score;
+ this.postType = postType;
+ this.voteType = voteType;
+ this.nsfw = nsfw;
+ }
+
+ BestPostData(String id, String fullName, String subredditName, String postTime, String title, String permalink, int score, int postType, int voteType, boolean nsfw) {
+ this.id = id;
+ this.fullName = fullName;
+ this.subredditName = subredditName;
+ this.postTime = postTime;
+ this.title = title;
+ this.permalink = RedditUtils.API_BASE_URI + permalink;
+ this.score = score;
+ this.postType = postType;
+ this.voteType = voteType;
+ this.nsfw = nsfw;
+ }
+
+ protected BestPostData(Parcel in) {
+ id = in.readString();
+ fullName = in.readString();
+ subredditName = in.readString();
+ postTime = in.readString();
+ title = in.readString();
+ selfText = in.readString();
+ previewUrl = in.readString();
+ url = in.readString();
+ videoUrl = in.readString();
+ gifOrVideoDownloadUrl = in.readString();
+ permalink = in.readString();
+ score = in.readInt();
+ postType = in.readInt();
+ voteType = in.readInt();
+ nsfw = in.readByte() != 0;
+ isDashVideo = in.readByte() != 0;
+ isDownloadableGifOrVideo = in.readByte() != 0;
+ }
+
+ public static final Creator<BestPostData> CREATOR = new Creator<BestPostData>() {
+ @Override
+ public BestPostData createFromParcel(Parcel in) {
+ return new BestPostData(in);
+ }
+
+ @Override
+ public BestPostData[] newArray(int size) {
+ return new BestPostData[size];
+ }
+ };
+
+ public String getId() {
+ return id;
+ }
+
+ public String getFullName() {
+ return fullName;
+ }
+
+ public String getSubredditName() {
+ return subredditName;
+ }
+
+ public String getPostTime() {
+ return postTime;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setSelfText(String selfText) {
+ this.selfText = selfText;
+ }
+
+ public String getSelfText() {
+ return selfText;
+ }
+
+ public String getPreviewUrl() {
+ return previewUrl;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setVideoUrl(String videoUrl) {
+ this.videoUrl = videoUrl;
+ }
+
+ public String getVideoUrl() {
+ return videoUrl;
+ }
+
+ public String getGifOrVideoDownloadUrl() {
+ return gifOrVideoDownloadUrl;
+ }
+
+ public void setGifOrVideoDownloadUrl(String gifOrVideoDownloadUrl) {
+ this.gifOrVideoDownloadUrl = gifOrVideoDownloadUrl;
+ }
+
+ public String getPermalink() {
+ return permalink;
+ }
+
+ public void setScore(int score) {
+ this.score = score;
+ }
+
+ public int getScore() {
+ return score;
+ }
+
+ public int getPostType() {
+ return postType;
+ }
+
+ public void setVoteType(int voteType) {
+ this.voteType = voteType;
+ }
+
+ public int getVoteType() {
+ return voteType;
+ }
+
+ public boolean getNSFW() {
+ return nsfw;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public boolean isDashVideo() {
+ return isDashVideo;
+ }
+
+ public void setDownloadableGifOrVideo(boolean isDownloadableGifOrVideo) {
+ this.isDownloadableGifOrVideo = isDownloadableGifOrVideo;
+ }
+
+ public boolean isDownloadableGifOrVideo() {
+ return isDownloadableGifOrVideo;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeString(id);
+ parcel.writeString(fullName);
+ parcel.writeString(subredditName);
+ parcel.writeString(postTime);
+ parcel.writeString(title);
+ parcel.writeString(selfText);
+ parcel.writeString(previewUrl);
+ parcel.writeString(url);
+ parcel.writeString(videoUrl);
+ parcel.writeString(gifOrVideoDownloadUrl);
+ parcel.writeString(permalink);
+ parcel.writeInt(score);
+ parcel.writeInt(postType);
+ parcel.writeInt(voteType);
+ parcel.writeByte((byte) (nsfw ? 1 : 0));
+ parcel.writeByte((byte) (isDashVideo ? 1 : 0));
+ parcel.writeByte((byte) (isDownloadableGifOrVideo ? 1 : 0));
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostFragment.java
new file mode 100644
index 00000000..d7b23a23
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostFragment.java
@@ -0,0 +1,245 @@
+package ml.docilealligator.infinityforreddit;
+
+
+import android.app.Fragment;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.FloatingActionButton;
+import android.support.design.widget.Snackbar;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+
+/**
+ * A simple {@link Fragment} subclass.
+ */
+public class BestPostFragment extends Fragment {
+
+ private CoordinatorLayout mCoordinatorLayout;
+ private RecyclerView mBestPostRecyclerView;
+ private LinearLayoutManager mLinearLayoutManager;
+ private ProgressBar mProgressBar;
+ private ArrayList<BestPostData> mBestPostData;
+ private String mLastItem;
+ private PaginationSynchronizer mPaginationSynchronizer;
+ private BestPostRecyclerViewAdapter mAdapter;
+
+ private String bestPostDataParcelableState = "BPDPS";
+ private String lastItemState = "LIS";
+ private String paginationSynchronizerState = "PSS";
+
+ private RequestQueue mRequestQueue;
+ private RequestQueue mPaginationRequestQueue;
+ private RequestQueue mAcquireAccessTokenRequestQueue;
+ private RequestQueue mVoteThingRequestQueue;
+
+ public BestPostFragment() {
+ // Required empty public constructor
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ if(savedInstanceState != null) {
+ if(savedInstanceState.containsKey(bestPostDataParcelableState)) {
+ mBestPostData = savedInstanceState.getParcelableArrayList(bestPostDataParcelableState);
+ mLastItem = savedInstanceState.getString(lastItemState);
+ mAdapter = new BestPostRecyclerViewAdapter(getActivity(), mBestPostData, mPaginationSynchronizer, mVoteThingRequestQueue, mAcquireAccessTokenRequestQueue);
+ mBestPostRecyclerView.setAdapter(mAdapter);
+ mBestPostRecyclerView.addOnScrollListener(new BestPostPaginationScrollListener(getActivity(), mLinearLayoutManager, mAdapter, mLastItem, mBestPostData, mPaginationSynchronizer,
+ mAcquireAccessTokenRequestQueue, mPaginationSynchronizer.isLoading(), mPaginationSynchronizer.isLoadSuccess()));
+ mProgressBar.setVisibility(View.GONE);
+ } else {
+ queryBestPost(1);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if(mRequestQueue != null) {
+ mRequestQueue.cancelAll(this);
+ }
+
+ if(mAcquireAccessTokenRequestQueue != null) {
+ mAcquireAccessTokenRequestQueue.cancelAll(AcquireAccessToken.class);
+ }
+
+ if(mVoteThingRequestQueue != null) {
+ mVoteThingRequestQueue.cancelAll(VoteThing.class);
+ }
+
+ if(mPaginationRequestQueue != null) {
+ mPaginationRequestQueue.cancelAll(BestPostPaginationScrollListener.class);
+ }
+
+ if(mBestPostData != null) {
+ outState.putParcelableArrayList(bestPostDataParcelableState, mBestPostData);
+ outState.putString(lastItemState, mLastItem);
+ outState.putParcelable(paginationSynchronizerState, mPaginationSynchronizer);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if(mAdapter != null) {
+ mAdapter.setCanStartActivity(true);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ // Inflate the layout for this fragment
+ View rootView = inflater.inflate(R.layout.fragment_best_post, container, false);
+ mCoordinatorLayout = rootView.findViewById(R.id.coordinator_layout_best_post_fragment);
+ mBestPostRecyclerView = rootView.findViewById(R.id.recycler_view_best_post_fragment);
+ mLinearLayoutManager = new LinearLayoutManager(getActivity());
+ mBestPostRecyclerView.setLayoutManager(mLinearLayoutManager);
+ mProgressBar = rootView.findViewById(R.id.progress_bar_best_post_fragment);
+ FloatingActionButton fab = rootView.findViewById(R.id.fab_best_post_fragment);
+ fab.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
+ .setAction("Action", null).show();
+ }
+ });
+
+ mRequestQueue = Volley.newRequestQueue(getActivity());
+ mAcquireAccessTokenRequestQueue = Volley.newRequestQueue(getActivity());
+ mVoteThingRequestQueue = Volley.newRequestQueue(getActivity());
+
+ if(savedInstanceState != null && savedInstanceState.getParcelable(paginationSynchronizerState) != null) {
+ mPaginationSynchronizer = savedInstanceState.getParcelable(paginationSynchronizerState);
+ } else {
+ mPaginationSynchronizer = new PaginationSynchronizer();
+ queryBestPost(1);
+ }
+
+ LastItemSynchronizer lastItemSynchronizer = new LastItemSynchronizer() {
+ @Override
+ public void lastItemChanged(String lastItem) {
+ mLastItem = lastItem;
+ }
+ };
+ mPaginationSynchronizer.setLastItemSynchronizer(lastItemSynchronizer);
+
+ PaginationRequestQueueSynchronizer paginationRequestQueueSynchronizer = new PaginationRequestQueueSynchronizer() {
+ @Override
+ public void passQueue(RequestQueue q) {
+ mPaginationRequestQueue = q;
+ }
+ };
+ mPaginationSynchronizer.setPaginationRequestQueueSynchronizer(paginationRequestQueueSynchronizer);
+
+
+ return rootView;
+ }
+
+ private void queryBestPost(final int refreshTime) {
+ if(refreshTime < 0) {
+ showErrorSnackbar();
+ return;
+ }
+
+ mProgressBar.setVisibility(View.VISIBLE);
+
+ StringRequest bestPostRequest = new StringRequest(Request.Method.GET, RedditUtils.OAUTH_API_BASE_URI + RedditUtils.BEST_POST_SUFFIX, new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ if(getActivity() != null) {
+ ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("response", response);
+ clipboard.setPrimaryClip(clip);
+ //new ParseBestPostDataAsyncTask(response, accessToken).execute();
+ new ParseBestPost(getActivity(), new ParseBestPost.ParseBestPostListener() {
+ @Override
+ public void onParseBestPostSuccess(ArrayList<BestPostData> bestPostData, String lastItem) {
+ mBestPostData = bestPostData;
+ mLastItem = lastItem;
+ mAdapter = new BestPostRecyclerViewAdapter(getActivity(), bestPostData, mPaginationSynchronizer, mVoteThingRequestQueue, mAcquireAccessTokenRequestQueue);
+
+ mBestPostRecyclerView.setAdapter(mAdapter);
+ mBestPostRecyclerView.addOnScrollListener(new BestPostPaginationScrollListener(getActivity(), mLinearLayoutManager, mAdapter, lastItem, bestPostData, mPaginationSynchronizer,
+ mAcquireAccessTokenRequestQueue, mPaginationSynchronizer.isLoading(), mPaginationSynchronizer.isLoadSuccess()));
+ mProgressBar.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onParseBestPostFail() {
+ Toast.makeText(getActivity(), "Error parsing data", Toast.LENGTH_SHORT).show();
+ Log.i("Best post fetch error", "Error parsing data");
+ mProgressBar.setVisibility(View.GONE);
+ }
+ }).parseBestPost(response, null);
+ }
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if (error instanceof AuthFailureError) {
+ // Error indicating that there was an Authentication Failure while performing the request
+ // Access token expired
+ new AcquireAccessToken(getActivity()).refreshAccessToken(mAcquireAccessTokenRequestQueue,
+ new AcquireAccessToken.AcquireAccessTokenListener() {
+ @Override
+ public void onAcquireAccessTokenSuccess() {
+ queryBestPost(refreshTime - 1);
+ }
+
+ @Override
+ public void onAcquireAccessTokenFail() {}
+ });
+ } else {
+ Log.i("best post fetch error", error.toString());
+ showErrorSnackbar();
+ }
+ }
+ }) {
+ @Override
+ public Map<String, String> getHeaders() {
+ String accessToken = getActivity().getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, "");
+ return RedditUtils.getOAuthHeader(accessToken);
+ }
+ };
+ bestPostRequest.setTag(BestPostFragment.class);
+ mRequestQueue.add(bestPostRequest);
+ }
+
+ private void showErrorSnackbar() {
+ mProgressBar.setVisibility(View.GONE);
+ Snackbar snackbar = Snackbar.make(mCoordinatorLayout, "Error getting best post", Snackbar.LENGTH_INDEFINITE);
+ snackbar.setAction(R.string.retry, new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ queryBestPost(1);
+ }
+ });
+ snackbar.show();
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostPaginationScrollListener.java b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostPaginationScrollListener.java
new file mode 100644
index 00000000..d96af7d3
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostPaginationScrollListener.java
@@ -0,0 +1,161 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * Created by alex on 3/12/18.
+ */
+
+class BestPostPaginationScrollListener extends RecyclerView.OnScrollListener {
+ private Context mContext;
+ private LinearLayoutManager mLayoutManager;
+ private BestPostRecyclerViewAdapter mAdapter;
+ private ArrayList<BestPostData> mBestPostData;
+ private PaginationSynchronizer mPaginationSynchronizer;
+ private PaginationRetryNotifier mPaginationRetryNotifier;
+ private LastItemSynchronizer mLastItemSynchronizer;
+ private PaginationRequestQueueSynchronizer mPaginationRequestQueueSynchronizer;
+
+ private boolean isLoading;
+ private boolean loadSuccess;
+ private String mLastItem;
+ private RequestQueue mRequestQueue;
+ private RequestQueue mAcquireAccessTokenRequestQueue;
+
+ BestPostPaginationScrollListener(Context context, LinearLayoutManager layoutManager, BestPostRecyclerViewAdapter adapter, String lastItem, ArrayList<BestPostData> bestPostData, PaginationSynchronizer paginationSynchronizer,
+ RequestQueue acquireAccessTokenRequestQueue, boolean isLoading, boolean loadSuccess) {
+ if(context != null) {
+ this.mContext = context;
+ this.mLayoutManager = layoutManager;
+ this.mAdapter = adapter;
+ this.mLastItem = lastItem;
+ this.mBestPostData = bestPostData;
+ this.mPaginationSynchronizer = paginationSynchronizer;
+ this.mAcquireAccessTokenRequestQueue = acquireAccessTokenRequestQueue;
+ this.isLoading = isLoading;
+ this.loadSuccess = loadSuccess;
+
+ mRequestQueue = Volley.newRequestQueue(mContext);
+ mAcquireAccessTokenRequestQueue = Volley.newRequestQueue(mContext);
+ mPaginationRetryNotifier = new PaginationRetryNotifier() {
+ @Override
+ public void retry() {
+ fetchBestPost(1);
+ }
+ };
+ mPaginationSynchronizer.setPaginationRetryNotifier(mPaginationRetryNotifier);
+ mLastItemSynchronizer = mPaginationSynchronizer.getLastItemSynchronizer();
+ mPaginationRequestQueueSynchronizer = mPaginationSynchronizer.getPaginationRequestQueueSynchronizer();
+ mPaginationRequestQueueSynchronizer.passQueue(mRequestQueue);
+ }
+ }
+
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ if(!isLoading && loadSuccess) {
+ int visibleItemCount = mLayoutManager.getChildCount();
+ int totalItemCount = mLayoutManager.getItemCount();
+ int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
+
+ if((visibleItemCount + firstVisibleItemPosition >= totalItemCount) && firstVisibleItemPosition >= 0) {
+ fetchBestPost(1);
+ }
+ }
+ }
+
+
+ private void fetchBestPost(final int refreshTime) {
+ if(refreshTime < 0) {
+ loadFailed();
+ return;
+ }
+
+ isLoading = true;
+ loadSuccess = false;
+ mPaginationSynchronizer.setLoading(true);
+
+ StringRequest bestPostRequest = new StringRequest(Request.Method.GET, RedditUtils.OAUTH_API_BASE_URI + RedditUtils.BEST_POST_SUFFIX + "&" + RedditUtils.AFTER_KEY + "=" + mLastItem, new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ ClipboardManager clipboard = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText("response", response);
+ clipboard.setPrimaryClip(clip);
+ new ParseBestPost(mContext, new ParseBestPost.ParseBestPostListener() {
+ @Override
+ public void onParseBestPostSuccess(ArrayList<BestPostData> bestPostData, String lastItem) {
+ mAdapter.notifyDataSetChanged();
+ mLastItem = lastItem;
+ mLastItemSynchronizer.lastItemChanged(mLastItem);
+
+ isLoading = false;
+ loadSuccess = true;
+ mPaginationSynchronizer.setLoading(false);
+ mPaginationSynchronizer.setLoadingState(true);
+ }
+
+ @Override
+ public void onParseBestPostFail() {
+ Toast.makeText(mContext, "Error parsing data", Toast.LENGTH_SHORT).show();
+ Log.i("Best post", "Error parsing data");
+ loadFailed();
+ }
+ }).parseBestPost(response, mBestPostData);
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if (error instanceof AuthFailureError) {
+ //Access token expired
+ new AcquireAccessToken(mContext).refreshAccessToken(mAcquireAccessTokenRequestQueue,
+ new AcquireAccessToken.AcquireAccessTokenListener() {
+ @Override
+ public void onAcquireAccessTokenSuccess() {
+ fetchBestPost(refreshTime - 1);
+ }
+
+ @Override
+ public void onAcquireAccessTokenFail() {
+ }
+ });
+ } else {
+ Toast.makeText(mContext, "Error getting best post", Toast.LENGTH_SHORT).show();
+ Log.i("best post", error.toString());
+ loadFailed();
+ }
+ }
+ }) {
+ @Override
+ public Map<String, String> getHeaders() {
+ String accessToken = mContext.getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, "");
+ return RedditUtils.getOAuthHeader(accessToken);
+ }
+ };
+ bestPostRequest.setTag(BestPostPaginationScrollListener.class);
+ mRequestQueue.add(bestPostRequest);
+ }
+
+ private void loadFailed() {
+ isLoading = false;
+ loadSuccess = false;
+ mPaginationSynchronizer.setLoading(false);
+ mPaginationSynchronizer.setLoadingState(false);
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostRecyclerViewAdapter.java
new file mode 100644
index 00000000..94c9efc5
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/BestPostRecyclerViewAdapter.java
@@ -0,0 +1,527 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.customtabs.CustomTabsIntent;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.volley.RequestQueue;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+
+import java.util.ArrayList;
+
+import de.hdodenhof.circleimageview.CircleImageView;
+
+/**
+ * Created by alex on 2/25/18.
+ */
+
+class BestPostRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private ArrayList<BestPostData> bestPostData;
+ private Context mContext;
+ private PaginationSynchronizer mPaginationSynchronizer;
+ private RequestQueue mVoteThingRequestQueue;
+ private RequestQueue mAcquireAccessTokenRequestQueue;
+ private RequestManager glide;
+ private boolean isLoadingMorePostSuccess;
+ private boolean canStartActivity;
+
+ private static final int VIEW_TYPE_DATA = 0;
+ private static final int VIEW_TYPE_LOADING = 1;
+
+
+ BestPostRecyclerViewAdapter(Context context, ArrayList<BestPostData> bestPostData, PaginationSynchronizer paginationSynchronizer,
+ RequestQueue voteThingRequestQueue, RequestQueue acquireAccessTokenRequestQueue) {
+ if(context != null) {
+ mContext = context;
+ this.bestPostData = bestPostData;
+ mPaginationSynchronizer = paginationSynchronizer;
+ mVoteThingRequestQueue = voteThingRequestQueue;
+ mAcquireAccessTokenRequestQueue = acquireAccessTokenRequestQueue;
+ isLoadingMorePostSuccess = true;
+ canStartActivity = true;
+ glide = Glide.with(mContext);
+ }
+ }
+
+ void setCanStartActivity(boolean canStartActivity) {
+ this.canStartActivity = canStartActivity;
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ if(viewType == VIEW_TYPE_DATA) {
+ CardView cardView = (CardView) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_best_post, parent, false);
+ return new DataViewHolder(cardView);
+ } else {
+ LinearLayout linearLayout = (LinearLayout) LayoutInflater.from(parent.getContext()).inflate(R.layout.item_footer_progress_bar, parent, false);
+ return new LoadingViewHolder(linearLayout);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
+ if(holder instanceof DataViewHolder) {
+ if(bestPostData.get(position) == null) {
+ Log.i("is null", Integer.toString(position));
+ } else {
+ final String id = bestPostData.get(position).getFullName();
+ final String subredditName = bestPostData.get(position).getSubredditName();
+ final String postTime = bestPostData.get(position).getPostTime();
+ final String title = bestPostData.get(position).getTitle();
+ final String permalink = bestPostData.get(position).getPermalink();
+ int voteType = bestPostData.get(position).getVoteType();
+ boolean nsfw = bestPostData.get(position).getNSFW();
+
+ ((DataViewHolder) holder).cardView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if(canStartActivity) {
+ canStartActivity = false;
+ Intent intent = new Intent(mContext, ViewPostDetailActivity.class);
+ intent.putExtra(ViewPostDetailActivity.EXTRA_TITLE, title);
+ intent.putExtra(ViewPostDetailActivity.EXTRA_POST_DATA, bestPostData.get(position));
+ mContext.startActivity(intent);
+ }
+ }
+ });
+
+ ((DataViewHolder) holder).subredditNameTextView.setText(subredditName);
+ ((DataViewHolder) holder).postTimeTextView.setText(postTime);
+ ((DataViewHolder) holder).titleTextView.setText(title);
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore()));
+
+ if(nsfw) {
+ ((DataViewHolder) holder).nsfwTextView.setVisibility(View.VISIBLE);
+ }
+
+ switch (voteType) {
+ case 1:
+ //Upvote
+ ((DataViewHolder) holder).plusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.colorPrimary), android.graphics.PorterDuff.Mode.SRC_IN);
+ break;
+ case -1:
+ //Downvote
+ ((DataViewHolder) holder).minusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.minusButtonColor), android.graphics.PorterDuff.Mode.SRC_IN);
+ break;
+ }
+
+ if(bestPostData.get(position).getPostType() != BestPostData.TEXT_TYPE && bestPostData.get(position).getPostType() != BestPostData.NO_PREVIEW_LINK_TYPE) {
+ ((DataViewHolder) holder).relativeLayout.setVisibility(View.VISIBLE);
+ ((DataViewHolder) holder).progressBar.setVisibility(View.VISIBLE);
+ ((DataViewHolder) holder).imageView.setVisibility(View.VISIBLE);
+ }
+
+ switch (bestPostData.get(position).getPostType()) {
+ case BestPostData.IMAGE_TYPE:
+ ((DataViewHolder) holder).typeTextView.setText("IMAGE");
+ final String previewImageUrl = bestPostData.get(position).getPreviewUrl();
+ final String imageUrl = bestPostData.get(position).getUrl();
+ glide.load(previewImageUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ ((DataViewHolder) holder).progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(((DataViewHolder) holder).imageView);
+ ((DataViewHolder) holder).imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(mContext, ViewImageActivity.class);
+ intent.putExtra(ViewImageActivity.IMAGE_URL_KEY, imageUrl);
+ intent.putExtra(ViewImageActivity.TITLE_KEY, title);
+ intent.putExtra(ViewImageActivity.SUBREDDIT_KEY, subredditName);
+ intent.putExtra(ViewImageActivity.ID_KEY, id);
+ mContext.startActivity(intent);
+ }
+ });
+ break;
+ case BestPostData.LINK_TYPE:
+ ((DataViewHolder) holder).typeTextView.setText("LINK");
+ String linkPreviewUrl = bestPostData.get(position).getPreviewUrl();
+ glide.load(linkPreviewUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ ((DataViewHolder) holder).progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(((DataViewHolder) holder).imageView);
+ final String linkUrl = bestPostData.get(position).getUrl();
+ ((DataViewHolder) holder).imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ // add share action to menu list
+ builder.addDefaultShareMenuItem();
+ builder.setToolbarColor(mContext.getResources().getColor(R.color.colorPrimary));
+ CustomTabsIntent customTabsIntent = builder.build();
+ customTabsIntent.launchUrl(mContext, Uri.parse(linkUrl));
+ }
+ });
+ break;
+ case BestPostData.GIF_VIDEO_TYPE:
+ ((DataViewHolder) holder).typeTextView.setText("GIF");
+ String gifVideoPreviewUrl = bestPostData.get(position).getPreviewUrl();
+ glide.load(gifVideoPreviewUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ ((DataViewHolder) holder).progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(((DataViewHolder) holder).imageView);
+
+ String gifVideoUrl = bestPostData.get(position).getVideoUrl();
+ final Uri gifVideoUri = Uri.parse(gifVideoUrl);
+
+ ((DataViewHolder) holder).imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(mContext, ViewVideoActivity.class);
+ intent.setData(gifVideoUri);
+ intent.putExtra(ViewVideoActivity.TITLE_KEY, title);
+ intent.putExtra(ViewVideoActivity.IS_DASH_VIDEO_KEY, bestPostData.get(position).isDashVideo());
+ intent.putExtra(ViewVideoActivity.IS_DOWNLOADABLE_KEY, bestPostData.get(position).isDownloadableGifOrVideo());
+ if(bestPostData.get(position).isDownloadableGifOrVideo()) {
+ intent.putExtra(ViewVideoActivity.DOWNLOAD_URL_KEY, bestPostData.get(position).getGifOrVideoDownloadUrl());
+ intent.putExtra(ViewVideoActivity.SUBREDDIT_KEY, subredditName);
+ intent.putExtra(ViewVideoActivity.ID_KEY, id);
+ }
+ mContext.startActivity(intent);
+ }
+ });
+ break;
+ case BestPostData.VIDEO_TYPE:
+ ((DataViewHolder) holder).typeTextView.setText("VIDEO");
+ String videoPreviewUrl = bestPostData.get(position).getPreviewUrl();
+ glide.load(videoPreviewUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ ((DataViewHolder) holder).progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(((DataViewHolder) holder).imageView);
+
+ String videoUrl = bestPostData.get(position).getVideoUrl();
+ final Uri videoUri = Uri.parse(videoUrl);
+
+ ((DataViewHolder) holder).imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(mContext, ViewVideoActivity.class);
+ intent.setData(videoUri);
+ intent.putExtra(ViewVideoActivity.TITLE_KEY, title);
+ intent.putExtra(ViewVideoActivity.IS_DASH_VIDEO_KEY, bestPostData.get(position).isDashVideo());
+ intent.putExtra(ViewVideoActivity.IS_DOWNLOADABLE_KEY, bestPostData.get(position).isDownloadableGifOrVideo());
+ if(bestPostData.get(position).isDownloadableGifOrVideo()) {
+ intent.putExtra(ViewVideoActivity.DOWNLOAD_URL_KEY, bestPostData.get(position).getGifOrVideoDownloadUrl());
+ intent.putExtra(ViewVideoActivity.SUBREDDIT_KEY, subredditName);
+ intent.putExtra(ViewVideoActivity.ID_KEY, id);
+ }
+ mContext.startActivity(intent);
+ }
+ });
+ break;
+ case BestPostData.NO_PREVIEW_LINK_TYPE:
+ ((DataViewHolder) holder).typeTextView.setText("LINK");
+ final String noPreviewLinkUrl = bestPostData.get(position).getUrl();
+ ((DataViewHolder) holder).noPreviewLinkImageView.setVisibility(View.VISIBLE);
+ ((DataViewHolder) holder).noPreviewLinkImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ // add share action to menu list
+ builder.addDefaultShareMenuItem();
+ builder.setToolbarColor(mContext.getResources().getColor(R.color.colorPrimary));
+ CustomTabsIntent customTabsIntent = builder.build();
+ customTabsIntent.launchUrl(mContext, Uri.parse(noPreviewLinkUrl));
+ }
+ });
+ break;
+ default:
+ ((DataViewHolder) holder).typeTextView.setText("TEXT");
+ }
+
+ ((DataViewHolder) holder).plusButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final boolean isDownvotedBefore = ((DataViewHolder) holder).minusButton.getColorFilter() != null;
+ ((DataViewHolder) holder).minusButton.clearColorFilter();
+
+ if (((DataViewHolder) holder).plusButton.getColorFilter() == null) {
+ ((DataViewHolder) holder).plusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.colorPrimary), android.graphics.PorterDuff.Mode.SRC_IN);
+ if(isDownvotedBefore) {
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() + 2));
+ } else {
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() + 1));
+ }
+
+ new VoteThing(mContext, mVoteThingRequestQueue, mAcquireAccessTokenRequestQueue).votePost(new VoteThing.VoteThingListener() {
+ @Override
+ public void onVoteThingSuccess(int position) {
+ bestPostData.get(position).setVoteType(1);
+ if(isDownvotedBefore) {
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore() + 2);
+ } else {
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore() + 1);
+ }
+ }
+
+ @Override
+ public void onVoteThingFail(int position) {
+ Toast.makeText(mContext, "Cannot upvote this post", Toast.LENGTH_SHORT).show();
+ ((DataViewHolder) holder).plusButton.clearColorFilter();
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore()));
+ ((DataViewHolder) holder).minusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.minusButtonColor), android.graphics.PorterDuff.Mode.SRC_IN);
+ }
+ }, id, RedditUtils.DIR_UPVOTE, ((DataViewHolder) holder).getAdapterPosition(), 1);
+ } else {
+ //Upvoted before
+ ((DataViewHolder) holder).plusButton.clearColorFilter();
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() - 1));
+
+ new VoteThing(mContext, mVoteThingRequestQueue, mAcquireAccessTokenRequestQueue).votePost(new VoteThing.VoteThingListener() {
+ @Override
+ public void onVoteThingSuccess(int position) {
+ bestPostData.get(position).setVoteType(0);
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore() - 1);
+ }
+
+ @Override
+ public void onVoteThingFail(int position) {
+ Toast.makeText(mContext, "Cannot unvote this post", Toast.LENGTH_SHORT).show();
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() + 1));
+ ((DataViewHolder) holder).plusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.colorPrimary), android.graphics.PorterDuff.Mode.SRC_IN);
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore() + 1);
+ }
+ }, id, RedditUtils.DIR_UNVOTE, ((DataViewHolder) holder).getAdapterPosition(), 1);
+ }
+ }
+ });
+
+ ((DataViewHolder) holder).minusButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ final boolean isUpvotedBefore = ((DataViewHolder) holder).plusButton.getColorFilter() != null;
+
+ ((DataViewHolder) holder).plusButton.clearColorFilter();
+ if (((DataViewHolder) holder).minusButton.getColorFilter() == null) {
+ ((DataViewHolder) holder).minusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.minusButtonColor), android.graphics.PorterDuff.Mode.SRC_IN);
+ if (isUpvotedBefore) {
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() - 2));
+ } else {
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() - 1));
+ }
+
+ new VoteThing(mContext, mVoteThingRequestQueue, mAcquireAccessTokenRequestQueue).votePost(new VoteThing.VoteThingListener() {
+ @Override
+ public void onVoteThingSuccess(int position) {
+ bestPostData.get(position).setVoteType(-1);
+ if(isUpvotedBefore) {
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore() - 2);
+ } else {
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore() - 1);
+ }
+ }
+
+ @Override
+ public void onVoteThingFail(int position) {
+ Toast.makeText(mContext, "Cannot downvote this post", Toast.LENGTH_SHORT).show();
+ ((DataViewHolder) holder).minusButton.clearColorFilter();
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore()));
+ ((DataViewHolder) holder).plusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.colorPrimary), android.graphics.PorterDuff.Mode.SRC_IN);
+ }
+ }, id, RedditUtils.DIR_DOWNVOTE, holder.getAdapterPosition(), 1);
+ } else {
+ //Down voted before
+ ((DataViewHolder) holder).minusButton.clearColorFilter();
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore() + 1));
+
+ new VoteThing(mContext, mVoteThingRequestQueue, mAcquireAccessTokenRequestQueue).votePost(new VoteThing.VoteThingListener() {
+ @Override
+ public void onVoteThingSuccess(int position) {
+ bestPostData.get(position).setVoteType(0);
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore());
+ }
+
+ @Override
+ public void onVoteThingFail(int position) {
+ Toast.makeText(mContext, "Cannot unvote this post", Toast.LENGTH_SHORT).show();
+ ((DataViewHolder) holder).minusButton.setColorFilter(ContextCompat.getColor(mContext, R.color.minusButtonColor), android.graphics.PorterDuff.Mode.SRC_IN);
+ ((DataViewHolder) holder).scoreTextView.setText(Integer.toString(bestPostData.get(position).getScore()));
+ bestPostData.get(position).setScore(bestPostData.get(position).getScore());
+ }
+ }, id, RedditUtils.DIR_UNVOTE, holder.getAdapterPosition(), 1);
+ }
+ }
+ });
+
+ ((DataViewHolder) holder).shareButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ String extraText = title + "\n" + permalink;
+ intent.putExtra(Intent.EXTRA_TEXT, extraText);
+ mContext.startActivity(Intent.createChooser(intent, "Share"));
+ }
+ });
+ }
+ } else if(holder instanceof LoadingViewHolder) {
+ ((LoadingViewHolder) holder).retryButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mPaginationSynchronizer.getPaginationRetryNotifier().retry();
+ ((LoadingViewHolder) holder).progressBar.setVisibility(View.VISIBLE);
+ ((LoadingViewHolder) holder).relativeLayout.setVisibility(View.GONE);
+ }
+ });
+
+ PaginationNotifier mPaginationNotifier = new PaginationNotifier() {
+ @Override
+ public void LoadMorePostSuccess() {
+ isLoadingMorePostSuccess = true;
+ }
+
+ @Override
+ public void LoadMorePostFail() {
+ ((LoadingViewHolder) holder).progressBar.setVisibility(View.GONE);
+ ((LoadingViewHolder) holder).relativeLayout.setVisibility(View.VISIBLE);
+ isLoadingMorePostSuccess = false;
+ }
+ };
+
+ mPaginationSynchronizer.setPaginationNotifier(mPaginationNotifier);
+
+ if(!mPaginationSynchronizer.isLoadSuccess()) {
+ ((LoadingViewHolder) holder).progressBar.setVisibility(View.GONE);
+ ((LoadingViewHolder) holder).relativeLayout.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return bestPostData.size() + 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return (position >= bestPostData.size() ? VIEW_TYPE_LOADING : VIEW_TYPE_DATA);
+ }
+
+ class DataViewHolder extends RecyclerView.ViewHolder {
+ private CardView cardView;
+ private CircleImageView subredditImageView;
+ private TextView subredditNameTextView;
+ private TextView postTimeTextView;
+ private TextView titleTextView;
+ private TextView typeTextView;
+ private TextView nsfwTextView;
+ private RelativeLayout relativeLayout;
+ private ProgressBar progressBar;
+ private ImageView imageView;
+ private ImageView noPreviewLinkImageView;
+ private ImageView plusButton;
+ private TextView scoreTextView;
+ private ImageView minusButton;
+ private ImageView shareButton;
+
+ DataViewHolder(CardView itemView) {
+ super(itemView);
+ cardView = itemView.findViewById(R.id.card_view_view_post_detail);
+ subredditImageView = itemView.findViewById(R.id.subreddit_icon_circle_image_view_best_post_item);
+ subredditNameTextView = itemView.findViewById(R.id.subreddit_text_view_best_post_item);
+ postTimeTextView = itemView.findViewById(R.id.post_time_text_view_best_post_item);
+ titleTextView = itemView.findViewById(R.id.title_text_view_best_post_item);
+ typeTextView = itemView.findViewById(R.id.type_text_view_item_best_post);
+ nsfwTextView = itemView.findViewById(R.id.nsfw_text_view_item_best_post);
+ relativeLayout = itemView.findViewById(R.id.image_view_wrapper_item_best_post);
+ progressBar = itemView.findViewById(R.id.progress_bar_best_post_item);
+ imageView = itemView.findViewById(R.id.image_view_best_post_item);
+ noPreviewLinkImageView = itemView.findViewById(R.id.image_view_no_preview_link_best_post_item);
+
+ plusButton = itemView.findViewById(R.id.plus_button_item_best_post);
+ scoreTextView = itemView.findViewById(R.id.score_text_view_item_best_post);
+ minusButton = itemView.findViewById(R.id.minus_button_item_best_post);
+ shareButton = itemView.findViewById(R.id.share_button_item_best_post);
+ }
+ }
+
+ class LoadingViewHolder extends RecyclerView.ViewHolder {
+ private ProgressBar progressBar;
+ private RelativeLayout relativeLayout;
+ private Button retryButton;
+
+ LoadingViewHolder(LinearLayout itemView) {
+ super(itemView);
+ progressBar = itemView.findViewById(R.id.progress_bar_footer_progress_bar_item);
+ relativeLayout = itemView.findViewById(R.id.relative_layout_footer_progress_bar_item);
+ retryButton = itemView.findViewById(R.id.retry_button_footer_progress_bar_item);
+ }
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) {
+ if(holder instanceof DataViewHolder) {
+ glide.clear(((DataViewHolder) holder).imageView);
+ ((DataViewHolder) holder).relativeLayout.setVisibility(View.GONE);
+ ((DataViewHolder) holder).nsfwTextView.setVisibility(View.GONE);
+ ((DataViewHolder) holder).progressBar.setVisibility(View.GONE);
+ ((DataViewHolder) holder).imageView.setVisibility(View.GONE);
+ ((DataViewHolder) holder).noPreviewLinkImageView.setVisibility(View.GONE);
+ ((DataViewHolder) holder).plusButton.clearColorFilter();
+ ((DataViewHolder) holder).minusButton.clearColorFilter();
+ } else if(holder instanceof LoadingViewHolder) {
+ if(isLoadingMorePostSuccess) {
+ ((LoadingViewHolder) holder).relativeLayout.setVisibility(View.GONE);
+ ((LoadingViewHolder) holder).progressBar.setVisibility(View.VISIBLE);
+ } else {
+ ((LoadingViewHolder) holder).relativeLayout.setVisibility(View.VISIBLE);
+ ((LoadingViewHolder) holder).progressBar.setVisibility(View.GONE);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentData.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentData.java
new file mode 100644
index 00000000..fbc0c830
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentData.java
@@ -0,0 +1,84 @@
+package ml.docilealligator.infinityforreddit;
+
+class CommentData {
+ private String id;
+ private String author;
+ private String commentTime;
+ private String commentContent;
+ private int score;
+ private boolean isSubmitter;
+ private String permalink;
+ private int depth;
+ private boolean collapsed;
+ private boolean hasReply;
+ private boolean scoreHidden;
+
+ CommentData(String id, String author, String commentTime, String commentContent, int score,
+ boolean isSubmitter, String permalink, int depth, boolean collapsed, boolean hasReply,
+ boolean scoreHidden) {
+ this.id = id;
+ this.author = author;
+ this.commentTime = commentTime;
+ this.commentContent = commentContent;
+ this.score = score;
+ this.isSubmitter = isSubmitter;
+ this.permalink = RedditUtils.API_BASE_URI + permalink;
+ this.depth = depth;
+ this.collapsed = collapsed;
+ this.hasReply = hasReply;
+ this.scoreHidden = scoreHidden;
+ }
+
+
+ public String getId() {
+ return id;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public String getCommentTime() {
+ return commentTime;
+ }
+
+ public String getCommentContent() {
+ return commentContent;
+ }
+
+ public int getScore() {
+ return score;
+ }
+
+ public void setScore(int score) {
+ this.score = score;
+ }
+
+ public boolean isSubmitter() {
+ return isSubmitter;
+ }
+
+ public String getPermalink() {
+ return permalink;
+ }
+
+ public int getDepth() {
+ return depth;
+ }
+
+ public boolean isCollapsed() {
+ return collapsed;
+ }
+
+ public boolean isHasReply() {
+ return hasReply;
+ }
+
+ public void setHasReply(boolean hasReply) {
+ this.hasReply = hasReply;
+ }
+
+ public boolean isScoreHidden() {
+ return scoreHidden;
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/CommentRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentRecyclerViewAdapter.java
new file mode 100644
index 00000000..e8bdd764
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/CommentRecyclerViewAdapter.java
@@ -0,0 +1,79 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.volley.RequestQueue;
+
+import java.util.ArrayList;
+
+class CommentRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ private Context mContext;
+ private ArrayList<CommentData> mCommentData;
+ private RequestQueue mVoteThingRequestQueue;
+
+ CommentRecyclerViewAdapter(Context context, ArrayList<CommentData> commentData,
+ RequestQueue voteThingRequestQueue) {
+ mContext = context;
+ mCommentData = commentData;
+ mVoteThingRequestQueue = voteThingRequestQueue;
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new CommentViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_post_comment, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ ((CommentViewHolder) holder).authorTextView.setText(mCommentData.get(position).getAuthor());
+ ((CommentViewHolder) holder).commentTimeTextView.setText(mCommentData.get(position).getCommentTime());
+ ((CommentViewHolder) holder).commentTextView.setText(mCommentData.get(position).getCommentContent());
+ ((CommentViewHolder) holder).scoreTextView.setText(Integer.toString(mCommentData.get(position).getScore()));
+ ((CommentViewHolder) holder).upvoteButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+
+ }
+ });
+ ((CommentViewHolder) holder).downvoteButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mCommentData.size();
+ }
+
+ private class CommentViewHolder extends RecyclerView.ViewHolder {
+ private TextView authorTextView;
+ private TextView commentTimeTextView;
+ private TextView commentTextView;
+ private ImageView upvoteButton;
+ private ImageView downvoteButton;
+ private TextView scoreTextView;
+ private ImageView replyButton;
+
+ public CommentViewHolder(View itemView) {
+ super(itemView);
+ authorTextView = itemView.findViewById(R.id.author_text_view_item_post_comment);
+ commentTimeTextView = itemView.findViewById(R.id.comment_time_text_view_item_post_comment);
+ commentTextView = itemView.findViewById(R.id.comment_text_view_item_post_comment);
+ upvoteButton = itemView.findViewById(R.id.plus_button_item_post_comment);
+ downvoteButton = itemView.findViewById(R.id.minus_button_item_post_comment);
+ scoreTextView = itemView.findViewById(R.id.score_text_view_item_post_comment);
+ replyButton = itemView.findViewById(R.id.reply_button_item_post_comment);
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchComment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchComment.java
new file mode 100644
index 00000000..5bba9261
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchComment.java
@@ -0,0 +1,42 @@
+package ml.docilealligator.infinityforreddit;
+
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+
+class FetchComment {
+ interface FetchCommentListener {
+ void onFetchCommentSuccess(String response);
+ void onFetchCommentFail();
+ }
+
+ private RequestQueue requestQueue;
+ private String subredditName;
+ private String article;
+ private FetchCommentListener mFetchCommentListener;
+
+ FetchComment(RequestQueue requestQueue, String subredditName, String article) {
+ this.requestQueue = requestQueue;
+ this.subredditName = subredditName;
+ this.article = article;
+ }
+
+ void queryComment(FetchCommentListener fetchCommentListener) {
+ mFetchCommentListener = fetchCommentListener;
+ StringRequest commentRequest = new StringRequest(Request.Method.GET, RedditUtils.getQueryCommentURI(subredditName, article), new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ mFetchCommentListener.onFetchCommentSuccess(response);
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ mFetchCommentListener.onFetchCommentFail();
+ }
+ }) {};
+ commentRequest.setTag(FetchComment.class);
+ requestQueue.add(commentRequest);
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubscribedSubreddits.java b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubscribedSubreddits.java
new file mode 100644
index 00000000..76869e0f
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubscribedSubreddits.java
@@ -0,0 +1,5 @@
+package ml.docilealligator.infinityforreddit;
+
+class FetchSubscribedSubreddits {
+
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/FetchUserInfo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchUserInfo.java
new file mode 100644
index 00000000..bd3e0b9d
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/FetchUserInfo.java
@@ -0,0 +1,69 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+
+import java.util.Map;
+
+class FetchUserInfo {
+ interface FetchUserInfoListener {
+ void onFetchUserInfoSuccess(String response);
+ void onFetchUserInfoFail();
+ }
+
+ private Context context;
+ private RequestQueue requestQueue;
+ private FetchUserInfoListener mFetchUserInfoListener;
+
+ FetchUserInfo(Context context, RequestQueue requestQueue) {
+ this.context = context;
+ this.requestQueue = requestQueue;
+ }
+
+ void queryUserInfo(FetchUserInfoListener fetchUserInfoListener, final int refreshTime) {
+ if(refreshTime < 0) {
+ mFetchUserInfoListener.onFetchUserInfoFail();
+ return;
+ }
+
+ mFetchUserInfoListener = fetchUserInfoListener;
+ StringRequest commentRequest = new StringRequest(Request.Method.GET, RedditUtils.OAUTH_API_BASE_URI + RedditUtils.USER_INFO_SUFFIX, new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ mFetchUserInfoListener.onFetchUserInfoSuccess(response);
+
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if(error instanceof AuthFailureError) {
+ new AcquireAccessToken(context).refreshAccessToken(requestQueue, new AcquireAccessToken.AcquireAccessTokenListener() {
+ @Override
+ public void onAcquireAccessTokenSuccess() {
+ queryUserInfo(mFetchUserInfoListener, refreshTime - 1);
+ }
+
+ @Override
+ public void onAcquireAccessTokenFail() {}
+ });
+ } else {
+ mFetchUserInfoListener.onFetchUserInfoFail();
+ }
+ }
+ }) {
+ @Override
+ public Map<String, String> getHeaders() {
+ String accessToken = context.getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, "");
+ return RedditUtils.getOAuthHeader(accessToken);
+ }
+ };
+ commentRequest.setTag(FetchComment.class);
+ requestQueue.add(commentRequest);
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java
new file mode 100644
index 00000000..3cdb7bd8
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java
@@ -0,0 +1,59 @@
+package ml.docilealligator.infinityforreddit;
+
+/**
+ * Created by alex on 2/25/18.
+ */
+
+class JSONUtils {
+ static final String DATA_KEY = "data";
+ static final String AFTER_KEY = "after";
+ static final String MODHASH_KEY = "modhash";
+ static final String CHILDREN_KEY = "children";
+ static final String COUNT_KEY = "count";
+ static final String TITLE_KEY = "title";
+ static final String NAME_KEY = "name";
+ static final String SUBREDDIT_NAME_PREFIX_KEY = "subreddit_name_prefixed";
+ static final String SELF_TEXT_KEY = "selftext";
+ static final String AUTHOR_KEY = "author";
+ static final String DOMAIN_KEY = "domain";
+ static final String LINK_FLAIR_TEXT_KEY = "link_flair_text";
+ static final String NUM_CROSSPOST_KEY = "num_crossposts";
+ static final String CAN_MOD_POST_KEY = "can_mod_post";
+ static final String SCORE_KEY = "score";
+ static final String LIKES_KEY = "likes";
+ static final String NSFW_KEY = "over_18";
+ static final String GILDED_KEY = "gilded";
+ static final String POST_HINT_KEY = "post_hint";
+ static final String PERMALINK_KEY = "permalink";
+ static final String CREATED_UTC_KEY = "created_utc";
+ static final String PREVIEW_KEY = "preview";
+ static final String IMAGES_KEY = "images";
+ static final String WIDTH_KEY = "width";
+ static final String HEIGHT_KEY = "height";
+ static final String VARIANTS_KEY = "variants";
+ static final String GIF_KEY = "gif";
+ static final String MP4_KEY = "mp4";
+ static final String SOURCE_KEY = "source";
+ static final String URL_KEY = "url";
+ static final String MEDIA_KEY = "media";
+ static final String REDDIT_VIDEO_KEY = "reddit_video";
+ static final String FALLBACK_URL_KEY = "fallback_url";
+ static final String DASH_URL_KEY = "dash_url";
+ static final String IS_VIDEO_KEY = "is_video";
+ static final String CROSSPOST_PARENT_LIST = "crosspost_parent_list";
+ static final String REDDIT_VIDEO_PREVIEW_KEY = "reddit_video_preview";
+ static final String IS_REDDIT_MEDIA_DOMAIN = "is_reddit_media_domain";
+ static final String STICKIED_KEY = "stickied";
+ static final String BODY_KEY = "body";
+ static final String COLLAPSED_KEY = "collapsed";
+ static final String IS_SUBMITTER_KEY = "is_submitter";
+ static final String REPLIES_KEY = "replies";
+ static final String DEPTH_KEY = "depth";
+ static final String ID_KEY = "id";
+ static final String SCORE_HIDDEN_KEY = "score_hidden";
+ static final String SUBREDDIT_KEY = "subreddit";
+ static final String BANNER_IMG_KEY = "banner_img";
+ static final String ICON_IMG_KEY = "icon_img";
+ static final String LINK_KARMA_KEY = "link_karma";
+ static final String COMMENT_KARMA_KEY = "comment_karma";
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/LastItemSynchronizer.java b/app/src/main/java/ml/docilealligator/infinityforreddit/LastItemSynchronizer.java
new file mode 100644
index 00000000..3feef840
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/LastItemSynchronizer.java
@@ -0,0 +1,5 @@
+package ml.docilealligator.infinityforreddit;
+
+interface LastItemSynchronizer {
+ void lastItemChanged(String lastItem);
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/LoginActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/LoginActivity.java
new file mode 100644
index 00000000..605d2d23
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/LoginActivity.java
@@ -0,0 +1,147 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.MenuItem;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.Toast;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+import com.android.volley.toolbox.Volley;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class LoginActivity extends AppCompatActivity {
+
+ private String authCode;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_login);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ WebView webView = findViewById(R.id.webview_login_activity);
+ webView.getSettings().setJavaScriptEnabled(true);
+
+ Uri baseUri = Uri.parse(RedditUtils.OAUTH_URL);
+ Uri.Builder uriBuilder = baseUri.buildUpon();
+ uriBuilder.appendQueryParameter(RedditUtils.CLIENT_ID_KEY, RedditUtils.CLIENT_ID);
+ uriBuilder.appendQueryParameter(RedditUtils.RESPONSE_TYPE_KEY, RedditUtils.RESPONSE_TYPE);
+ uriBuilder.appendQueryParameter(RedditUtils.STATE_KEY, RedditUtils.STATE);
+ uriBuilder.appendQueryParameter(RedditUtils.REDIRECT_URI_KEY, RedditUtils.REDIRECT_URI);
+ uriBuilder.appendQueryParameter(RedditUtils.DURATION_KEY, RedditUtils.DURATION);
+ uriBuilder.appendQueryParameter(RedditUtils.SCOPE_KEY, RedditUtils.SCOPE);
+
+ String url = uriBuilder.toString();
+
+ webView.loadUrl(url);
+ webView.setWebViewClient(new WebViewClient() {
+ @Override
+ public boolean shouldOverrideUrlLoading(WebView view, String url) {
+ view.loadUrl(url);
+ return true;
+ }
+
+ @Override
+ public void onPageStarted(WebView view, String url, Bitmap favicon) {
+ super.onPageStarted(view, url, favicon);
+ }
+
+ @Override
+ public void onPageFinished(WebView view, String url) {
+ super.onPageFinished(view, url);
+ if(url.contains("&code=") || url.contains("?code=")) {
+ Uri uri = Uri.parse(url);
+ String state = uri.getQueryParameter("state");
+ if(state.equals(RedditUtils.STATE)) {
+ authCode = uri.getQueryParameter("code");
+ Intent intent = new Intent(LoginActivity.this, MainActivity.class);
+ intent.putExtra("authCode", authCode);
+
+ final SharedPreferences.Editor editor = getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).edit();
+ editor.putString(SharedPreferencesUtils.AUTH_CODE_KEY, authCode);
+ editor.apply();
+
+ RequestQueue queue = Volley.newRequestQueue(LoginActivity.this);
+ String tokenRetrievalUrl = "https://www.reddit.com/api/v1/access_token";
+ StringRequest requestToken = new StringRequest(Request.Method.POST, tokenRetrievalUrl, new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ try {
+ JSONObject responseJSON = new JSONObject(response);
+ String accessToken = responseJSON.getString(RedditUtils.ACCESS_TOKEN_KEY);
+ String refreshToken = responseJSON.getString(RedditUtils.REFRESH_TOKEN_KEY);
+
+ editor.putString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, accessToken);
+ editor.putString(SharedPreferencesUtils.REFRESH_TOKEN_KEY, refreshToken);
+ editor.apply();
+ finish();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Toast.makeText(LoginActivity.this, "Error occurred when parsing the JSON response", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ Toast.makeText(LoginActivity.this, "Error Retrieving the token", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }){
+ @Override
+ protected Map<String, String> getParams() throws AuthFailureError {
+ Map<String, String> params = new HashMap<>();
+ params.put(RedditUtils.GRANT_TYPE_KEY, "authorization_code");
+ params.put("code", authCode);
+ params.put("redirect_uri", RedditUtils.REDIRECT_URI);
+
+ return params;
+ }
+
+ @Override
+ public Map<String, String> getHeaders() throws AuthFailureError {
+ return RedditUtils.getHttpBasicAuthHeader();
+ }
+ };
+
+ queue.add(requestToken);
+ } else {
+ Toast.makeText(LoginActivity.this, "Something went wrong. Try again later.", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ } else if (url.contains("error=access_denied")) {
+ Toast.makeText(LoginActivity.this, "Access denied", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java
new file mode 100644
index 00000000..ed792b2e
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java
@@ -0,0 +1,232 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.design.widget.NavigationView;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.app.ActionBarDrawerToggle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.volley.toolbox.Volley;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestManager;
+
+import de.hdodenhof.circleimageview.CircleImageView;
+
+public class MainActivity extends AppCompatActivity
+ implements NavigationView.OnNavigationItemSelectedListener {
+
+ private String nameState = "NS";
+ private String profileImageUrlState = "PIUS";
+ private String bannerImageUrlState = "BIUS";
+ private String karmaState = "KS";
+
+ private TextView mNameTextView;
+ private TextView mKarmaTextView;
+ private CircleImageView mProfileImageView;
+ private ImageView mBannerImageView;
+
+ private Fragment mFragment;
+ private RequestManager glide;
+
+ private String mName;
+ private String mProfileImageUrl;
+ private String mBannerImageUrl;
+ private String mKarma;
+ private boolean mFetchUserInfoSuccess;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ Toolbar toolbar = findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+
+ DrawerLayout drawer = findViewById(R.id.drawer_layout);
+ ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
+ this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
+ drawer.addDrawerListener(toggle);
+ toggle.syncState();
+
+ NavigationView navigationView = findViewById(R.id.nav_view);
+ navigationView.setNavigationItemSelectedListener(this);
+
+ View header = navigationView.getHeaderView(0);
+ mNameTextView = header.findViewById(R.id.name_text_view_nav_header_main);
+ mKarmaTextView = header.findViewById(R.id.karma_text_view_nav_header_main);
+ mProfileImageView = header.findViewById(R.id.profile_image_view_nav_header_main);
+ mBannerImageView = header.findViewById(R.id.banner_image_view_nav_header_main);
+
+ mName = getSharedPreferences(SharedPreferencesUtils.USER_INFO_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.USER_KEY, "");
+ mProfileImageUrl = getSharedPreferences(SharedPreferencesUtils.USER_INFO_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.PROFILE_IMAGE_URL_KEY, "");
+ mBannerImageUrl = getSharedPreferences(SharedPreferencesUtils.USER_INFO_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.BANNER_IMAGE_URL_KEY, "");
+ mKarma = getSharedPreferences(SharedPreferencesUtils.USER_INFO_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.KARMA_KEY, "");
+
+ mNameTextView.setText(mName);
+ mKarmaTextView.setText(mKarma);
+ glide = Glide.with(this);
+ if(!mProfileImageUrl.equals("")) {
+ glide.load(mProfileImageUrl).into(mProfileImageView);
+ }
+ if(!mBannerImageUrl.equals("")) {
+ glide.load(mBannerImageUrl).into(mBannerImageView);
+ }
+
+ String accessToken = getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, "");
+ if(accessToken.equals("")) {
+ Intent loginIntent = new Intent(this, LoginActivity.class);
+ startActivity(loginIntent);
+ } else {
+ if(savedInstanceState == null) {
+ FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
+ mFragment = new BestPostFragment();
+ fragmentTransaction.replace(R.id.frame_layout_content_main, mFragment).commit();
+ } else {
+ mFragment = getFragmentManager().getFragment(savedInstanceState, "outStateFragment");
+ getFragmentManager().beginTransaction().replace(R.id.frame_layout_content_main, mFragment).commit();
+ }
+ }
+
+ if(savedInstanceState == null && !mFetchUserInfoSuccess) {
+ new FetchUserInfo(this, Volley.newRequestQueue(this)).queryUserInfo(new FetchUserInfo.FetchUserInfoListener() {
+ @Override
+ public void onFetchUserInfoSuccess(String response) {
+ new ParseUserInfo().parseUserInfo(MainActivity.this, response, new ParseUserInfo.ParseUserInfoListener() {
+ @Override
+ public void onParseUserInfoSuccess(String name, String profileImageUrl, String bannerImageUrl, int karma) {
+ mNameTextView.setText(name);
+ if(!mProfileImageUrl.equals("")) {
+ glide.load(profileImageUrl).into(mProfileImageView);
+ }
+ if(!mBannerImageUrl.equals("")) {
+ glide.load(bannerImageUrl).into(mBannerImageView);
+ }
+
+ mName = name;
+ mProfileImageUrl = profileImageUrl;
+ mBannerImageUrl = bannerImageUrl;
+ mKarma = getString(R.string.karma_info, karma);
+
+ mKarmaTextView.setText(mKarma);
+
+ SharedPreferences.Editor editor = getSharedPreferences(SharedPreferencesUtils.USER_INFO_FILE_KEY, Context.MODE_PRIVATE).edit();
+ editor.putString(SharedPreferencesUtils.USER_KEY, name);
+ editor.putString(SharedPreferencesUtils.PROFILE_IMAGE_URL_KEY, profileImageUrl);
+ editor.putString(SharedPreferencesUtils.BANNER_IMAGE_URL_KEY, bannerImageUrl);
+ editor.putString(SharedPreferencesUtils.KARMA_KEY, mKarma);
+ editor.apply();
+ mFetchUserInfoSuccess = true;
+ }
+
+ @Override
+ public void onParseUserInfoFail() {
+ mFetchUserInfoSuccess = false;
+ }
+ });
+ }
+
+ @Override
+ public void onFetchUserInfoFail() {
+
+ }
+ }, 1);
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ DrawerLayout drawer = findViewById(R.id.drawer_layout);
+ if (drawer.isDrawerOpen(GravityCompat.START)) {
+ drawer.closeDrawer(GravityCompat.START);
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ // Inflate the menu; this adds items to the action bar if it is present.
+ getMenuInflater().inflate(R.menu.main, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // Handle action bar item clicks here. The action bar will
+ // automatically handle clicks on the Home/Up button, so long
+ // as you specify a parent activity in AndroidManifest.xml.
+ int id = item.getItemId();
+
+ //noinspection SimplifiableIfStatement
+ if (id == R.id.action_settings) {
+ return true;
+ }
+
+ return super.onOptionsItemSelected(item);
+ }
+
+ @SuppressWarnings("StatementWithEmptyBody")
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ // Handle navigation view item clicks here.
+ int id = item.getItemId();
+
+ if (id == R.id.nav_camera) {
+ // Handle the camera action
+ } else if (id == R.id.nav_gallery) {
+
+ } else if (id == R.id.nav_slideshow) {
+
+ } else if (id == R.id.nav_manage) {
+
+ } else if (id == R.id.nav_share) {
+
+ } else if (id == R.id.nav_send) {
+
+ }
+
+ DrawerLayout drawer = findViewById(R.id.drawer_layout);
+ drawer.closeDrawer(GravityCompat.START);
+ return true;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if(mFragment != null) {
+ getFragmentManager().putFragment(outState, "outStateFragment", mFragment);
+ }
+ outState.putString(nameState, mName);
+ outState.putString(profileImageUrlState, mProfileImageUrl);
+ outState.putString(bannerImageUrlState, mBannerImageUrl);
+ outState.putString(karmaState, mKarma);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ mName = savedInstanceState.getString(nameState);
+ mProfileImageUrl = savedInstanceState.getString(profileImageUrlState);
+ mBannerImageUrl = savedInstanceState.getString(bannerImageUrlState);
+ mKarma = savedInstanceState.getString(karmaState);
+ mNameTextView.setText(mName);
+ mKarmaTextView.setText(mKarma);
+ if(!mProfileImageUrl.equals("")) {
+ glide.load(mProfileImageUrl).into(mProfileImageView);
+ }
+ if(!mBannerImageUrl.equals("")) {
+ glide.load(mBannerImageUrl).into(mBannerImageView);
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationNotifier.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationNotifier.java
new file mode 100644
index 00000000..433daa15
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationNotifier.java
@@ -0,0 +1,6 @@
+package ml.docilealligator.infinityforreddit;
+
+interface PaginationNotifier {
+ void LoadMorePostSuccess();
+ void LoadMorePostFail();
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRequestQueueSynchronizer.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRequestQueueSynchronizer.java
new file mode 100644
index 00000000..bd81e4a3
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRequestQueueSynchronizer.java
@@ -0,0 +1,7 @@
+package ml.docilealligator.infinityforreddit;
+
+import com.android.volley.RequestQueue;
+
+interface PaginationRequestQueueSynchronizer {
+ void passQueue(RequestQueue q);
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRetryNotifier.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRetryNotifier.java
new file mode 100644
index 00000000..b9a93f73
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRetryNotifier.java
@@ -0,0 +1,5 @@
+package ml.docilealligator.infinityforreddit;
+
+interface PaginationRetryNotifier {
+ void retry();
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationSynchronizer.java b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationSynchronizer.java
new file mode 100644
index 00000000..38cc7bef
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/PaginationSynchronizer.java
@@ -0,0 +1,96 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+class PaginationSynchronizer implements Parcelable {
+ private boolean loadingState;
+ private boolean loadSuccess;
+ private PaginationNotifier paginationNotifier;
+ private PaginationRetryNotifier paginationRetryNotifier;
+ private LastItemSynchronizer lastItemSynchronizer;
+ private PaginationRequestQueueSynchronizer paginationRequestQueueSynchronizer;
+
+ PaginationSynchronizer() {
+ loadingState = false;
+ loadSuccess = true;
+ }
+
+ protected PaginationSynchronizer(Parcel in) {
+ loadingState = in.readByte() != 0;
+ loadSuccess = in.readByte() != 0;
+ }
+
+ public static final Creator<PaginationSynchronizer> CREATOR = new Creator<PaginationSynchronizer>() {
+ @Override
+ public PaginationSynchronizer createFromParcel(Parcel in) {
+ return new PaginationSynchronizer(in);
+ }
+
+ @Override
+ public PaginationSynchronizer[] newArray(int size) {
+ return new PaginationSynchronizer[size];
+ }
+ };
+
+ public void setLoading(boolean isLoading) {
+ this.loadingState = isLoading;
+ }
+
+ public boolean isLoading() {
+ return loadingState;
+ }
+
+ public void setLoadingState(boolean state) {
+ loadSuccess = state;
+ if(loadSuccess) {
+ paginationNotifier.LoadMorePostSuccess();
+ } else {
+ paginationNotifier.LoadMorePostFail();
+ }
+ }
+
+ public boolean isLoadSuccess() {
+ return loadSuccess;
+ }
+
+ public void setPaginationNotifier(PaginationNotifier paginationNotifier) {
+ this.paginationNotifier = paginationNotifier;
+ }
+
+ public void setPaginationRetryNotifier(PaginationRetryNotifier paginationRetryNotifier) {
+ this.paginationRetryNotifier = paginationRetryNotifier;
+ }
+
+ public PaginationRetryNotifier getPaginationRetryNotifier() {
+ return paginationRetryNotifier;
+ }
+
+ public void setLastItemSynchronizer(LastItemSynchronizer lastItemSynchronizer) {
+ this.lastItemSynchronizer = lastItemSynchronizer;
+ }
+
+ public LastItemSynchronizer getLastItemSynchronizer() {
+ return lastItemSynchronizer;
+ }
+
+ public void setPaginationRequestQueueSynchronizer(PaginationRequestQueueSynchronizer paginationRequestQueueSynchronizer) {
+ this.paginationRequestQueueSynchronizer = paginationRequestQueueSynchronizer;
+ }
+
+ public PaginationRequestQueueSynchronizer getPaginationRequestQueueSynchronizer() {
+ return paginationRequestQueueSynchronizer;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeByte((byte) (loadingState ? 1 : 0));
+ parcel.writeByte((byte) (loadSuccess ? 1 : 0));
+ }
+}
+
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseBestPost.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseBestPost.java
new file mode 100644
index 00000000..3c1121a6
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseBestPost.java
@@ -0,0 +1,304 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+/**
+ * Created by alex on 3/21/18.
+ */
+
+class ParseBestPost {
+
+ interface ParseBestPostListener {
+ void onParseBestPostSuccess(ArrayList<BestPostData> bestPostData, String lastItem);
+ void onParseBestPostFail();
+ }
+
+ private Context mContext;
+ private ParseBestPostListener mParseBetPostListener;
+
+ ParseBestPost(Context context, ParseBestPostListener parseBestPostListener) {
+ mContext = context;
+ mParseBetPostListener = parseBestPostListener;
+ }
+
+ void parseBestPost(String response, ArrayList<BestPostData> bestPostData) {
+ new ParseBestPostDataAsyncTask(response, bestPostData).execute();
+ }
+
+ private class ParseBestPostDataAsyncTask extends AsyncTask<Void, Void, Void> {
+ private JSONObject jsonResponse;
+ private ArrayList<BestPostData> bestPostData;
+ private ArrayList<BestPostData> newBestPostData;
+ private String lastItem;
+ private boolean parseFailed;
+
+ ParseBestPostDataAsyncTask(String response, ArrayList<BestPostData> bestPostData) {
+ try {
+ jsonResponse = new JSONObject(response);
+ this.bestPostData = bestPostData;
+ newBestPostData = new ArrayList<>();
+ parseFailed = false;
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Toast.makeText(mContext, "Error converting response to JSON", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ try {
+ JSONArray allData = jsonResponse.getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.CHILDREN_KEY);
+ if(bestPostData == null) {
+ bestPostData = new ArrayList<>();
+ }
+
+ lastItem = jsonResponse.getJSONObject(JSONUtils.DATA_KEY).getString(JSONUtils.AFTER_KEY);
+ for(int i = 0; i < allData.length(); i++) {
+ JSONObject data = allData.getJSONObject(i).getJSONObject(JSONUtils.DATA_KEY);
+ String id = data.getString(JSONUtils.ID_KEY);
+ String fullName = data.getString(JSONUtils.NAME_KEY);
+ String subredditName = data.getString(JSONUtils.SUBREDDIT_NAME_PREFIX_KEY);
+ long postTime = data.getLong(JSONUtils.CREATED_UTC_KEY) * 1000;
+ String title = data.getString(JSONUtils.TITLE_KEY);
+ int score = data.getInt(JSONUtils.SCORE_KEY);
+ int voteType;
+ boolean nsfw = data.getBoolean(JSONUtils.NSFW_KEY);
+
+ if(data.isNull(JSONUtils.LIKES_KEY)) {
+ voteType = 0;
+ } else {
+ voteType = data.getBoolean(JSONUtils.LIKES_KEY) ? 1 : -1;
+ }
+ Calendar postTimeCalendar = Calendar.getInstance();
+ postTimeCalendar.setTimeInMillis(postTime);
+ String formattedPostTime = new SimpleDateFormat("MMM d, YYYY, HH:mm",
+ mContext.getResources().getConfiguration().locale).format(postTimeCalendar.getTime());
+ String permalink = data.getString(JSONUtils.PERMALINK_KEY);
+
+ String previewUrl = "";
+ if(data.has(JSONUtils.PREVIEW_KEY)) {
+ previewUrl = data.getJSONObject(JSONUtils.PREVIEW_KEY).getJSONArray(JSONUtils.IMAGES_KEY).getJSONObject(0)
+ .getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+ }
+
+ if(data.has(JSONUtils.CROSSPOST_PARENT_LIST)) {
+ //Cross post
+ data = data.getJSONArray(JSONUtils.CROSSPOST_PARENT_LIST).getJSONObject(0);
+ parseData(data, permalink, newBestPostData, id, fullName, subredditName,
+ formattedPostTime, title, previewUrl, score, voteType, nsfw, i);
+ } else {
+ parseData(data, permalink, newBestPostData, id, fullName, subredditName,
+ formattedPostTime, title, previewUrl, score, voteType, nsfw, i);
+ }
+ }
+ } catch (JSONException e) {
+ Log.e("error", e.getMessage());
+ Log.i("Best post", "Error parsing data");
+ parseFailed = true;
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ if(!parseFailed) {
+ bestPostData.addAll(newBestPostData);
+ mParseBetPostListener.onParseBestPostSuccess(bestPostData, lastItem);
+ } else {
+ mParseBetPostListener.onParseBestPostFail();
+ }
+ }
+ }
+
+ private void parseData(JSONObject data, String permalink, ArrayList<BestPostData> bestPostData,
+ String id, String fullName, String subredditName, String formattedPostTime, String title,
+ String previewUrl, int score, int voteType, boolean nsfw, int i) throws JSONException {
+ boolean isVideo = data.getBoolean(JSONUtils.IS_VIDEO_KEY);
+ String url = data.getString(JSONUtils.URL_KEY);
+
+ if(!data.has(JSONUtils.PREVIEW_KEY) && previewUrl.equals("")) {
+ if(url.contains(permalink)) {
+ //Text post
+ Log.i("text", Integer.toString(i));
+ int postType = BestPostData.TEXT_TYPE;
+ BestPostData postData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, permalink, score, postType, voteType, nsfw);
+ postData.setSelfText(data.getString(JSONUtils.SELF_TEXT_KEY).trim());
+ bestPostData.add(postData);
+ } else {
+ //No preview link post
+ Log.i("no preview link", Integer.toString(i));
+ int postType = BestPostData.NO_PREVIEW_LINK_TYPE;
+ BestPostData linkPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, url, permalink, score, postType, voteType, nsfw);
+ bestPostData.add(linkPostData);
+ }
+ } else if(isVideo) {
+ //Video post
+ Log.i("video", Integer.toString(i));
+ JSONObject redditVideoObject = data.getJSONObject(JSONUtils.MEDIA_KEY).getJSONObject(JSONUtils.REDDIT_VIDEO_KEY);
+ int postType = BestPostData.VIDEO_TYPE;
+ String videoUrl = redditVideoObject.getString(JSONUtils.DASH_URL_KEY);
+
+ BestPostData videoPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw, true);
+
+ videoPostData.setVideoUrl(videoUrl);
+ videoPostData.setDownloadableGifOrVideo(false);
+
+ bestPostData.add(videoPostData);
+ } else {
+ JSONObject variations = data.getJSONObject(JSONUtils.PREVIEW_KEY).getJSONArray(JSONUtils.IMAGES_KEY).getJSONObject(0);
+ if (variations.has(JSONUtils.VARIANTS_KEY) && variations.getJSONObject(JSONUtils.VARIANTS_KEY).has(JSONUtils.MP4_KEY)) {
+ //Gif video post (MP4)
+ Log.i("gif video mp4", Integer.toString(i));
+ int postType = BestPostData.GIF_VIDEO_TYPE;
+ String videoUrl = variations.getJSONObject(JSONUtils.VARIANTS_KEY).getJSONObject(JSONUtils.MP4_KEY).getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+ String gifDownloadUrl = variations.getJSONObject(JSONUtils.VARIANTS_KEY).getJSONObject(JSONUtils.GIF_KEY).getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+ BestPostData post = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw, false);
+
+ post.setVideoUrl(videoUrl);
+ post.setDownloadableGifOrVideo(true);
+ post.setGifOrVideoDownloadUrl(gifDownloadUrl);
+
+ bestPostData.add(post);
+ } else if(data.getJSONObject(JSONUtils.PREVIEW_KEY).has(JSONUtils.REDDIT_VIDEO_PREVIEW_KEY)) {
+ //Gif video post (Dash)
+ Log.i("gif video dash", Integer.toString(i));
+ int postType = BestPostData.GIF_VIDEO_TYPE;
+ String videoUrl = data.getJSONObject(JSONUtils.PREVIEW_KEY)
+ .getJSONObject(JSONUtils.REDDIT_VIDEO_PREVIEW_KEY).getString(JSONUtils.DASH_URL_KEY);
+
+ BestPostData post = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw, true);
+
+ post.setVideoUrl(videoUrl);
+ post.setDownloadableGifOrVideo(false);
+
+ bestPostData.add(post);
+ } else {
+ if (url.endsWith("jpg") || url.endsWith("png")) {
+ //Image post
+ Log.i("image", Integer.toString(i));
+ int postType = BestPostData.IMAGE_TYPE;
+ bestPostData.add(new BestPostData(id, fullName, subredditName, formattedPostTime, title, url, url, permalink, score, postType, voteType, nsfw));
+ } else {
+ //Link post
+ Log.i("link", Integer.toString(i));
+ int postType = BestPostData.LINK_TYPE;
+ BestPostData linkPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, url, permalink, score, postType, voteType, nsfw);
+ bestPostData.add(linkPostData);
+ }
+ }
+ }
+ }
+
+ /*private void parseData(JSONObject data, String permalink, ArrayList<BestPostData> bestPostData,
+ String id, String fullName, String subredditName, String formattedPostTime, String title,
+ int score, int voteType, boolean nsfw, int i) throws JSONException {
+ boolean isVideo = data.getBoolean(JSONUtils.IS_VIDEO_KEY);
+ if(!data.has(JSONUtils.PREVIEW_KEY)) {
+ String url = data.getString(JSONUtils.URL_KEY);
+ if(url.contains(permalink)) {
+ //Text post
+ Log.i("text", Integer.toString(i));
+ int postType = BestPostData.TEXT_TYPE;
+ BestPostData postData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, permalink, score, postType, voteType, nsfw);
+ postData.setSelfText(data.getString(JSONUtils.SELF_TEXT_KEY).trim());
+ bestPostData.add(postData);
+ } else {
+ //No preview link post
+ Log.i("no preview link", Integer.toString(i));
+ int postType = BestPostData.NO_PREVIEW_LINK_TYPE;
+ BestPostData post = new BestPostData(id, fullName, subredditName, formattedPostTime, title, permalink, score, postType, voteType, nsfw);
+ post.setLinkUrl(url);
+ bestPostData.add(post);
+ }
+ } else if (!isVideo) {
+ JSONObject variations = data.getJSONObject(JSONUtils.PREVIEW_KEY).getJSONArray(JSONUtils.IMAGES_KEY).getJSONObject(0);
+ String previewUrl = variations.getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+ if (variations.has(JSONUtils.VARIANTS_KEY)) {
+ if (variations.getJSONObject(JSONUtils.VARIANTS_KEY).has(JSONUtils.MP4_KEY)) {
+ //Gif video
+ Log.i("gif video", Integer.toString(i));
+ int postType = BestPostData.GIF_VIDEO_TYPE;
+ String videoUrl = variations.getJSONObject(JSONUtils.VARIANTS_KEY).getJSONObject(JSONUtils.MP4_KEY).getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+
+ BestPostData post = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw);
+ post.setVideoUrl(videoUrl);
+ bestPostData.add(post);
+ } else if (variations.getJSONObject(JSONUtils.VARIANTS_KEY).has(JSONUtils.GIF_KEY)) {
+ //Gif post
+ Log.i("gif", Integer.toString(i));
+ int postType = BestPostData.GIF_TYPE;
+ String gifUrl = variations.getJSONObject(JSONUtils.VARIANTS_KEY).getJSONObject(JSONUtils.GIF_KEY).getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+
+ BestPostData post = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw);
+ post.setGifUrl(gifUrl);
+ bestPostData.add(post);
+ } else {
+ if(data.getJSONObject(JSONUtils.PREVIEW_KEY).has(JSONUtils.REDDIT_VIDEO_PREVIEW_KEY)) {
+ //Gif link post
+ Log.i("gif link", Integer.toString(i));
+ int postType = BestPostData.LINK_TYPE;
+ String gifUrl = data.getString(JSONUtils.URL_KEY);
+ BestPostData gifLinkPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw);
+ gifLinkPostData.setLinkUrl(gifUrl);
+ bestPostData.add(gifLinkPostData);
+ } else {
+ if(!data.isNull(JSONUtils.MEDIA_KEY)) {
+ //Video link post
+ Log.i("video link", Integer.toString(i));
+ int postType = BestPostData.LINK_TYPE;
+ String videoUrl = data.getString(JSONUtils.URL_KEY);
+ BestPostData videoLinkPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw);
+ videoLinkPostData.setLinkUrl(videoUrl);
+ bestPostData.add(videoLinkPostData);
+ } else {
+ if(data.getBoolean(JSONUtils.IS_REDDIT_MEDIA_DOMAIN)) {
+ //Image post
+ Log.i("image", Integer.toString(i));
+ int postType = BestPostData.IMAGE_TYPE;
+ bestPostData.add(new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw));
+ } else {
+ //Link post
+ Log.i("link", Integer.toString(i));
+ int postType = BestPostData.LINK_TYPE;
+ String linkUrl = data.getString(JSONUtils.URL_KEY);
+ BestPostData linkPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw);
+ linkPostData.setLinkUrl(linkUrl);
+ bestPostData.add(linkPostData);
+ }
+ }
+ }
+ }
+ } else {
+ //Image post
+ Toast.makeText(mContext, "Fixed post" + Integer.toString(i), Toast.LENGTH_SHORT).show();
+ Log.i("fixed image", Integer.toString(i));
+ int postType = BestPostData.IMAGE_TYPE;
+ bestPostData.add(new BestPostData(id, fullName, subredditName, formattedPostTime, title, previewUrl, permalink, score, postType, voteType, nsfw));
+ }
+ } else {
+ //Video post
+ Log.i("video", Integer.toString(i));
+ JSONObject redditVideoObject = data.getJSONObject(JSONUtils.MEDIA_KEY).getJSONObject(JSONUtils.REDDIT_VIDEO_KEY);
+ int postType = BestPostData.VIDEO_TYPE;
+ String videoUrl = redditVideoObject.getString(JSONUtils.DASH_URL_KEY);
+
+ String videoPreviewUrl = data.getJSONObject(JSONUtils.PREVIEW_KEY).getJSONArray(JSONUtils.IMAGES_KEY).getJSONObject(0).getJSONObject(JSONUtils.SOURCE_KEY).getString(JSONUtils.URL_KEY);
+
+ BestPostData videoPostData = new BestPostData(id, fullName, subredditName, formattedPostTime, title, videoPreviewUrl, permalink, score, postType, voteType, nsfw);
+ videoPostData.setVideoUrl(videoUrl);
+
+ bestPostData.add(videoPostData);
+ }
+ }*/
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java
new file mode 100644
index 00000000..dc40018c
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java
@@ -0,0 +1,105 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+
+class ParseComment {
+ interface ParseCommentListener {
+ void onParseCommentSuccess(ArrayList<CommentData> commentData, int moreCommentCount);
+ void onParseCommentFail();
+ }
+
+ private Context mContext;
+ private ParseCommentListener mParseCommentListener;
+
+ void parseComment(Context context, String response, ArrayList<CommentData> commentData, ParseCommentListener parseCommentListener) {
+ mParseCommentListener = parseCommentListener;
+ mContext = context;
+ new ParseCommentAsyncTask(response, commentData).execute();
+ }
+
+ private class ParseCommentAsyncTask extends AsyncTask<Void, Void, Void> {
+ private JSONArray jsonResponse;
+ private ArrayList<CommentData> commentData;
+ private ArrayList<CommentData> newcommentData;
+ private boolean parseFailed;
+ int moreCommentCount;
+
+ ParseCommentAsyncTask(String response, ArrayList<CommentData> commentData){
+ try {
+ jsonResponse = new JSONArray(response);
+ this.commentData = commentData;
+ newcommentData = new ArrayList<>();
+ parseFailed = false;
+ } catch (JSONException e) {
+ Log.i("comment json error", e.getMessage());
+ mParseCommentListener.onParseCommentFail();
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ try {
+ moreCommentCount = 0;
+ int actualCommentLength;
+
+ JSONArray allComments = jsonResponse.getJSONObject(1).getJSONObject(JSONUtils.DATA_KEY).getJSONArray(JSONUtils.CHILDREN_KEY);
+ JSONObject more = allComments.getJSONObject(allComments.length() - 1).getJSONObject(JSONUtils.DATA_KEY);
+
+ if(more.has(JSONUtils.COUNT_KEY)) {
+ moreCommentCount = more.getInt(JSONUtils.COUNT_KEY);
+ actualCommentLength = allComments.length() - 1;
+ } else {
+ actualCommentLength = allComments.length();
+ }
+
+ for (int i = 0; i < actualCommentLength; i++) {
+ JSONObject data = allComments.getJSONObject(i).getJSONObject(JSONUtils.DATA_KEY);
+ String id = data.getString(JSONUtils.ID_KEY);
+ String author = data.getString(JSONUtils.AUTHOR_KEY);
+ boolean isSubmitter = data.getBoolean(JSONUtils.IS_SUBMITTER_KEY);
+ String commentContent = data.getString(JSONUtils.BODY_KEY);
+ String permalink = data.getString(JSONUtils.PERMALINK_KEY);
+ int score = data.getInt(JSONUtils.SCORE_KEY);
+ long submitTime = data.getLong(JSONUtils.CREATED_UTC_KEY) * 1000;
+ boolean scoreHidden = data.getBoolean(JSONUtils.SCORE_HIDDEN_KEY);
+
+ Calendar submitTimeCalendar = Calendar.getInstance();
+ submitTimeCalendar.setTimeInMillis(submitTime);
+ String formattedSubmitTime = new SimpleDateFormat("MMM d, YYYY, HH:mm",
+ mContext.getResources().getConfiguration().locale).format(submitTimeCalendar.getTime());
+
+ int depth = data.getInt(JSONUtils.DEPTH_KEY);
+ boolean collapsed = data.getBoolean(JSONUtils.COLLAPSED_KEY);
+ boolean hasReply = !(data.get(JSONUtils.REPLIES_KEY) instanceof String);
+
+ newcommentData.add(new CommentData(id, author, formattedSubmitTime, commentContent, score, isSubmitter, permalink, depth, collapsed, hasReply, scoreHidden));
+ }
+ } catch (JSONException e) {
+ parseFailed = true;
+ Log.i("parse comment error", e.getMessage());
+ mParseCommentListener.onParseCommentFail();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ if(!parseFailed) {
+ commentData.addAll(newcommentData);
+ mParseCommentListener.onParseCommentSuccess(commentData, moreCommentCount);
+ } else {
+ mParseCommentListener.onParseCommentFail();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubscribedSubreddits.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubscribedSubreddits.java
new file mode 100644
index 00000000..62b5eee9
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubscribedSubreddits.java
@@ -0,0 +1,4 @@
+package ml.docilealligator.infinityforreddit;
+
+class ParseSubscribedSubreddits {
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ParseUserInfo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseUserInfo.java
new file mode 100644
index 00000000..49ff057b
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ParseUserInfo.java
@@ -0,0 +1,70 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.text.Html;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+class ParseUserInfo {
+ interface ParseUserInfoListener {
+ void onParseUserInfoSuccess(String name, String profileImageUrl, String bannerImageUrl, int karma);
+ void onParseUserInfoFail();
+ }
+
+ private Context mContext;
+ private ParseUserInfoListener mParseUserInfoListener;
+
+ void parseUserInfo(Context context, String response, ParseUserInfoListener parseUserInfoListener) {
+ mParseUserInfoListener = parseUserInfoListener;
+ mContext = context;
+ new ParseUserInfo.ParseUserInfoAsyncTask(response).execute();
+ }
+
+ private class ParseUserInfoAsyncTask extends AsyncTask<Void, Void, Void> {
+ private JSONObject jsonResponse;
+ private boolean parseFailed;
+
+ private String name;
+ private String profileImageUrl;
+ private String bannerImageUrl;
+ private int karma;
+
+ ParseUserInfoAsyncTask(String response){
+ try {
+ jsonResponse = new JSONObject(response);
+ parseFailed = false;
+ } catch (JSONException e) {
+ Log.i("user info json error", e.getMessage());
+ mParseUserInfoListener.onParseUserInfoFail();
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ try {
+ name = jsonResponse.getString(JSONUtils.NAME_KEY);
+ profileImageUrl = Html.fromHtml(jsonResponse.getString(JSONUtils.ICON_IMG_KEY)).toString();
+ bannerImageUrl = Html.fromHtml(jsonResponse.getJSONObject(JSONUtils.SUBREDDIT_KEY).getString(JSONUtils.BANNER_IMG_KEY)).toString();
+ int linkKarma = jsonResponse.getInt(JSONUtils.LINK_KARMA_KEY);
+ int commentKarma = jsonResponse.getInt(JSONUtils.COMMENT_KARMA_KEY);
+ karma = linkKarma + commentKarma;
+ } catch (JSONException e) {
+ parseFailed = true;
+ Log.i("parse comment error", e.getMessage());
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ if(!parseFailed) {
+ mParseUserInfoListener.onParseUserInfoSuccess(name, profileImageUrl, bannerImageUrl, karma);
+ } else {
+ mParseUserInfoListener.onParseUserInfoFail();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditUtils.java
new file mode 100644
index 00000000..6d421c09
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditUtils.java
@@ -0,0 +1,73 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.util.Base64;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by alex on 2/23/18.
+ */
+
+class RedditUtils {
+ static final String OAUTH_URL ="https://www.reddit.com/api/v1/authorize.compact";
+ static final String ACQUIRE_ACCESS_TOKEN_URL = "https://www.reddit.com/api/v1/access_token";
+ static final String OAUTH_API_BASE_URI = "https://oauth.reddit.com";
+ static final String API_BASE_URI = "https://www.reddit.com";
+ static final String BEST_POST_SUFFIX = "/best?raw_json=1";
+ static final String VOTE_SUFFIX = "/api/vote";
+ static final String USER_INFO_SUFFIX = "/api/v1/me?raw_json=1";
+
+ static final String CLIENT_ID_KEY = "client_id";
+ static final String CLIENT_ID = "";
+ static final String RESPONSE_TYPE_KEY = "response_type";
+ static final String RESPONSE_TYPE = "code";
+ static final String STATE_KEY = "state";
+ static final String STATE = "";
+ static final String REDIRECT_URI_KEY = "redirect_uri";
+ static final String REDIRECT_URI = "";
+ static final String DURATION_KEY = "duration";
+ static final String DURATION = "permanent";
+ static final String SCOPE_KEY = "scope";
+ static final String SCOPE = "identity edit flair history modconfig modflair modlog modposts modwiki mysubreddits privatemessages read report save submit subscribe vote wikiedit wikiread";
+ static final String ACCESS_TOKEN_KEY = "access_token";
+ static final String EXPIRES_IN_KEY = "expires_in";
+
+ static final String AUTHORIZATION_KEY = "Authorization";
+ static final String AUTHORIZATION_BASE = "bearer ";
+ static final String USER_AGENT_KEY = "User-Agent";
+ static final String USER_AGENT = "";
+
+ static final String GRANT_TYPE_KEY = "grant_type";
+ static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
+ static final String REFRESH_TOKEN_KEY = "refresh_token";
+
+ static final String DIR_KEY = "dir";
+ static final String ID_KEY = "id";
+ static final String RANK_KEY = "rank";
+ static final String DIR_UPVOTE = "1";
+ static final String DIR_UNVOTE = "0";
+ static final String DIR_DOWNVOTE = "-1";
+ static final String RANK = "10";
+
+ static final String AFTER_KEY = "after";
+
+ static Map<String, String> getHttpBasicAuthHeader() {
+ Map<String, String> params = new HashMap<>();
+ String credentials = String.format("%s:%s", RedditUtils.CLIENT_ID, "");
+ String auth = "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);
+ params.put(RedditUtils.AUTHORIZATION_KEY, auth);
+ return params;
+ }
+
+ static Map<String, String> getOAuthHeader(String accessToken) {
+ Map<String, String> params = new HashMap<>();
+ params.put(RedditUtils.AUTHORIZATION_KEY, RedditUtils.AUTHORIZATION_BASE + accessToken);
+ params.put(RedditUtils.USER_AGENT_KEY, RedditUtils.USER_AGENT);
+ return params;
+ }
+
+ static String getQueryCommentURI(String subredditName, String article) {
+ return API_BASE_URI + "/" + subredditName + "/comments/" + article + ".json";
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/SharedPreferencesUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/SharedPreferencesUtils.java
new file mode 100644
index 00000000..ed44dee0
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/SharedPreferencesUtils.java
@@ -0,0 +1,20 @@
+package ml.docilealligator.infinityforreddit;
+
+/**
+ * Created by alex on 2/23/18.
+ */
+
+class SharedPreferencesUtils {
+ static final String AUTH_CODE_FILE_KEY = "Auth_Code_Pref";
+ static final String USER_INFO_FILE_KEY = "User_Info";
+ static final String AUTH_CODE_KEY = "code";
+ static final String ACCESS_TOKEN_KEY = "accessToken";
+ static final String REFRESH_TOKEN_KEY = "refreshToken";
+ static final String QUERY_ACCESS_TOKEN_TIME_KEY = "queryAccessTokenTime";
+ static final String ACCESS_TOKEN_EXPIRE_TIME_KEY = "accessTokenExpireTime";
+ static final String MODHASH_KEY = "modhash";
+ static final String USER_KEY = "user";
+ static final String PROFILE_IMAGE_URL_KEY = "profileImageUrl";
+ static final String BANNER_IMAGE_URL_KEY = "bannerImageUrl";
+ static final String KARMA_KEY = "karma";
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewImageActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewImageActivity.java
new file mode 100644
index 00000000..f2cc0330
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewImageActivity.java
@@ -0,0 +1,425 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.Manifest;
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+
+import com.alexvasilkov.gestures.views.GestureImageView;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.RequestOptions;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.target.Target;
+import com.bumptech.glide.request.transition.Transition;
+import com.github.pwittchen.swipe.library.rx2.SimpleSwipeListener;
+import com.github.pwittchen.swipe.library.rx2.Swipe;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class ViewImageActivity extends AppCompatActivity {
+
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 0;
+
+ static final String TITLE_KEY = "TK";
+ static final String IMAGE_URL_KEY = "IUK";
+ static final String SUBREDDIT_KEY = "SK";
+ static final String ID_KEY = "IK";
+
+ private boolean isActionBarHidden = false;
+ private boolean isDownloading = false;
+
+ private Menu mMenu;
+ private Swipe swipe;
+
+ private String mImageUrl;
+ private String mImageFileName;
+
+ private float totalLengthY = 0.0f;
+ private float touchY = -1.0f;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_view_image);
+ final ActionBar actionBar = getSupportActionBar();
+ final Drawable upArrow = getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp);
+ actionBar.setHomeAsUpIndicator(upArrow);
+
+ Intent intent = getIntent();
+ mImageUrl = intent.getExtras().getString(IMAGE_URL_KEY);
+ String title = intent.getExtras().getString(TITLE_KEY);
+ final Spannable text = new SpannableString(title);
+ setTitle(text);
+
+ mImageFileName = intent.getExtras().getString(SUBREDDIT_KEY).substring(2) + "-" + intent.getExtras().getString(ID_KEY).substring(3);
+
+ final RelativeLayout relativeLayout = findViewById(R.id.parent_relative_layout_view_image_activity);
+ final GestureImageView imageView = findViewById(R.id.image_view_view_image_activity);
+ final ProgressBar progressBar = findViewById(R.id.progress_bar_view_image_activity);
+
+ final float pxHeight = getResources().getDisplayMetrics().heightPixels;
+
+ int activityColorFrom = getResources().getColor(android.R.color.black);
+ int actionBarColorFrom = getResources().getColor(R.color.transparentActionBarColor);
+ int actionBarElementColorFrom = getResources().getColor(android.R.color.white);
+ int colorTo = getResources().getColor(android.R.color.transparent);
+
+ final ValueAnimator activityColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), activityColorFrom, colorTo);
+ final ValueAnimator actionBarColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), actionBarColorFrom, colorTo);
+ final ValueAnimator actionBarElementColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), actionBarElementColorFrom, colorTo);
+
+ activityColorAnimation.setDuration(300); // milliseconds
+ actionBarColorAnimation.setDuration(300);
+ actionBarElementColorAnimation.setDuration(300);
+
+ activityColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ relativeLayout.setBackgroundColor((int) valueAnimator.getAnimatedValue());
+ }
+ });
+
+ actionBarColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ actionBar.setBackgroundDrawable(new ColorDrawable((int) valueAnimator.getAnimatedValue()));
+ }
+ });
+
+ actionBarElementColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ text.setSpan(new ForegroundColorSpan((int) valueAnimator.getAnimatedValue()), 0, text.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ actionBar.setTitle(text);
+ upArrow.setColorFilter((int) valueAnimator.getAnimatedValue(), PorterDuff.Mode.SRC_IN);
+ if(mMenu != null) {
+ Drawable drawable = mMenu.getItem(0).getIcon();
+ //drawable.mutate();
+ drawable.setColorFilter((int) valueAnimator.getAnimatedValue(), PorterDuff.Mode.SRC_IN);
+ }
+ }
+ });
+
+ swipe = new Swipe();
+ swipe.setListener(new SimpleSwipeListener() {
+ @Override
+ public void onSwipingUp(final MotionEvent event) {
+ float nowY = event.getY();
+ float offset;
+ if (touchY == -1.0f) {
+ offset = 0.0f;
+ } else {
+ offset = nowY - touchY;
+ }
+ totalLengthY += offset;
+ touchY = nowY;
+ imageView.animate()
+ .y(totalLengthY)
+ .setDuration(0)
+ .start();
+ }
+
+ @Override
+ public boolean onSwipedUp(final MotionEvent event) {
+ imageView.animate()
+ .y(0)
+ .setDuration(300)
+ .start();
+
+ if (totalLengthY < -pxHeight / 8) {
+ imageView.animate()
+ .y(-pxHeight)
+ .setDuration(300)
+ .setListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ activityColorAnimation.start();
+ actionBarColorAnimation.start();
+ actionBarElementColorAnimation.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ finish();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ }
+ })
+ .start();
+ } else {
+ imageView.animate()
+ .y(0)
+ .setDuration(300)
+ .start();
+ }
+
+ totalLengthY = 0.0f;
+ touchY = -1.0f;
+ return false;
+ }
+
+ @Override
+ public void onSwipingDown(final MotionEvent event) {
+ float nowY = event.getY();
+ float offset;
+ if (touchY == -1.0f) {
+ offset = 0.0f;
+ } else {
+ offset = nowY - touchY;
+ }
+ totalLengthY += offset;
+ touchY = nowY;
+ imageView.animate()
+ .y(totalLengthY)
+ .setDuration(0)
+ .start();
+ }
+
+ @Override
+ public boolean onSwipedDown(final MotionEvent event) {
+ if (totalLengthY > pxHeight / 8) {
+ imageView.animate()
+ .y(pxHeight)
+ .setDuration(300)
+ .setListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ activityColorAnimation.start();
+ actionBarColorAnimation.start();
+ actionBarElementColorAnimation.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ finish();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+ }
+ })
+ .start();
+ } else {
+ imageView.animate()
+ .y(0)
+ .setDuration(300)
+ .start();
+ }
+
+ totalLengthY = 0.0f;
+ touchY = -1.0f;
+
+ return false;
+ }
+ });
+
+ imageView.getController().getSettings()
+ .setPanEnabled(true)
+ .setRotationEnabled(true)
+ .setRestrictRotation(true);
+
+ Glide.with(this).load(mImageUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).apply(new RequestOptions().fitCenter()).into(imageView);
+
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (isActionBarHidden) {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ isActionBarHidden = false;
+ } else {
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE);
+ isActionBarHidden = true;
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ mMenu = menu;
+ getMenuInflater().inflate(R.menu.view_image, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.action_download_view_image:
+ if (isDownloading) {
+ return false;
+ }
+
+ isDownloading = true;
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ // Permission is not granted
+ // No explanation needed; request the permission
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
+ } else {
+ // Permission has already been granted
+ saveImage();
+ }
+ } else {
+ saveImage();
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ swipe.dispatchTouchEvent(ev);
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if(requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE && grantResults.length > 0) {
+ if(grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ Toast.makeText(this, "No storage permission to save this file", Toast.LENGTH_SHORT).show();
+ } else if(grantResults[0] == PackageManager.PERMISSION_GRANTED && isDownloading) {
+ saveImage();
+ }
+ isDownloading = false;
+ }
+ }
+
+ private void saveImage() {
+ Glide.with(this)
+ .asBitmap()
+ .load(mImageUrl)
+ .into(new SimpleTarget<Bitmap>() {
+ @SuppressLint("StaticFieldLeak")
+ @Override
+ public void onResourceReady(@NonNull final Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
+ new AsyncTask<Void, Void, Void>() {
+ private boolean saveSuccess = true;
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ isDownloading = false;
+ if(saveSuccess) {
+ Toast.makeText(ViewImageActivity.this, "Download completed", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(ViewImageActivity.this, "Download failed", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
+ File directory = new File(path + "/Infinity/");
+ if(!directory.exists()) {
+ if(!directory.mkdir()) {
+ saveSuccess = false;
+ return null;
+ }
+ } else {
+ if(directory.isFile()) {
+ if(!directory.delete() && !directory.mkdir()) {
+ saveSuccess = false;
+ return null;
+ }
+ }
+ }
+
+ File file = new File(path + "/Infinity/", mImageFileName + ".jpg");
+ int postfix = 1;
+ while(file.exists()) {
+ file = new File(path + "/Infinity/", mImageFileName + "-" + postfix + ".jpg");
+ }
+ OutputStream outputStream = new FileOutputStream(file);
+
+ resource.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
+
+ outputStream.flush();
+ outputStream.close();
+ } catch (IOException e) {
+ saveSuccess = false;
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+ }.execute();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java
new file mode 100644
index 00000000..f0f3dcf5
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java
@@ -0,0 +1,342 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.customtabs.CustomTabsIntent;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.Snackbar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.CardView;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.android.volley.RequestQueue;
+import com.android.volley.toolbox.Volley;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+
+import java.util.ArrayList;
+
+import de.hdodenhof.circleimageview.CircleImageView;
+
+public class ViewPostDetailActivity extends AppCompatActivity {
+
+ static final String EXTRA_TITLE = "ET";
+ static final String EXTRA_POST_DATA = "EPD";
+
+ private int orientation;
+ private String orientationState = "OS";
+
+ private int mMoreCommentCount;
+ private BestPostData mPostData;
+
+ private CoordinatorLayout mCoordinatorLayout;
+ private ProgressBar mCommentProgressbar;
+ private CardView mCommentCardView;
+ private RecyclerView mRecyclerView;
+
+ private RequestQueue mVoteThingQueue;
+ private RequestQueue mCommentQueue;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_view_post_detail);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ orientation = getResources().getConfiguration().orientation;
+
+ mPostData = getIntent().getExtras().getParcelable(EXTRA_POST_DATA);
+
+ TextView titleTextView = findViewById(R.id.title_text_view_view_post_detail);
+ titleTextView.setText(mPostData.getTitle());
+
+ mCoordinatorLayout = findViewById(R.id.coordinator_layout_view_post_detail);
+
+ CircleImageView subredditImageView = findViewById(R.id.subreddit_icon_circle_image_view_view_post_detail);
+ TextView postTimeTextView = findViewById(R.id.post_time_text_view_view_post_detail);
+ TextView subredditTextView = findViewById(R.id.subreddit_text_view_view_post_detail);
+ TextView contentTextView = findViewById(R.id.content_text_view_view_post_detail);
+ TextView typeTextView = findViewById(R.id.type_text_view_view_post_detail);
+ TextView nsfwTextView = findViewById(R.id.nsfw_text_view_view_post_detail);
+ RelativeLayout relativeLayout = findViewById(R.id.image_view_wrapper_view_post_detail);
+ final ProgressBar progressBar = findViewById(R.id.progress_bar_view_post_detail);
+ ImageView imageView = findViewById(R.id.image_view_view_post_detail);
+ ImageView noPreviewLinkImageView = findViewById(R.id.image_view_no_preview_link_view_post_detail);
+
+ ImageView plusButton = findViewById(R.id.plus_button_view_post_detail);
+ TextView scoreTextView = findViewById(R.id.score_text_view_view_post_detail);
+ ImageView minusButton = findViewById(R.id.minus_button_view_post_detail);
+ ImageView shareButton = findViewById(R.id.share_button_view_post_detail);
+
+ mCommentProgressbar = findViewById(R.id.comment_progress_bar_view_post_detail);
+ mCommentCardView = findViewById(R.id.comment_card_view_view_post_detail);
+ mRecyclerView = findViewById(R.id.recycler_view_view_post_detail);
+
+ mRecyclerView.setNestedScrollingEnabled(false);
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+ mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
+ mVoteThingQueue = Volley.newRequestQueue(this);
+ mCommentQueue = Volley.newRequestQueue(this);
+
+ subredditTextView.setText(mPostData.getSubredditName());
+ postTimeTextView.setText(mPostData.getPostTime());
+ if(mPostData.getNSFW()) {
+ nsfwTextView.setVisibility(View.VISIBLE);
+ }
+ scoreTextView.setText(Integer.toString(mPostData.getScore()));
+
+ shareButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ String extraText = mPostData.getTitle() + "\n" + mPostData.getPermalink();
+ intent.putExtra(Intent.EXTRA_TEXT, extraText);
+ startActivity(Intent.createChooser(intent, "Share"));
+ }
+ });
+
+ switch (mPostData.getPostType()) {
+ case BestPostData.IMAGE_TYPE:
+ typeTextView.setText("IMAGE");
+ relativeLayout.setVisibility(View.VISIBLE);
+ Glide.with(this).load(mPostData.getPreviewUrl()).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ //Need to be implemented
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(imageView);
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(ViewPostDetailActivity.this, ViewImageActivity.class);
+ intent.putExtra(ViewImageActivity.IMAGE_URL_KEY, mPostData.getPreviewUrl());
+ intent.putExtra(ViewImageActivity.TITLE_KEY, mPostData.getTitle());
+ startActivity(intent);
+ }
+ });
+ break;
+ case BestPostData.LINK_TYPE:
+ relativeLayout.setVisibility(View.VISIBLE);
+ typeTextView.setText("LINK");
+ String linkPreviewUrl = mPostData.getPreviewUrl();
+ Glide.with(this).load(linkPreviewUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(imageView);
+ final String linkUrl = mPostData.getUrl();
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ // add share action to menu list
+ builder.addDefaultShareMenuItem();
+ builder.setToolbarColor(getResources().getColor(R.color.colorPrimary));
+ CustomTabsIntent customTabsIntent = builder.build();
+ customTabsIntent.launchUrl(ViewPostDetailActivity.this, Uri.parse(linkUrl));
+ }
+ });
+ break;
+ case BestPostData.GIF_VIDEO_TYPE:
+ relativeLayout.setVisibility(View.VISIBLE);
+ typeTextView.setText("VIDEO");
+ String gifVideoPreviewUrl = mPostData.getPreviewUrl();
+ Glide.with(this).load(gifVideoPreviewUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(imageView);
+
+ String gifVideoUrl = mPostData.getVideoUrl();
+ final Uri gifVideoUri = Uri.parse(gifVideoUrl);
+
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(ViewPostDetailActivity.this, ViewVideoActivity.class);
+ intent.setData(gifVideoUri);
+ intent.putExtra(ViewVideoActivity.TITLE_KEY, mPostData.getTitle());
+ intent.putExtra(ViewVideoActivity.IS_DASH_VIDEO_KEY, mPostData.isDashVideo());
+ intent.putExtra(ViewVideoActivity.IS_DOWNLOADABLE_KEY, mPostData.isDownloadableGifOrVideo());
+ if(mPostData.isDownloadableGifOrVideo()) {
+ intent.putExtra(ViewVideoActivity.DOWNLOAD_URL_KEY, mPostData.getGifOrVideoDownloadUrl());
+ intent.putExtra(ViewVideoActivity.SUBREDDIT_KEY, mPostData.getSubredditName());
+ intent.putExtra(ViewVideoActivity.ID_KEY, mPostData.getId());
+ }
+ startActivity(intent);
+ }
+ });
+ break;
+ case BestPostData.VIDEO_TYPE:
+ relativeLayout.setVisibility(View.VISIBLE);
+ typeTextView.setText("VIDEO");
+ String videoPreviewUrl = mPostData.getPreviewUrl();
+ Glide.with(this).load(videoPreviewUrl).listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ progressBar.setVisibility(View.GONE);
+ return false;
+ }
+ }).into(imageView);
+
+ String videoUrl = mPostData.getVideoUrl();
+ final Uri videoUri = Uri.parse(videoUrl);
+
+ imageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(ViewPostDetailActivity.this, ViewVideoActivity.class);
+ intent.setData(videoUri);
+ intent.putExtra(ViewVideoActivity.TITLE_KEY, mPostData.getTitle());
+ intent.putExtra(ViewVideoActivity.IS_DASH_VIDEO_KEY, mPostData.isDashVideo());
+ intent.putExtra(ViewVideoActivity.IS_DOWNLOADABLE_KEY, mPostData.isDownloadableGifOrVideo());
+ if(mPostData.isDownloadableGifOrVideo()) {
+ intent.putExtra(ViewVideoActivity.DOWNLOAD_URL_KEY, mPostData.getGifOrVideoDownloadUrl());
+ intent.putExtra(ViewVideoActivity.SUBREDDIT_KEY, mPostData.getSubredditName());
+ intent.putExtra(ViewVideoActivity.ID_KEY, mPostData.getId());
+ }
+ startActivity(intent);
+ }
+ });
+ break;
+ case BestPostData.NO_PREVIEW_LINK_TYPE:
+ typeTextView.setText("LINK");
+ noPreviewLinkImageView.setVisibility(View.VISIBLE);
+ noPreviewLinkImageView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
+ // add share action to menu list
+ builder.addDefaultShareMenuItem();
+ builder.setToolbarColor(getResources().getColor(R.color.colorPrimary));
+ CustomTabsIntent customTabsIntent = builder.build();
+ customTabsIntent.launchUrl(ViewPostDetailActivity.this, Uri.parse(mPostData.getUrl()));
+ }
+ });
+ break;
+ case BestPostData.TEXT_TYPE:
+ typeTextView.setText("TEXT");
+ if(!mPostData.getSelfText().equals("")) {
+ contentTextView.setVisibility(View.VISIBLE);
+ contentTextView.setText(mPostData.getSelfText());
+ }
+ }
+ queryComment();
+ }
+
+ private void queryComment() {
+ mCommentProgressbar.setVisibility(View.VISIBLE);
+ new FetchComment(mCommentQueue, mPostData.getSubredditName(), mPostData.getId()).queryComment(new FetchComment.FetchCommentListener() {
+ @Override
+ public void onFetchCommentSuccess(String response) {
+ new ParseComment().parseComment(ViewPostDetailActivity.this, response, new ArrayList<CommentData>(), new ParseComment.ParseCommentListener() {
+ @Override
+ public void onParseCommentSuccess(ArrayList<CommentData> commentData, int moreCommentCount) {
+ mCommentProgressbar.setVisibility(View.GONE);
+ mMoreCommentCount = moreCommentCount;
+ if(commentData.size() > 0) {
+ CommentRecyclerViewAdapter adapter = new CommentRecyclerViewAdapter(ViewPostDetailActivity.this, commentData, mVoteThingQueue);
+ mRecyclerView.setAdapter(adapter);
+ mCommentCardView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void onParseCommentFail() {
+ mCommentProgressbar.setVisibility(View.GONE);
+ showRetrySnackbar();
+ }
+ });
+ }
+
+ @Override
+ public void onFetchCommentFail() {
+ mCommentProgressbar.setVisibility(View.GONE);
+ showRetrySnackbar();
+ }
+ });
+ }
+
+ private void showRetrySnackbar() {
+ Snackbar snackbar = Snackbar.make(mCoordinatorLayout, R.string.load_comment_failed, Snackbar.LENGTH_INDEFINITE);
+ snackbar.setAction(R.string.retry, new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ queryComment();
+ }
+ });
+ snackbar.show();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ onBackPressed();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(orientationState, orientation);
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ orientation = savedInstanceState.getInt(orientationState);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if(orientation == getResources().getConfiguration().orientation) {
+ super.onBackPressed();
+ } else {
+ finish();
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/ViewVideoActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewVideoActivity.java
new file mode 100644
index 00000000..407f8eff
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/ViewVideoActivity.java
@@ -0,0 +1,406 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.Manifest;
+import android.animation.Animator;
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.DownloadManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.Toast;
+
+import com.github.pwittchen.swipe.library.rx2.SimpleSwipeListener;
+import com.github.pwittchen.swipe.library.rx2.Swipe;
+import com.google.android.exoplayer2.ExoPlayerFactory;
+import com.google.android.exoplayer2.Player;
+import com.google.android.exoplayer2.SimpleExoPlayer;
+import com.google.android.exoplayer2.source.ExtractorMediaSource;
+import com.google.android.exoplayer2.source.dash.DashChunkSource;
+import com.google.android.exoplayer2.source.dash.DashMediaSource;
+import com.google.android.exoplayer2.source.dash.DefaultDashChunkSource;
+import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
+import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
+import com.google.android.exoplayer2.trackselection.TrackSelection;
+import com.google.android.exoplayer2.trackselection.TrackSelector;
+import com.google.android.exoplayer2.ui.PlayerControlView;
+import com.google.android.exoplayer2.ui.PlayerView;
+import com.google.android.exoplayer2.upstream.DataSource;
+import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
+import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
+import com.google.android.exoplayer2.util.Util;
+
+public class ViewVideoActivity extends AppCompatActivity {
+
+ private static final int PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 0;
+
+ static final String TITLE_KEY = "TK";
+ static final String IS_DASH_VIDEO_KEY = "IDVK";
+ static final String IS_DOWNLOADABLE_KEY = "IDK";
+ static final String DOWNLOAD_URL_KEY = "DUK";
+ static final String SUBREDDIT_KEY = "SK";
+ static final String ID_KEY = "IK";
+
+ private Uri mVideoUri;
+ private SimpleExoPlayer player;
+
+ private Menu mMenu;
+ private Swipe swipe;
+
+ private String mGifOrVideoFileName;
+ private String mDownloadUrl;
+ private boolean mIsDashVideo;
+ private boolean wasPlaying;
+ private boolean isDownloading = false;
+ private float totalLengthY = 0.0f;
+ private float touchY = -1.0f;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_view_video);
+
+ final ActionBar actionBar = getSupportActionBar();
+ final Drawable upArrow = getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp);
+ getSupportActionBar().setHomeAsUpIndicator(upArrow);
+
+ //Set player controller margin bottom in order to display it above the navbar
+ int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
+ LinearLayout controllerLinearLayout = findViewById(R.id.linear_layout_exo_playback_control_view);
+ ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) controllerLinearLayout.getLayoutParams();
+ params.bottomMargin = getResources().getDimensionPixelSize(resourceId);
+
+ Intent intent = getIntent();
+ mVideoUri = intent.getData();
+ mIsDashVideo = intent.getExtras().getBoolean(IS_DASH_VIDEO_KEY);
+
+ String title = intent.getExtras().getString(TITLE_KEY);
+ final Spannable text = new SpannableString(title);
+ setTitle(text);
+
+ if(intent.getExtras().getBoolean(IS_DOWNLOADABLE_KEY)) {
+ mGifOrVideoFileName = intent.getExtras().getString(SUBREDDIT_KEY).substring(2)
+ + "-" + intent.getExtras().getString(ID_KEY).substring(3) + ".gif";
+ mDownloadUrl = intent.getExtras().getString(DOWNLOAD_URL_KEY);
+ }
+
+ final RelativeLayout relativeLayout = findViewById(R.id.relative_layout_view_video_activity);
+ final PlayerView videoPlayerView = findViewById(R.id.player_view_view_video_activity);
+
+ final float pxHeight = getResources().getDisplayMetrics().heightPixels;
+
+ int activityColorFrom = getResources().getColor(android.R.color.black);
+ int actionBarColorFrom = getResources().getColor(R.color.transparentActionBarColor);
+ int actionBarElementColorFrom = getResources().getColor(android.R.color.white);
+ int colorTo = getResources().getColor(android.R.color.transparent);
+
+ final ValueAnimator activityColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), activityColorFrom, colorTo);
+ final ValueAnimator actionBarColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), actionBarColorFrom, colorTo);
+ final ValueAnimator actionBarElementColorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), actionBarElementColorFrom, colorTo);
+
+ activityColorAnimation.setDuration(300); // milliseconds
+ actionBarColorAnimation.setDuration(300);
+ actionBarElementColorAnimation.setDuration(300);
+
+ activityColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ relativeLayout.setBackgroundColor((int) valueAnimator.getAnimatedValue());
+ }
+ });
+
+ actionBarColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ actionBar.setBackgroundDrawable(new ColorDrawable((int) valueAnimator.getAnimatedValue()));
+ }
+ });
+
+ actionBarElementColorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ text.setSpan(new ForegroundColorSpan((int) valueAnimator.getAnimatedValue()), 0, text.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+ actionBar.setTitle(text);
+ upArrow.setColorFilter((int) valueAnimator.getAnimatedValue(), PorterDuff.Mode.SRC_IN);
+ if(mMenu != null) {
+ Drawable drawable = mMenu.getItem(0).getIcon();
+ drawable.setColorFilter((int) valueAnimator.getAnimatedValue(), PorterDuff.Mode.SRC_IN);
+ }
+ }
+ });
+
+ swipe = new Swipe();
+ swipe.setListener(new SimpleSwipeListener() {
+ @Override
+ public void onSwipingUp(final MotionEvent event) {
+ float nowY = event.getY();
+ float offset;
+ if(touchY == -1.0f) {
+ offset = 0.0f;
+ } else {
+ offset = nowY - touchY;
+ }
+ totalLengthY += offset;
+ touchY = nowY;
+ videoPlayerView.animate()
+ .y(totalLengthY)
+ .setDuration(0)
+ .start();
+ }
+
+ @Override
+ public boolean onSwipedUp(final MotionEvent event) {
+ videoPlayerView.animate()
+ .y(0)
+ .setDuration(300)
+ .start();
+
+ if(totalLengthY < -pxHeight / 8) {
+ videoPlayerView.animate()
+ .y(-pxHeight)
+ .setDuration(300)
+ .setListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ activityColorAnimation.start();
+ actionBarColorAnimation.start();
+ actionBarElementColorAnimation.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ finish();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+ })
+ .start();
+ } else {
+ videoPlayerView.animate()
+ .y(0)
+ .setDuration(300)
+ .start();
+ }
+
+ totalLengthY = 0.0f;
+ touchY = -1.0f;
+ return false;
+ }
+
+ @Override
+ public void onSwipingDown(final MotionEvent event) {
+ float nowY = event.getY();
+ float offset;
+ if(touchY == -1.0f) {
+ offset = 0.0f;
+ } else {
+ offset = nowY - touchY;
+ }
+ totalLengthY += offset;
+ touchY = nowY;
+ videoPlayerView.animate()
+ .y(totalLengthY)
+ .setDuration(0)
+ .start();
+ }
+
+ @Override
+ public boolean onSwipedDown(final MotionEvent event) {
+ if(totalLengthY > pxHeight / 8) {
+ videoPlayerView.animate()
+ .y(pxHeight)
+ .setDuration(300)
+ .setListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ activityColorAnimation.start();
+ actionBarColorAnimation.start();
+ actionBarElementColorAnimation.start();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ finish();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {}
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+ })
+ .start();
+ } else {
+ videoPlayerView.animate()
+ .y(0)
+ .setDuration(300)
+ .start();
+ }
+
+ totalLengthY = 0.0f;
+ touchY = -1.0f;
+
+ return false;
+ }
+ });
+
+ videoPlayerView.setControllerVisibilityListener(new PlayerControlView.VisibilityListener() {
+ @Override
+ public void onVisibilityChange(int visibility) {
+ switch (visibility) {
+ case View.GONE:
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE);
+ break;
+ case View.VISIBLE:
+ getWindow().getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+ }
+ }
+ });
+
+ DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
+ TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter);
+ TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
+ player = ExoPlayerFactory.newSimpleInstance(this, trackSelector);
+ videoPlayerView.setPlayer(player);
+ // Produces DataSource instances through which media data is loaded.
+ DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this,
+ Util.getUserAgent(this, "Infinity"), bandwidthMeter);
+
+ // Prepare the player with the source.
+ if(mIsDashVideo) {
+ DashChunkSource.Factory dashChunkSourceFactory = new DefaultDashChunkSource.Factory(dataSourceFactory);
+ player.prepare(new DashMediaSource(mVideoUri, dataSourceFactory, dashChunkSourceFactory, null, null));
+ } else {
+ player.prepare(new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(mVideoUri));
+ }
+ player.setRepeatMode(Player.REPEAT_MODE_ALL);
+ player.setPlayWhenReady(true);
+ wasPlaying = true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ if(!mIsDashVideo) {
+ getMenuInflater().inflate(R.menu.view_video, menu);
+ mMenu = menu;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ swipe.dispatchTouchEvent(ev);
+ return super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ player.release();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ case R.id.action_download_view_video:
+ isDownloading = true;
+ if (Build.VERSION.SDK_INT >= 23) {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+
+ // Permission is not granted
+ // No explanation needed; request the permission
+ ActivityCompat.requestPermissions(this,
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
+ } else {
+ // Permission has already been granted
+ download();
+ }
+ } else {
+ download();
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if(wasPlaying) {
+ player.setPlayWhenReady(true);
+ }
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ wasPlaying = player.getPlayWhenReady();
+ player.setPlayWhenReady(false);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if(requestCode == PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE && grantResults.length > 0) {
+ if(grantResults[0] == PackageManager.PERMISSION_DENIED) {
+ Toast.makeText(this, "No storage permission to save this file", Toast.LENGTH_SHORT).show();
+ } else if(grantResults[0] == PackageManager.PERMISSION_GRANTED && isDownloading) {
+ download();
+ }
+ isDownloading = false;
+ }
+ }
+
+ private void download() {
+ DownloadManager.Request request = new DownloadManager.Request(Uri.parse(mDownloadUrl));
+ request.setTitle(mGifOrVideoFileName);
+
+ request.allowScanningByMediaScanner();
+ request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+ request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES + "/Infinity/", mGifOrVideoFileName);
+
+ DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
+ manager.enqueue(request);
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/VoteThing.java b/app/src/main/java/ml/docilealligator/infinityforreddit/VoteThing.java
new file mode 100644
index 00000000..c6807134
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/VoteThing.java
@@ -0,0 +1,88 @@
+package ml.docilealligator.infinityforreddit;
+
+import android.content.Context;
+
+import com.android.volley.AuthFailureError;
+import com.android.volley.Request;
+import com.android.volley.RequestQueue;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by alex on 3/14/18.
+ */
+
+class VoteThing {
+
+ interface VoteThingListener {
+ void onVoteThingSuccess(int position);
+ void onVoteThingFail(int position);
+ }
+
+ private Context mContext;
+ private VoteThingListener mVoteThingListener;
+ private RequestQueue mQueue;
+ private RequestQueue mAcquireAccessTokenRequestQueue;
+
+ VoteThing(Context context, RequestQueue queue, RequestQueue acquireAccessTokenRequestQueue) {
+ mContext = context;
+ mQueue = queue;
+ mAcquireAccessTokenRequestQueue = acquireAccessTokenRequestQueue;
+ }
+
+ void votePost(VoteThingListener voteThingListener, final String fullName, final String point, final int position, final int refreshTime) {
+ if(mContext != null) {
+ if(refreshTime < 0) {
+ mVoteThingListener.onVoteThingFail(position);
+ return;
+ }
+ mVoteThingListener = voteThingListener;
+ StringRequest voteRequest = new StringRequest(Request.Method.POST, RedditUtils.OAUTH_API_BASE_URI + RedditUtils.VOTE_SUFFIX, new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ mVoteThingListener.onVoteThingSuccess(position);
+ }
+ }, new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError error) {
+ if (error instanceof AuthFailureError) {
+ //Access token expired
+ new AcquireAccessToken(mContext).refreshAccessToken(mAcquireAccessTokenRequestQueue,
+ new AcquireAccessToken.AcquireAccessTokenListener() {
+ @Override
+ public void onAcquireAccessTokenSuccess() {
+ votePost(mVoteThingListener, fullName, point, position, refreshTime - 1);
+ }
+
+ @Override
+ public void onAcquireAccessTokenFail() {}
+ });
+ } else {
+ mVoteThingListener.onVoteThingFail(position);
+ }
+ }
+ }) {
+ @Override
+ protected Map<String, String> getParams() {
+ HashMap<String, String> params = new HashMap<>();
+ params.put(RedditUtils.DIR_KEY, point);
+ params.put(RedditUtils.ID_KEY, fullName);
+ params.put(RedditUtils.RANK_KEY, RedditUtils.RANK);
+ return params;
+ }
+
+ @Override
+ public Map<String, String> getHeaders() {
+ String accessToken = mContext.getSharedPreferences(SharedPreferencesUtils.AUTH_CODE_FILE_KEY, Context.MODE_PRIVATE).getString(SharedPreferencesUtils.ACCESS_TOKEN_KEY, "");
+ return RedditUtils.getOAuthHeader(accessToken);
+ }
+ };
+ voteRequest.setTag(VoteThing.class);
+ mQueue.add(voteRequest);
+ }
+ }
+}