diff options
Diffstat (limited to 'app')
132 files changed, 5442 insertions, 0 deletions
diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..0d69fae8 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,48 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "ml.docilealligator.infinityforreddit" + minSdkVersion 21 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +repositories { + maven { + url "http://dl.bintray.com/rilixtech/maven" + } + mavenCentral() + google() +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' + implementation 'com.android.support:design:28.0.0-alpha3' + implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation 'com.android.support:support-v4:28.0.0-alpha3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.android.volley:volley:1.1.0' + implementation 'de.hdodenhof:circleimageview:2.2.0' + implementation 'com.google.android.exoplayer:exoplayer:2.7.0' + implementation 'com.google.android.exoplayer:exoplayer-dash:2.7.0' + implementation 'com.android.support:customtabs:28.0.0-alpha3' + implementation 'com.alexvasilkov:gesture-views:2.5.2' + implementation 'com.android.support:cardview-v7:28.0.0-alpha3' + implementation 'com.github.bumptech.glide:glide:4.6.1' + implementation 'com.github.pwittchen:swipe-rx2:0.3.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.6.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/ml/docilealligator/infinityforreddit/ExampleInstrumentedTest.java b/app/src/androidTest/java/ml/docilealligator/infinityforreddit/ExampleInstrumentedTest.java new file mode 100644 index 00000000..fd2873ec --- /dev/null +++ b/app/src/androidTest/java/ml/docilealligator/infinityforreddit/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package ml.docilealligator.infinityforreddit; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("ml.docilealligator.infinityforreddit", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..5fa6f8e0 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="ml.docilealligator.infinityforreddit"> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission-sdk-23 android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" + android:maxSdkVersion="22" /> + + <application + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme"> + <activity + android:name=".MainActivity" + android:label="@string/app_name" + android:theme="@style/AppTheme.NoActionBar"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name=".LoginActivity" + android:label="@string/login_activity_label" + android:parentActivityName=".MainActivity" /> + <activity + android:name=".ViewImageActivity" + android:parentActivityName=".MainActivity" + android:theme="@style/Theme.AppCompat.Transparent" /> + <activity + android:name=".ViewVideoActivity" + android:parentActivityName=".MainActivity" + android:theme="@style/Theme.AppCompat.Transparent" /> + <activity + android:name=".ViewPostDetailActivity" + android:parentActivityName=".MainActivity" /> + </application> + +</manifest>
\ No newline at end of file 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); + } + } +} diff --git a/app/src/main/res/drawable-hdpi/baseline_add_white_24.png b/app/src/main/res/drawable-hdpi/baseline_add_white_24.png Binary files differnew file mode 100755 index 00000000..ea78104d --- /dev/null +++ b/app/src/main/res/drawable-hdpi/baseline_add_white_24.png diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_downward_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_arrow_downward_black_24dp.png Binary files differnew file mode 100644 index 00000000..b848197a --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_arrow_downward_black_24dp.png diff --git a/app/src/main/res/drawable-hdpi/ic_arrow_upward_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_arrow_upward_black_24dp.png Binary files differnew file mode 100644 index 00000000..705f7ae8 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_arrow_upward_black_24dp.png diff --git a/app/src/main/res/drawable-hdpi/ic_link.png b/app/src/main/res/drawable-hdpi/ic_link.png Binary files differnew file mode 100644 index 00000000..151e101d --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_link.png diff --git a/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 00000000..20ba4806 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png diff --git a/app/src/main/res/drawable-mdpi/baseline_add_white_24.png b/app/src/main/res/drawable-mdpi/baseline_add_white_24.png Binary files differnew file mode 100755 index 00000000..eeba9f66 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/baseline_add_white_24.png diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_downward_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_arrow_downward_black_24dp.png Binary files differnew file mode 100644 index 00000000..3cc4b130 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_arrow_downward_black_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_arrow_upward_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_arrow_upward_black_24dp.png Binary files differnew file mode 100644 index 00000000..368eb65f --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_arrow_upward_black_24dp.png diff --git a/app/src/main/res/drawable-mdpi/ic_link.png b/app/src/main/res/drawable-mdpi/ic_link.png Binary files differnew file mode 100644 index 00000000..d407acb5 --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_link.png diff --git a/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 00000000..f02d360a --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png diff --git a/app/src/main/res/drawable-v21/ic_menu_camera.xml b/app/src/main/res/drawable-v21/ic_menu_camera.xml new file mode 100644 index 00000000..0d9ea104 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_camera.xml @@ -0,0 +1,12 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0" /> + <path + android:fillColor="#FF000000" + android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zm3,15c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z" /> +</vector> diff --git a/app/src/main/res/drawable-v21/ic_menu_gallery.xml b/app/src/main/res/drawable-v21/ic_menu_gallery.xml new file mode 100644 index 00000000..f6872c40 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_gallery.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M22,16V4c0,-1.1 -0.9,-2 -2,-2H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2zm-11,-4l2.03,2.71L16,11l4,5H8l3,-4zM2,6v14c0,1.1 0.9,2 2,2h14v-2H4V6H2z" /> +</vector> diff --git a/app/src/main/res/drawable-v21/ic_menu_manage.xml b/app/src/main/res/drawable-v21/ic_menu_manage.xml new file mode 100644 index 00000000..c1be60b3 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_manage.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" /> +</vector>
\ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_menu_send.xml b/app/src/main/res/drawable-v21/ic_menu_send.xml new file mode 100644 index 00000000..00c668c6 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_send.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z" /> +</vector> diff --git a/app/src/main/res/drawable-v21/ic_menu_share.xml b/app/src/main/res/drawable-v21/ic_menu_share.xml new file mode 100644 index 00000000..a28fb9e2 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_share.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" /> +</vector> diff --git a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml new file mode 100644 index 00000000..209aa643 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M4,6H2v14c0,1.1 0.9,2 2,2h14v-2H4V6zm16,-4H8c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2zm-8,12.5v-9l6,4.5 -6,4.5z" /> +</vector> diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..c7bd21db --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillType="evenOdd" + android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z" + android:strokeColor="#00000000" + android:strokeWidth="1"> + <aapt:attr name="android:fillColor"> + <gradient + android:endX="78.5885" + android:endY="90.9159" + android:startX="48.7653" + android:startY="61.0927" + android:type="linear"> + <item + android:color="#44000000" + android:offset="0.0" /> + <item + android:color="#00000000" + android:offset="1.0" /> + </gradient> + </aapt:attr> + </path> + <path + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z" + android:strokeColor="#00000000" + android:strokeWidth="1" /> +</vector> diff --git a/app/src/main/res/drawable-xhdpi/baseline_add_white_24.png b/app/src/main/res/drawable-xhdpi/baseline_add_white_24.png Binary files differnew file mode 100755 index 00000000..67bb598e --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/baseline_add_white_24.png diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_downward_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_arrow_downward_black_24dp.png Binary files differnew file mode 100644 index 00000000..3c147bc5 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_arrow_downward_black_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_arrow_upward_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_arrow_upward_black_24dp.png Binary files differnew file mode 100644 index 00000000..f2b35ac2 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_arrow_upward_black_24dp.png diff --git a/app/src/main/res/drawable-xhdpi/ic_link.png b/app/src/main/res/drawable-xhdpi/ic_link.png Binary files differnew file mode 100644 index 00000000..34062296 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_link.png diff --git a/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 00000000..81c80b70 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/baseline_add_white_24.png b/app/src/main/res/drawable-xxhdpi/baseline_add_white_24.png Binary files differnew file mode 100755 index 00000000..0fdced8f --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/baseline_add_white_24.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_downward_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_downward_black_24dp.png Binary files differnew file mode 100644 index 00000000..ace750c4 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_arrow_downward_black_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_upward_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_upward_black_24dp.png Binary files differnew file mode 100644 index 00000000..99622e7c --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_arrow_upward_black_24dp.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_link.png b/app/src/main/res/drawable-xxhdpi/ic_link.png Binary files differnew file mode 100644 index 00000000..9b2ad879 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_link.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 00000000..784933ad --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/baseline_add_white_24.png b/app/src/main/res/drawable-xxxhdpi/baseline_add_white_24.png Binary files differnew file mode 100755 index 00000000..d64c22e9 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/baseline_add_white_24.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_downward_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_downward_black_24dp.png Binary files differnew file mode 100644 index 00000000..2690b202 --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_arrow_downward_black_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_arrow_upward_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_arrow_upward_black_24dp.png Binary files differnew file mode 100644 index 00000000..fca5022c --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_arrow_upward_black_24dp.png diff --git a/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 00000000..5a8544ce --- /dev/null +++ b/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png diff --git a/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml b/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml new file mode 100644 index 00000000..71d5bbd2 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back_white_24dp.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#FFFFFF" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_arrow_downward_black_12dp.xml b/app/src/main/res/drawable/ic_arrow_downward_black_12dp.xml new file mode 100644 index 00000000..91cbabce --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_downward_black_12dp.xml @@ -0,0 +1,4 @@ +<vector android:height="12dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#010101" android:pathData="M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_arrow_upward_black_12dp.xml b/app/src/main/res/drawable/ic_arrow_upward_black_12dp.xml new file mode 100644 index 00000000..355c40f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_upward_black_12dp.xml @@ -0,0 +1,4 @@ +<vector android:height="12dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_comment_white_24dp.xml b/app/src/main/res/drawable/ic_comment_white_24dp.xml new file mode 100644 index 00000000..efb7e76a --- /dev/null +++ b/app/src/main/res/drawable/ic_comment_white_24dp.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#FFFFFF" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_file_download_white_24dp.xml b/app/src/main/res/drawable/ic_file_download_white_24dp.xml new file mode 100644 index 00000000..b8e83614 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_download_white_24dp.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#FFFFFF" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..d5fccc53 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="108dp" + android:height="108dp" + android:viewportHeight="108" + android:viewportWidth="108"> + <path + android:fillColor="#26A69A" + android:pathData="M0,0h108v108h-108z" /> + <path + android:fillColor="#00000000" + android:pathData="M9,0L9,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,0L19,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,0L29,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,0L39,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,0L49,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,0L59,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,0L69,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,0L79,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M89,0L89,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M99,0L99,108" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,9L108,9" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,19L108,19" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,29L108,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,39L108,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,49L108,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,59L108,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,69L108,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,79L108,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,89L108,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M0,99L108,99" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,29L89,29" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,39L89,39" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,49L89,49" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,59L89,59" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,69L89,69" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M19,79L89,79" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M29,19L29,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M39,19L39,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M49,19L49,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M59,19L59,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M69,19L69,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> + <path + android:fillColor="#00000000" + android:pathData="M79,19L79,89" + android:strokeColor="#33FFFFFF" + android:strokeWidth="0.8" /> +</vector> diff --git a/app/src/main/res/drawable/ic_reply_black_12dp.xml b/app/src/main/res/drawable/ic_reply_black_12dp.xml new file mode 100644 index 00000000..e35610aa --- /dev/null +++ b/app/src/main/res/drawable/ic_reply_black_12dp.xml @@ -0,0 +1,4 @@ +<vector android:height="12dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="12dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M10,9V5l-7,7 7,7v-4.1c5,0 8.5,1.6 11,5.1 -1,-5 -4,-10 -11,-11z"/> +</vector> diff --git a/app/src/main/res/drawable/ic_send_black_24dp.xml b/app/src/main/res/drawable/ic_send_black_24dp.xml new file mode 100644 index 00000000..e145ca83 --- /dev/null +++ b/app/src/main/res/drawable/ic_send_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/> +</vector> diff --git a/app/src/main/res/drawable/nsfw_rounded_corner.xml b/app/src/main/res/drawable/nsfw_rounded_corner.xml new file mode 100644 index 00000000..0d3eacc5 --- /dev/null +++ b/app/src/main/res/drawable/nsfw_rounded_corner.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <!--<stroke + android:width="1dp" + android:color="@color/colorPrimary" />--> + + <solid android:color="@color/colorAccent" /> + + <padding + android:left="4dp" + android:right="4dp" + android:top="4dp" + android:bottom="4dp"/> + + <corners android:radius="4dp" /> +</shape>
\ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_corner.xml b/app/src/main/res/drawable/rounded_corner.xml new file mode 100644 index 00000000..421af4ca --- /dev/null +++ b/app/src/main/res/drawable/rounded_corner.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" > + <!--<stroke + android:width="1dp" + android:color="@color/colorPrimary" />--> + + <solid android:color="@color/colorPrimary" /> + + <padding + android:left="4dp" + android:right="4dp" + android:top="4dp" + android:bottom="4dp"/> + + <corners android:radius="4dp" /> +</shape>
\ No newline at end of file diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 00000000..92abf9f0 --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="135" + android:centerColor="#1565C0" + android:endColor="#0D47A1" + android:startColor="#1976D2" + android:type="linear" /> +</shape>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 00000000..ebe641c7 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="ml.docilealligator.infinityforreddit.LoginActivity"> + + <WebView + android:id="@+id/webview_login_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + +</android.support.constraint.ConstraintLayout> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..db65bf75 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/drawer_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true" + tools:openDrawer="start"> + + <include + layout="@layout/app_bar_main" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <android.support.design.widget.NavigationView + android:id="@+id/nav_view" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="start" + android:fitsSystemWindows="true" + app:headerLayout="@layout/nav_header_main" + app:menu="@menu/activity_main_drawer" /> + + +</android.support.v4.widget.DrawerLayout> diff --git a/app/src/main/res/layout/activity_view_image.xml b/app/src/main/res/layout/activity_view_image.xml new file mode 100644 index 00000000..066557bd --- /dev/null +++ b/app/src/main/res/layout/activity_view_image.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/parent_relative_layout_view_image_activity" + android:background="@android:color/black" + tools:context="ml.docilealligator.infinityforreddit.ViewImageActivity"> + + <ProgressBar + android:id="@+id/progress_bar_view_image_activity" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" /> + + <com.alexvasilkov.gestures.views.GestureImageView + android:id="@+id/image_view_view_image_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:gest_fillViewport="true" /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/activity_view_post_detail.xml b/app/src/main/res/layout/activity_view_post_detail.xml new file mode 100644 index 00000000..694253e6 --- /dev/null +++ b/app/src/main/res/layout/activity_view_post_detail.xml @@ -0,0 +1,257 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/coordinator_layout_view_post_detail" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".ViewPostDetailActivity"> + + <android.support.v4.widget.NestedScrollView + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:descendantFocusability="blocksDescendants"> + + <android.support.v7.widget.CardView + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <RelativeLayout + android:id="@+id/relative_view_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/subreddit_icon_circle_image_view_view_post_detail" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" /> + + <TextView + android:id="@+id/subreddit_text_view_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginStart="16dp" + android:layout_toEndOf="@id/subreddit_icon_circle_image_view_view_post_detail" + android:layout_toStartOf="@id/post_time_text_view_view_post_detail" + android:textColor="#E91E63" /> + + <TextView + android:id="@+id/post_time_text_view_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" /> + + </RelativeLayout> + + <TextView + android:id="@+id/title_text_view_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginStart="16dp" + android:textColor="#000000" + android:textSize="18sp" /> + + <TextView + android:id="@+id/content_text_view_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" + android:textSize="16sp" + android:visibility="gone" /> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/type_text_view_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:background="@drawable/rounded_corner" + android:textColor="@android:color/white" /> + + <TextView + android:id="@+id/nsfw_text_view_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:background="@drawable/nsfw_rounded_corner" + android:textColor="@android:color/white" + android:visibility="gone" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/image_view_wrapper_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:visibility="gone"> + + <ProgressBar + android:id="@+id/progress_bar_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" /> + + <ImageView + android:id="@+id/image_view_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:scaleType="fitStart"/> + + </RelativeLayout> + + <ImageView + android:id="@+id/image_view_no_preview_link_view_post_detail" + android:layout_width="match_parent" + android:layout_height="150dp" + android:layout_marginTop="16dp" + android:background="#E0E0E0" + android:scaleType="center" + android:src="@drawable/ic_link" + android:visibility="gone" /> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/plus_button_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true" + android:padding="16dp" + android:src="@drawable/ic_arrow_upward_black_24dp" + android:tint="@android:color/tab_indicator_text" /> + + <TextView + android:id="@+id/score_text_view_view_post_detail" + android:layout_width="64dp" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/plus_button_view_post_detail" + android:gravity="center" /> + + <ImageView + android:id="@+id/minus_button_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/score_text_view_view_post_detail" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true" + android:padding="16dp" + android:src="@drawable/ic_arrow_downward_black_24dp" + android:tint="@android:color/tab_indicator_text" /> + + <ImageView + android:id="@+id/share_button_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true" + android:padding="16dp" + android:src="@drawable/ic_share_black_24dp" + android:tint="@android:color/tab_indicator_text" /> + + </RelativeLayout> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + <ProgressBar + android:id="@+id/comment_progress_bar_view_post_detail" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_margin="16dp" + android:visibility="gone"/> + + <android.support.v7.widget.CardView + android:id="@+id/comment_card_view_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:visibility="gone"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingEnd="16dp" + android:paddingLeft="16dp" + android:paddingRight="16dp" + android:paddingStart="16dp" + android:paddingTop="16dp" + android:text="@string/comments" + android:textColor="#000000" + android:textSize="18sp" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/recycler_view_view_post_detail" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + </LinearLayout> + + + </android.support.v4.widget.NestedScrollView> + +</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_view_video.xml b/app/src/main/res/layout/activity_view_video.xml new file mode 100644 index 00000000..0ff1b5f3 --- /dev/null +++ b/app/src/main/res/layout/activity_view_video.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@android:color/black" + android:id="@+id/relative_layout_view_video_activity" + android:keepScreenOn="true" + tools:context="ml.docilealligator.infinityforreddit.ViewVideoActivity"> + + <com.google.android.exoplayer2.ui.PlayerView + android:id="@+id/player_view_view_video_activity" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:controller_layout_id="@layout/exo_playback_control_view"/> + +</RelativeLayout> diff --git a/app/src/main/res/layout/app_bar_main.xml b/app/src/main/res/layout/app_bar_main.xml new file mode 100644 index 00000000..b4df7936 --- /dev/null +++ b/app/src/main/res/layout/app_bar_main.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="ml.docilealligator.infinityforreddit.MainActivity"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="@style/AppTheme.AppBarOverlay"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="?attr/colorPrimary" + app:popupTheme="@style/AppTheme.PopupOverlay" /> + + </android.support.design.widget.AppBarLayout> + + <include layout="@layout/content_main" /> + +</android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..9820167d --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/frame_layout_content_main" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:context="ml.docilealligator.infinityforreddit.MainActivity" + tools:showIn="@layout/app_bar_main" /> diff --git a/app/src/main/res/layout/exo_playback_control_view.xml b/app/src/main/res/layout/exo_playback_control_view.xml new file mode 100644 index 00000000..752826fb --- /dev/null +++ b/app/src/main/res/layout/exo_playback_control_view.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/linear_layout_exo_playback_control_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:background="@color/transparentActionBarColor" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingTop="4dp" + android:orientation="horizontal"> + + <ImageButton android:id="@id/exo_rew" + style="@style/ExoMediaButton.Rewind"/> + + <ImageButton android:id="@id/exo_play" + style="@style/ExoMediaButton.Play"/> + + <ImageButton android:id="@id/exo_pause" + style="@style/ExoMediaButton.Pause"/> + + <ImageButton android:id="@id/exo_ffwd" + style="@style/ExoMediaButton.FastForward"/> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <TextView android:id="@id/exo_position" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textStyle="bold" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:includeFontPadding="false" + android:textColor="#FFBEBEBE"/> + + <com.google.android.exoplayer2.ui.DefaultTimeBar + android:id="@id/exo_progress" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="26dp"/> + + <TextView android:id="@id/exo_duration" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textStyle="bold" + android:paddingLeft="4dp" + android:paddingRight="4dp" + android:includeFontPadding="false" + android:textColor="#FFBEBEBE"/> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_best_post.xml b/app/src/main/res/layout/fragment_best_post.xml new file mode 100644 index 00000000..f4d24c08 --- /dev/null +++ b/app/src/main/res/layout/fragment_best_post.xml @@ -0,0 +1,28 @@ +<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/coordinator_layout_best_post_fragment" + tools:context="ml.docilealligator.infinityforreddit.BestPostFragment"> + + <ProgressBar + android:id="@+id/progress_bar_best_post_fragment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" /> + + <android.support.v7.widget.RecyclerView + android:id="@+id/recycler_view_best_post_fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + + <android.support.design.widget.FloatingActionButton + android:id="@+id/fab_best_post_fragment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom|end" + android:layout_margin="@dimen/fab_margin" + app:srcCompat="@drawable/baseline_add_white_24" /> + +</android.support.design.widget.CoordinatorLayout> diff --git a/app/src/main/res/layout/item_best_post.xml b/app/src/main/res/layout/item_best_post.xml new file mode 100644 index 00000000..4bd95542 --- /dev/null +++ b/app/src/main/res/layout/item_best_post.xml @@ -0,0 +1,179 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:id="@+id/card_view_view_post_detail"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <RelativeLayout + android:id="@+id/relative_view_item_best_post" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="8dp" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:layout_marginTop="16dp"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/subreddit_icon_circle_image_view_best_post_item" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentStart="true" + android:layout_centerVertical="true"/> + + <TextView + android:id="@+id/subreddit_text_view_best_post_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_toEndOf="@id/subreddit_icon_circle_image_view_best_post_item" + android:layout_centerVertical="true" + android:textColor="#E91E63"/> + + <TextView + android:id="@+id/post_time_text_view_best_post_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true"/> + + </RelativeLayout> + + <TextView + android:id="@+id/title_text_view_best_post_item" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:textSize="18sp" + android:textColor="#000000"/> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/type_text_view_item_best_post" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/rounded_corner" + android:layout_marginTop="8dp" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:textColor="@android:color/white"/> + + <TextView + android:id="@+id/nsfw_text_view_item_best_post" + android:text="@string/nsfw" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/nsfw_rounded_corner" + android:layout_alignParentEnd="true" + android:layout_marginTop="8dp" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:textColor="@android:color/white" + android:visibility="gone"/> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/image_view_wrapper_item_best_post" + android:layout_width="match_parent" + android:layout_height="350dp" + android:layout_marginTop="16dp" + android:visibility="gone"> + + <ProgressBar + android:id="@+id/progress_bar_best_post_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" /> + + <ImageView + android:id="@+id/image_view_best_post_item" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerCrop" /> + + </RelativeLayout> + + <ImageView + android:id="@+id/image_view_no_preview_link_best_post_item" + android:layout_width="match_parent" + android:layout_height="150dp" + android:layout_marginTop="16dp" + android:scaleType="center" + android:src="@drawable/ic_link" + android:background="#E0E0E0" + android:visibility="gone"/> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <ImageView + android:id="@+id/plus_button_item_best_post" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:padding="16dp" + android:src="@drawable/ic_arrow_upward_black_24dp" + android:layout_centerVertical="true" + android:tint="@android:color/tab_indicator_text" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true"/> + + <TextView + android:id="@+id/score_text_view_item_best_post" + android:layout_width="64dp" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/plus_button_item_best_post" + android:gravity="center"/> + + <ImageView + android:id="@+id/minus_button_item_best_post" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/score_text_view_item_best_post" + android:padding="16dp" + android:src="@drawable/ic_arrow_downward_black_24dp" + android:tint="@android:color/tab_indicator_text" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true"/> + + <ImageView + android:id="@+id/share_button_item_best_post" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:padding="16dp" + android:src="@drawable/ic_share_black_24dp" + android:tint="@android:color/tab_indicator_text" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true" /> + + </RelativeLayout> + + </LinearLayout> + +</android.support.v7.widget.CardView>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_footer_progress_bar.xml b/app/src/main/res/layout/item_footer_progress_bar.xml new file mode 100644 index 00000000..9f2347ec --- /dev/null +++ b/app/src/main/res/layout/item_footer_progress_bar.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/linear_layout_footer_progress_bar_item" + android:orientation="vertical"> + + <ProgressBar + android:id="@+id/progress_bar_footer_progress_bar_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_gravity="center" /> + + <RelativeLayout + android:id="@+id/relative_layout_footer_progress_bar_item" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="16dp" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:visibility="gone"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toStartOf="@id/retry_button_footer_progress_bar_item" + android:layout_alignParentStart="true" + android:text="@string/load_data_failed" + android:textSize="18sp" + android:layout_centerVertical="true" /> + + <Button + android:id="@+id/retry_button_footer_progress_bar_item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:text="@string/retry" /> + + </RelativeLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_post_comment.xml b/app/src/main/res/layout/item_post_comment.xml new file mode 100644 index 00000000..25f0ef81 --- /dev/null +++ b/app/src/main/res/layout/item_post_comment.xml @@ -0,0 +1,105 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/linear_layout_item_post_comment" + android:orientation="vertical" + android:layout_marginTop="12dp" + android:layout_marginBottom="8dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <TextView + android:id="@+id/author_text_view_item_post_comment" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:layout_marginRight="16dp" + android:layout_marginEnd="16dp" + android:textColor="@color/colorPrimary"/> + + <TextView + android:id="@+id/comment_time_text_view_item_post_comment" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="end" + android:layout_marginEnd="16dp" /> + + </LinearLayout> + + <TextView + android:id="@+id/comment_text_view_item_post_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="6dp" + android:layout_marginLeft="32dp" + android:layout_marginStart="32dp" + android:layout_marginRight="32dp" + android:layout_marginEnd="32dp" + android:textColor="#000000"/> + + <RelativeLayout + android:id="@+id/relative_layout_item_post_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="8dp" + android:layout_marginStart="8dp" + android:layout_marginRight="8dp" + android:layout_marginEnd="8dp"> + + <ImageView + android:id="@+id/plus_button_item_post_comment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:padding="8dp" + android:src="@drawable/ic_arrow_upward_black_12dp" + android:layout_centerVertical="true" + android:tint="@android:color/tab_indicator_text" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true"/> + + <TextView + android:id="@+id/score_text_view_item_post_comment" + android:layout_width="64dp" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/plus_button_item_post_comment" + android:gravity="center"/> + + <ImageView + android:id="@+id/minus_button_item_post_comment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toEndOf="@id/score_text_view_item_post_comment" + android:padding="8dp" + android:src="@drawable/ic_arrow_downward_black_12dp" + android:tint="@android:color/tab_indicator_text" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true"/> + + <ImageView + android:id="@+id/reply_button_item_post_comment" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:padding="8dp" + android:layout_marginEnd="16dp" + android:layout_alignParentEnd="true" + android:src="@drawable/ic_reply_black_12dp" + android:tint="@android:color/tab_indicator_text" + android:background="?actionBarItemBackground" + android:clickable="true" + android:focusable="true"/> + + </RelativeLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml new file mode 100644 index 00000000..babdcfe9 --- /dev/null +++ b/app/src/main/res/layout/nav_header_main.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="@dimen/nav_header_height" + android:background="@drawable/side_nav_bar" + android:gravity="bottom" + android:orientation="vertical" + android:theme="@style/ThemeOverlay.AppCompat.Dark"> + + <ImageView + android:id="@+id/banner_image_view_nav_header_main" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerCrop"/> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/profile_image_view_nav_header_main" + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_marginTop="40dp" + android:layout_marginBottom="16dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp"/> + + <TextView + android:id="@+id/name_text_view_nav_header_main" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/nav_header_vertical_spacing" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:layout_below="@id/profile_image_view_nav_header_main"/> + + <TextView + android:id="@+id/karma_text_view_nav_header_main" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_below="@id/name_text_view_nav_header_main"/> + +</RelativeLayout> diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml new file mode 100644 index 00000000..a30626e5 --- /dev/null +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:showIn="navigation_view"> + + <group android:checkableBehavior="single"> + <item + android:id="@+id/nav_camera" + android:icon="@drawable/ic_menu_camera" + android:title="Import" /> + <item + android:id="@+id/nav_gallery" + android:icon="@drawable/ic_menu_gallery" + android:title="Gallery" /> + <item + android:id="@+id/nav_slideshow" + android:icon="@drawable/ic_menu_slideshow" + android:title="Slideshow" /> + <item + android:id="@+id/nav_manage" + android:icon="@drawable/ic_menu_manage" + android:title="Tools" /> + </group> + + <item android:title="Communicate"> + <menu> + <item + android:id="@+id/nav_share" + android:icon="@drawable/ic_menu_share" + android:title="Share" /> + <item + android:id="@+id/nav_send" + android:icon="@drawable/ic_menu_send" + android:title="Send" /> + </menu> + </item> + +</menu> diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 00000000..a2411e31 --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:title="@string/action_settings" + app:showAsAction="never" /> +</menu> diff --git a/app/src/main/res/menu/view_image.xml b/app/src/main/res/menu/view_image.xml new file mode 100644 index 00000000..238334e1 --- /dev/null +++ b/app/src/main/res/menu/view_image.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/action_download_view_image" + android:orderInCategory="1" + android:title="@string/action_download" + android:icon="@drawable/ic_file_download_white_24dp" + app:showAsAction="always" /> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/menu/view_video.xml b/app/src/main/res/menu/view_video.xml new file mode 100644 index 00000000..ef64a9dd --- /dev/null +++ b/app/src/main/res/menu/view_video.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/action_download_view_video" + android:orderInCategory="1" + android:title="@string/action_download" + android:icon="@drawable/ic_file_download_white_24dp" + app:showAsAction="always" /> +</menu>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@drawable/ic_launcher_background" /> + <foreground android:drawable="@drawable/ic_launcher_foreground" /> +</adaptive-icon>
\ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..a2f59082 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..1b523998 --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..ff10afd6 --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..115a4c76 --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..dcd3cd80 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..459ca609 --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..8ca12fe0 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..8e19b410 --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png Binary files differnew file mode 100644 index 00000000..b824ebdd --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png Binary files differnew file mode 100644 index 00000000..4c19a13c --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/app/src/main/res/res/drawable-hdpi/baseline_comment_white_18.png b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_18.png Binary files differnew file mode 100755 index 00000000..56eccea4 --- /dev/null +++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_18.png diff --git a/app/src/main/res/res/drawable-hdpi/baseline_comment_white_24.png b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_24.png Binary files differnew file mode 100755 index 00000000..83d29b60 --- /dev/null +++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_24.png diff --git a/app/src/main/res/res/drawable-hdpi/baseline_comment_white_36.png b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_36.png Binary files differnew file mode 100755 index 00000000..e8d7c876 --- /dev/null +++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_36.png diff --git a/app/src/main/res/res/drawable-hdpi/baseline_comment_white_48.png b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_48.png Binary files differnew file mode 100755 index 00000000..846182e0 --- /dev/null +++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_48.png diff --git a/app/src/main/res/res/drawable-mdpi/baseline_comment_white_18.png b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_18.png Binary files differnew file mode 100755 index 00000000..57663a76 --- /dev/null +++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_18.png diff --git a/app/src/main/res/res/drawable-mdpi/baseline_comment_white_24.png b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_24.png Binary files differnew file mode 100755 index 00000000..7fe8f9eb --- /dev/null +++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_24.png diff --git a/app/src/main/res/res/drawable-mdpi/baseline_comment_white_36.png b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_36.png Binary files differnew file mode 100755 index 00000000..83d29b60 --- /dev/null +++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_36.png diff --git a/app/src/main/res/res/drawable-mdpi/baseline_comment_white_48.png b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_48.png Binary files differnew file mode 100755 index 00000000..48e5cda7 --- /dev/null +++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_48.png diff --git a/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_18.png b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_18.png Binary files differnew file mode 100755 index 00000000..83d29b60 --- /dev/null +++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_18.png diff --git a/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_24.png b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_24.png Binary files differnew file mode 100755 index 00000000..48e5cda7 --- /dev/null +++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_24.png diff --git a/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_36.png b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_36.png Binary files differnew file mode 100755 index 00000000..846182e0 --- /dev/null +++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_36.png diff --git a/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_48.png b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_48.png Binary files differnew file mode 100755 index 00000000..f4aa1346 --- /dev/null +++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_48.png diff --git a/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_18.png b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_18.png Binary files differnew file mode 100755 index 00000000..e8d7c876 --- /dev/null +++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_18.png diff --git a/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_24.png b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_24.png Binary files differnew file mode 100755 index 00000000..846182e0 --- /dev/null +++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_24.png diff --git a/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_36.png b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_36.png Binary files differnew file mode 100755 index 00000000..f61b7f3f --- /dev/null +++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_36.png diff --git a/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_48.png b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_48.png Binary files differnew file mode 100755 index 00000000..b57f4070 --- /dev/null +++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_48.png diff --git a/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_18.png b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_18.png Binary files differnew file mode 100755 index 00000000..846182e0 --- /dev/null +++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_18.png diff --git a/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_24.png b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_24.png Binary files differnew file mode 100755 index 00000000..f4aa1346 --- /dev/null +++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_24.png diff --git a/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_36.png b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_36.png Binary files differnew file mode 100755 index 00000000..b57f4070 --- /dev/null +++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_36.png diff --git a/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_48.png b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_48.png Binary files differnew file mode 100755 index 00000000..692fb91d --- /dev/null +++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_48.png diff --git a/app/src/main/res/res/drawable/baseline_comment_24.xml b/app/src/main/res/res/drawable/baseline_comment_24.xml new file mode 100755 index 00000000..891661ab --- /dev/null +++ b/app/src/main/res/res/drawable/baseline_comment_24.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M21.99,4c0,-1.1 -0.89,-2 -1.99,-2L4,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h14l4,4 -0.01,-18zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> +</vector> diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..344848b5 --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,16 @@ +<resources> + + <style name="AppTheme.NoActionBar"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + <item name="android:statusBarColor">@android:color/transparent</item> + </style> + + <style name="AppTheme.ActionBar.Transparent" parent="AppTheme"> + <!--<item name="colorPrimary">@android:color/transparent</item>--> + <item name="colorPrimary">@color/transparentActionBarColor</item> + <item name="android:windowTranslucentStatus">true</item> + <item name="android:windowTranslucentNavigation">true</item> + </style> + +</resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..bb250be8 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="colorPrimary">#1565C0</color> + <color name="colorPrimaryDark">#0D47A1</color> + <color name="colorAccent">#FF4081</color> + + <color name="minusButtonColor">#E91E63</color> + + <color name="transparentActionBarColor">#88000000</color> +</resources> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..4ab4520f --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,8 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + <dimen name="nav_header_vertical_spacing">8dp</dimen> + <dimen name="nav_header_height">176dp</dimen> + <dimen name="fab_margin">16dp</dimen> +</resources>
\ No newline at end of file diff --git a/app/src/main/res/values/drawables.xml b/app/src/main/res/values/drawables.xml new file mode 100644 index 00000000..52c6a6c8 --- /dev/null +++ b/app/src/main/res/values/drawables.xml @@ -0,0 +1,8 @@ +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + <item name="ic_menu_camera" type="drawable">@android:drawable/ic_menu_camera</item> + <item name="ic_menu_gallery" type="drawable">@android:drawable/ic_menu_gallery</item> + <item name="ic_menu_slideshow" type="drawable">@android:drawable/ic_menu_slideshow</item> + <item name="ic_menu_manage" type="drawable">@android:drawable/ic_menu_manage</item> + <item name="ic_menu_share" type="drawable">@android:drawable/ic_menu_share</item> + <item name="ic_menu_send" type="drawable">@android:drawable/ic_menu_send</item> +</resources> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..2f36e7c0 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ +<resources> + <string name="app_name">Infinity</string> + <string name="login_activity_label">Login</string> + + <string name="navigation_drawer_open">Open navigation drawer</string> + <string name="navigation_drawer_close">Close navigation drawer</string> + + <string name="action_settings">Settings</string> + <string name="action_download">Download</string> + + <string name="load_data_failed">Cannot load posts</string> + <string name="load_comment_failed">Error loading comments</string> + <string name="retry">Retry</string> + <string name="comments">Comments</string> + + <string name="nsfw">NSFW</string> + <string name="karma_info">Karma: %1$d</string> +</resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..abb0cb8a --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,35 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <!-- Customize your theme here. --> + <item name="colorPrimary">@color/colorPrimary</item> + <item name="colorPrimaryDark">@color/colorPrimaryDark</item> + <item name="colorAccent">@color/colorAccent</item> + </style> + + <style name="AppTheme.NoActionBar"> + <item name="android:windowContentOverlay">@null</item> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + </style> + + <style name="AppTheme.ActionBar.Transparent" parent="AppTheme"> + <item name="android:windowContentOverlay">@null</item> + <item name="windowActionBarOverlay">true</item> + <item name="colorPrimary">@android:color/transparent</item> + </style> + + <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" /> + + <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" /> + + <style name="Theme.AppCompat.Transparent" parent="AppTheme.ActionBar.Transparent"> + <item name="android:windowNoTitle">true</item> + <item name="android:windowBackground">@android:color/transparent</item> + <item name="android:colorBackgroundCacheHint">@null</item> + <item name="android:windowIsTranslucent">true</item> + <item name="android:windowAnimationStyle">@android:style/Animation</item> + </style> + +</resources> diff --git a/app/src/test/java/ml/docilealligator/infinityforreddit/ExampleUnitTest.java b/app/src/test/java/ml/docilealligator/infinityforreddit/ExampleUnitTest.java new file mode 100644 index 00000000..fc950db2 --- /dev/null +++ b/app/src/test/java/ml/docilealligator/infinityforreddit/ExampleUnitTest.java @@ -0,0 +1,17 @@ +package ml.docilealligator.infinityforreddit; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Example local unit test, which will execute on the development machine (host). + * + * @see <a href="http://d.android.com/tools/testing">Testing documentation</a> + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +}
\ No newline at end of file |