aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorAlex Ning <chineseperson5@gmail.com>2018-07-26 15:04:44 +0000
committerAlex Ning <chineseperson5@gmail.com>2018-07-26 15:04:44 +0000
commit1f2137dd365bc07b5fd38df22e54379933585994 (patch)
treef9ef585dda71db8bc809120761cac42e22dd1193 /app
downloadinfinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.gz
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.bz2
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.lz
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.xz
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.tar.zst
infinity-for-reddit-1f2137dd365bc07b5fd38df22e54379933585994.zip
First commit
Diffstat (limited to 'app')
-rw-r--r--app/.gitignore1
-rw-r--r--app/build.gradle48
-rw-r--r--app/proguard-rules.pro21
-rw-r--r--app/src/androidTest/java/ml/docilealligator/infinityforreddit/ExampleInstrumentedTest.java26
-rw-r--r--app/src/main/AndroidManifest.xml44
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/AcquireAccessToken.java86
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostData.java232
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostFragment.java245
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostPaginationScrollListener.java161
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/BestPostRecyclerViewAdapter.java527
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/CommentData.java84
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/CommentRecyclerViewAdapter.java79
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/FetchComment.java42
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubscribedSubreddits.java5
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/FetchUserInfo.java69
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java59
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/LastItemSynchronizer.java5
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/LoginActivity.java147
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java232
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationNotifier.java6
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRequestQueueSynchronizer.java7
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRetryNotifier.java5
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/PaginationSynchronizer.java96
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseBestPost.java304
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java105
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubscribedSubreddits.java4
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ParseUserInfo.java70
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/RedditUtils.java73
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/SharedPreferencesUtils.java20
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ViewImageActivity.java425
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java342
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/ViewVideoActivity.java406
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/VoteThing.java88
-rwxr-xr-xapp/src/main/res/drawable-hdpi/baseline_add_white_24.pngbin0 -> 138 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_arrow_downward_black_24dp.pngbin0 -> 185 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_arrow_upward_black_24dp.pngbin0 -> 197 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_link.pngbin0 -> 429 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_share_black_24dp.pngbin0 -> 398 bytes
-rwxr-xr-xapp/src/main/res/drawable-mdpi/baseline_add_white_24.pngbin0 -> 92 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_arrow_downward_black_24dp.pngbin0 -> 142 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_arrow_upward_black_24dp.pngbin0 -> 147 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_link.pngbin0 -> 299 bytes
-rw-r--r--app/src/main/res/drawable-mdpi/ic_share_black_24dp.pngbin0 -> 262 bytes
-rw-r--r--app/src/main/res/drawable-v21/ic_menu_camera.xml12
-rw-r--r--app/src/main/res/drawable-v21/ic_menu_gallery.xml9
-rw-r--r--app/src/main/res/drawable-v21/ic_menu_manage.xml9
-rw-r--r--app/src/main/res/drawable-v21/ic_menu_send.xml9
-rw-r--r--app/src/main/res/drawable-v21/ic_menu_share.xml9
-rw-r--r--app/src/main/res/drawable-v21/ic_menu_slideshow.xml9
-rw-r--r--app/src/main/res/drawable-v24/ic_launcher_foreground.xml34
-rwxr-xr-xapp/src/main/res/drawable-xhdpi/baseline_add_white_24.pngbin0 -> 97 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_arrow_downward_black_24dp.pngbin0 -> 221 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_arrow_upward_black_24dp.pngbin0 -> 204 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_link.pngbin0 -> 476 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_share_black_24dp.pngbin0 -> 483 bytes
-rwxr-xr-xapp/src/main/res/drawable-xxhdpi/baseline_add_white_24.pngbin0 -> 97 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_arrow_downward_black_24dp.pngbin0 -> 283 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_arrow_upward_black_24dp.pngbin0 -> 302 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_link.pngbin0 -> 832 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.pngbin0 -> 675 bytes
-rwxr-xr-xapp/src/main/res/drawable-xxxhdpi/baseline_add_white_24.pngbin0 -> 102 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_arrow_downward_black_24dp.pngbin0 -> 376 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_arrow_upward_black_24dp.pngbin0 -> 341 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.pngbin0 -> 888 bytes
-rw-r--r--app/src/main/res/drawable/ic_arrow_back_white_24dp.xml5
-rw-r--r--app/src/main/res/drawable/ic_arrow_downward_black_12dp.xml4
-rw-r--r--app/src/main/res/drawable/ic_arrow_upward_black_12dp.xml4
-rw-r--r--app/src/main/res/drawable/ic_comment_white_24dp.xml5
-rw-r--r--app/src/main/res/drawable/ic_file_download_white_24dp.xml5
-rw-r--r--app/src/main/res/drawable/ic_launcher_background.xml170
-rw-r--r--app/src/main/res/drawable/ic_reply_black_12dp.xml4
-rw-r--r--app/src/main/res/drawable/ic_send_black_24dp.xml9
-rw-r--r--app/src/main/res/drawable/nsfw_rounded_corner.xml16
-rw-r--r--app/src/main/res/drawable/rounded_corner.xml16
-rw-r--r--app/src/main/res/drawable/side_nav_bar.xml9
-rw-r--r--app/src/main/res/layout/activity_login.xml14
-rw-r--r--app/src/main/res/layout/activity_main.xml26
-rw-r--r--app/src/main/res/layout/activity_view_image.xml23
-rw-r--r--app/src/main/res/layout/activity_view_post_detail.xml257
-rw-r--r--app/src/main/res/layout/activity_view_video.xml18
-rw-r--r--app/src/main/res/layout/app_bar_main.xml25
-rw-r--r--app/src/main/res/layout/content_main.xml10
-rw-r--r--app/src/main/res/layout/exo_playback_control_view.xml66
-rw-r--r--app/src/main/res/layout/fragment_best_post.xml28
-rw-r--r--app/src/main/res/layout/item_best_post.xml179
-rw-r--r--app/src/main/res/layout/item_footer_progress_bar.xml46
-rw-r--r--app/src/main/res/layout/item_post_comment.xml105
-rw-r--r--app/src/main/res/layout/nav_header_main.xml44
-rw-r--r--app/src/main/res/menu/activity_main_drawer.xml38
-rw-r--r--app/src/main/res/menu/main.xml9
-rw-r--r--app/src/main/res/menu/view_image.xml10
-rw-r--r--app/src/main/res/menu/view_video.xml10
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml5
-rw-r--r--app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml5
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher.pngbin0 -> 3056 bytes
-rw-r--r--app/src/main/res/mipmap-hdpi/ic_launcher_round.pngbin0 -> 5024 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher.pngbin0 -> 2096 bytes
-rw-r--r--app/src/main/res/mipmap-mdpi/ic_launcher_round.pngbin0 -> 2858 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher.pngbin0 -> 4569 bytes
-rw-r--r--app/src/main/res/mipmap-xhdpi/ic_launcher_round.pngbin0 -> 7098 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher.pngbin0 -> 6464 bytes
-rw-r--r--app/src/main/res/mipmap-xxhdpi/ic_launcher_round.pngbin0 -> 10676 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher.pngbin0 -> 9250 bytes
-rw-r--r--app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.pngbin0 -> 15523 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-hdpi/baseline_comment_white_18.pngbin0 -> 188 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-hdpi/baseline_comment_white_24.pngbin0 -> 171 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-hdpi/baseline_comment_white_36.pngbin0 -> 244 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-hdpi/baseline_comment_white_48.pngbin0 -> 265 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-mdpi/baseline_comment_white_18.pngbin0 -> 153 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-mdpi/baseline_comment_white_24.pngbin0 -> 144 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-mdpi/baseline_comment_white_36.pngbin0 -> 171 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-mdpi/baseline_comment_white_48.pngbin0 -> 200 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xhdpi/baseline_comment_white_18.pngbin0 -> 171 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xhdpi/baseline_comment_white_24.pngbin0 -> 200 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xhdpi/baseline_comment_white_36.pngbin0 -> 265 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xhdpi/baseline_comment_white_48.pngbin0 -> 343 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxhdpi/baseline_comment_white_18.pngbin0 -> 244 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxhdpi/baseline_comment_white_24.pngbin0 -> 265 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxhdpi/baseline_comment_white_36.pngbin0 -> 384 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxhdpi/baseline_comment_white_48.pngbin0 -> 490 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_18.pngbin0 -> 265 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_24.pngbin0 -> 343 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_36.pngbin0 -> 490 bytes
-rwxr-xr-xapp/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_48.pngbin0 -> 679 bytes
-rwxr-xr-xapp/src/main/res/res/drawable/baseline_comment_24.xml10
-rw-r--r--app/src/main/res/values-v21/styles.xml16
-rw-r--r--app/src/main/res/values/colors.xml10
-rw-r--r--app/src/main/res/values/dimens.xml8
-rw-r--r--app/src/main/res/values/drawables.xml8
-rw-r--r--app/src/main/res/values/strings.xml18
-rw-r--r--app/src/main/res/values/styles.xml35
-rw-r--r--app/src/test/java/ml/docilealligator/infinityforreddit/ExampleUnitTest.java17
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
new file mode 100755
index 00000000..ea78104d
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/baseline_add_white_24.png
Binary files differ
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
new file mode 100644
index 00000000..b848197a
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_arrow_downward_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..705f7ae8
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_arrow_upward_black_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_link.png b/app/src/main/res/drawable-hdpi/ic_link.png
new file mode 100644
index 00000000..151e101d
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_link.png
Binary files differ
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
new file mode 100644
index 00000000..20ba4806
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png
Binary files differ
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
new file mode 100755
index 00000000..eeba9f66
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/baseline_add_white_24.png
Binary files differ
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
new file mode 100644
index 00000000..3cc4b130
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_arrow_downward_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..368eb65f
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_arrow_upward_black_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-mdpi/ic_link.png b/app/src/main/res/drawable-mdpi/ic_link.png
new file mode 100644
index 00000000..d407acb5
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_link.png
Binary files differ
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
new file mode 100644
index 00000000..f02d360a
--- /dev/null
+++ b/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png
Binary files differ
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
new file mode 100755
index 00000000..67bb598e
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/baseline_add_white_24.png
Binary files differ
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
new file mode 100644
index 00000000..3c147bc5
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_arrow_downward_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..f2b35ac2
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_arrow_upward_black_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_link.png b/app/src/main/res/drawable-xhdpi/ic_link.png
new file mode 100644
index 00000000..34062296
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_link.png
Binary files differ
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
new file mode 100644
index 00000000..81c80b70
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png
Binary files differ
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
new file mode 100755
index 00000000..0fdced8f
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/baseline_add_white_24.png
Binary files differ
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
new file mode 100644
index 00000000..ace750c4
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_arrow_downward_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..99622e7c
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_arrow_upward_black_24dp.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_link.png b/app/src/main/res/drawable-xxhdpi/ic_link.png
new file mode 100644
index 00000000..9b2ad879
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_link.png
Binary files differ
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
new file mode 100644
index 00000000..784933ad
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png
Binary files differ
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
new file mode 100755
index 00000000..d64c22e9
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/baseline_add_white_24.png
Binary files differ
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
new file mode 100644
index 00000000..2690b202
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_arrow_downward_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..fca5022c
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_arrow_upward_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..5a8544ce
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png
Binary files differ
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
new file mode 100644
index 00000000..a2f59082
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..1b523998
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..ff10afd6
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..115a4c76
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..dcd3cd80
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..459ca609
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8ca12fe0
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..8e19b410
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..b824ebdd
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..4c19a13c
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
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
new file mode 100755
index 00000000..56eccea4
--- /dev/null
+++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_18.png
Binary files differ
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
new file mode 100755
index 00000000..83d29b60
--- /dev/null
+++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_24.png
Binary files differ
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
new file mode 100755
index 00000000..e8d7c876
--- /dev/null
+++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_36.png
Binary files differ
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
new file mode 100755
index 00000000..846182e0
--- /dev/null
+++ b/app/src/main/res/res/drawable-hdpi/baseline_comment_white_48.png
Binary files differ
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
new file mode 100755
index 00000000..57663a76
--- /dev/null
+++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_18.png
Binary files differ
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
new file mode 100755
index 00000000..7fe8f9eb
--- /dev/null
+++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_24.png
Binary files differ
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
new file mode 100755
index 00000000..83d29b60
--- /dev/null
+++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_36.png
Binary files differ
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
new file mode 100755
index 00000000..48e5cda7
--- /dev/null
+++ b/app/src/main/res/res/drawable-mdpi/baseline_comment_white_48.png
Binary files differ
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
new file mode 100755
index 00000000..83d29b60
--- /dev/null
+++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_18.png
Binary files differ
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
new file mode 100755
index 00000000..48e5cda7
--- /dev/null
+++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_24.png
Binary files differ
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
new file mode 100755
index 00000000..846182e0
--- /dev/null
+++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_36.png
Binary files differ
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
new file mode 100755
index 00000000..f4aa1346
--- /dev/null
+++ b/app/src/main/res/res/drawable-xhdpi/baseline_comment_white_48.png
Binary files differ
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
new file mode 100755
index 00000000..e8d7c876
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_18.png
Binary files differ
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
new file mode 100755
index 00000000..846182e0
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_24.png
Binary files differ
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
new file mode 100755
index 00000000..f61b7f3f
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_36.png
Binary files differ
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
new file mode 100755
index 00000000..b57f4070
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_48.png
Binary files differ
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
new file mode 100755
index 00000000..846182e0
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_18.png
Binary files differ
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
new file mode 100755
index 00000000..f4aa1346
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_24.png
Binary files differ
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
new file mode 100755
index 00000000..b57f4070
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_36.png
Binary files differ
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
new file mode 100755
index 00000000..692fb91d
--- /dev/null
+++ b/app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_48.png
Binary files differ
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