diff options
author | Alex Ning <chineseperson5@gmail.com> | 2018-07-26 15:04:44 +0000 |
---|---|---|
committer | Alex Ning <chineseperson5@gmail.com> | 2018-07-26 15:04:44 +0000 |
commit | 1f2137dd365bc07b5fd38df22e54379933585994 (patch) | |
tree | f9ef585dda71db8bc809120761cac42e22dd1193 /app/src/main/java/ml/docilealligator/infinityforreddit | |
download | infinity-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/ml/docilealligator/infinityforreddit')
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); + } + } +} |