From 1f2137dd365bc07b5fd38df22e54379933585994 Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Thu, 26 Jul 2018 23:04:44 +0800 Subject: [PATCH] First commit --- .gitignore | 9 + .idea/assetWizardSettings.xml | 52 ++ .idea/caches/build_file_checksums.ser | Bin 0 -> 533 bytes .idea/codeStyles/Project.xml | 29 + .idea/gradle.xml | 18 + .idea/misc.xml | 34 ++ .idea/modules.xml | 9 + .idea/runConfigurations.xml | 12 + .idea/vcs.xml | 6 + app/.gitignore | 1 + app/build.gradle | 48 ++ app/proguard-rules.pro | 21 + .../ExampleInstrumentedTest.java | 26 + app/src/main/AndroidManifest.xml | 44 ++ .../infinityforreddit/AcquireAccessToken.java | 86 +++ .../infinityforreddit/BestPostData.java | 232 ++++++++ .../infinityforreddit/BestPostFragment.java | 245 ++++++++ .../BestPostPaginationScrollListener.java | 161 ++++++ .../BestPostRecyclerViewAdapter.java | 527 ++++++++++++++++++ .../infinityforreddit/CommentData.java | 84 +++ .../CommentRecyclerViewAdapter.java | 79 +++ .../infinityforreddit/FetchComment.java | 42 ++ .../FetchSubscribedSubreddits.java | 5 + .../infinityforreddit/FetchUserInfo.java | 69 +++ .../infinityforreddit/JSONUtils.java | 59 ++ .../LastItemSynchronizer.java | 5 + .../infinityforreddit/LoginActivity.java | 147 +++++ .../infinityforreddit/MainActivity.java | 232 ++++++++ .../infinityforreddit/PaginationNotifier.java | 6 + .../PaginationRequestQueueSynchronizer.java | 7 + .../PaginationRetryNotifier.java | 5 + .../PaginationSynchronizer.java | 96 ++++ .../infinityforreddit/ParseBestPost.java | 304 ++++++++++ .../infinityforreddit/ParseComment.java | 105 ++++ .../ParseSubscribedSubreddits.java | 4 + .../infinityforreddit/ParseUserInfo.java | 70 +++ .../infinityforreddit/RedditUtils.java | 73 +++ .../SharedPreferencesUtils.java | 20 + .../infinityforreddit/ViewImageActivity.java | 425 ++++++++++++++ .../ViewPostDetailActivity.java | 342 ++++++++++++ .../infinityforreddit/ViewVideoActivity.java | 406 ++++++++++++++ .../infinityforreddit/VoteThing.java | 88 +++ .../drawable-hdpi/baseline_add_white_24.png | Bin 0 -> 138 bytes .../ic_arrow_downward_black_24dp.png | Bin 0 -> 185 bytes .../ic_arrow_upward_black_24dp.png | Bin 0 -> 197 bytes app/src/main/res/drawable-hdpi/ic_link.png | Bin 0 -> 429 bytes .../res/drawable-hdpi/ic_share_black_24dp.png | Bin 0 -> 398 bytes .../drawable-mdpi/baseline_add_white_24.png | Bin 0 -> 92 bytes .../ic_arrow_downward_black_24dp.png | Bin 0 -> 142 bytes .../ic_arrow_upward_black_24dp.png | Bin 0 -> 147 bytes app/src/main/res/drawable-mdpi/ic_link.png | Bin 0 -> 299 bytes .../res/drawable-mdpi/ic_share_black_24dp.png | Bin 0 -> 262 bytes .../main/res/drawable-v21/ic_menu_camera.xml | 12 + .../main/res/drawable-v21/ic_menu_gallery.xml | 9 + .../main/res/drawable-v21/ic_menu_manage.xml | 9 + .../main/res/drawable-v21/ic_menu_send.xml | 9 + .../main/res/drawable-v21/ic_menu_share.xml | 9 + .../res/drawable-v21/ic_menu_slideshow.xml | 9 + .../drawable-v24/ic_launcher_foreground.xml | 34 ++ .../drawable-xhdpi/baseline_add_white_24.png | Bin 0 -> 97 bytes .../ic_arrow_downward_black_24dp.png | Bin 0 -> 221 bytes .../ic_arrow_upward_black_24dp.png | Bin 0 -> 204 bytes app/src/main/res/drawable-xhdpi/ic_link.png | Bin 0 -> 476 bytes .../drawable-xhdpi/ic_share_black_24dp.png | Bin 0 -> 483 bytes .../drawable-xxhdpi/baseline_add_white_24.png | Bin 0 -> 97 bytes .../ic_arrow_downward_black_24dp.png | Bin 0 -> 283 bytes .../ic_arrow_upward_black_24dp.png | Bin 0 -> 302 bytes app/src/main/res/drawable-xxhdpi/ic_link.png | Bin 0 -> 832 bytes .../drawable-xxhdpi/ic_share_black_24dp.png | Bin 0 -> 675 bytes .../baseline_add_white_24.png | Bin 0 -> 102 bytes .../ic_arrow_downward_black_24dp.png | Bin 0 -> 376 bytes .../ic_arrow_upward_black_24dp.png | Bin 0 -> 341 bytes .../drawable-xxxhdpi/ic_share_black_24dp.png | Bin 0 -> 888 bytes .../res/drawable/ic_arrow_back_white_24dp.xml | 5 + .../drawable/ic_arrow_downward_black_12dp.xml | 4 + .../drawable/ic_arrow_upward_black_12dp.xml | 4 + .../res/drawable/ic_comment_white_24dp.xml | 5 + .../drawable/ic_file_download_white_24dp.xml | 5 + .../res/drawable/ic_launcher_background.xml | 170 ++++++ .../main/res/drawable/ic_reply_black_12dp.xml | 4 + .../main/res/drawable/ic_send_black_24dp.xml | 9 + .../main/res/drawable/nsfw_rounded_corner.xml | 16 + app/src/main/res/drawable/rounded_corner.xml | 16 + app/src/main/res/drawable/side_nav_bar.xml | 9 + app/src/main/res/layout/activity_login.xml | 14 + app/src/main/res/layout/activity_main.xml | 26 + .../main/res/layout/activity_view_image.xml | 23 + .../res/layout/activity_view_post_detail.xml | 257 +++++++++ .../main/res/layout/activity_view_video.xml | 18 + app/src/main/res/layout/app_bar_main.xml | 25 + app/src/main/res/layout/content_main.xml | 10 + .../res/layout/exo_playback_control_view.xml | 66 +++ .../main/res/layout/fragment_best_post.xml | 28 + app/src/main/res/layout/item_best_post.xml | 179 ++++++ .../res/layout/item_footer_progress_bar.xml | 46 ++ app/src/main/res/layout/item_post_comment.xml | 105 ++++ app/src/main/res/layout/nav_header_main.xml | 44 ++ .../main/res/menu/activity_main_drawer.xml | 38 ++ app/src/main/res/menu/main.xml | 9 + app/src/main/res/menu/view_image.xml | 10 + app/src/main/res/menu/view_video.xml | 10 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../baseline_comment_white_18.png | Bin 0 -> 188 bytes .../baseline_comment_white_24.png | Bin 0 -> 171 bytes .../baseline_comment_white_36.png | Bin 0 -> 244 bytes .../baseline_comment_white_48.png | Bin 0 -> 265 bytes .../baseline_comment_white_18.png | Bin 0 -> 153 bytes .../baseline_comment_white_24.png | Bin 0 -> 144 bytes .../baseline_comment_white_36.png | Bin 0 -> 171 bytes .../baseline_comment_white_48.png | Bin 0 -> 200 bytes .../baseline_comment_white_18.png | Bin 0 -> 171 bytes .../baseline_comment_white_24.png | Bin 0 -> 200 bytes .../baseline_comment_white_36.png | Bin 0 -> 265 bytes .../baseline_comment_white_48.png | Bin 0 -> 343 bytes .../baseline_comment_white_18.png | Bin 0 -> 244 bytes .../baseline_comment_white_24.png | Bin 0 -> 265 bytes .../baseline_comment_white_36.png | Bin 0 -> 384 bytes .../baseline_comment_white_48.png | Bin 0 -> 490 bytes .../baseline_comment_white_18.png | Bin 0 -> 265 bytes .../baseline_comment_white_24.png | Bin 0 -> 343 bytes .../baseline_comment_white_36.png | Bin 0 -> 490 bytes .../baseline_comment_white_48.png | Bin 0 -> 679 bytes .../res/res/drawable/baseline_comment_24.xml | 10 + app/src/main/res/values-v21/styles.xml | 16 + app/src/main/res/values/colors.xml | 10 + app/src/main/res/values/dimens.xml | 8 + app/src/main/res/values/drawables.xml | 8 + app/src/main/res/values/strings.xml | 18 + app/src/main/res/values/styles.xml | 35 ++ .../infinityforreddit/ExampleUnitTest.java | 17 + build.gradle | 27 + gradle.properties | 17 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 ++++++ gradlew.bat | 90 +++ settings.gradle | 1 + 148 files changed, 5912 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/assetWizardSettings.xml create mode 100644 .idea/caches/build_file_checksums.ser create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/ml/docilealligator/infinityforreddit/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/AcquireAccessToken.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/BestPostData.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/BestPostFragment.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/BestPostPaginationScrollListener.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/BestPostRecyclerViewAdapter.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentData.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/CommentRecyclerViewAdapter.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/FetchComment.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/FetchSubscribedSubreddits.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/FetchUserInfo.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/JSONUtils.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/LastItemSynchronizer.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/LoginActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/MainActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PaginationNotifier.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRequestQueueSynchronizer.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PaginationRetryNotifier.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/PaginationSynchronizer.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ParseBestPost.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ParseComment.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ParseSubscribedSubreddits.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ParseUserInfo.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/RedditUtils.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/SharedPreferencesUtils.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ViewImageActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ViewPostDetailActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/ViewVideoActivity.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/VoteThing.java create mode 100755 app/src/main/res/drawable-hdpi/baseline_add_white_24.png create mode 100644 app/src/main/res/drawable-hdpi/ic_arrow_downward_black_24dp.png create mode 100644 app/src/main/res/drawable-hdpi/ic_arrow_upward_black_24dp.png create mode 100644 app/src/main/res/drawable-hdpi/ic_link.png create mode 100644 app/src/main/res/drawable-hdpi/ic_share_black_24dp.png create mode 100755 app/src/main/res/drawable-mdpi/baseline_add_white_24.png create mode 100644 app/src/main/res/drawable-mdpi/ic_arrow_downward_black_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_arrow_upward_black_24dp.png create mode 100644 app/src/main/res/drawable-mdpi/ic_link.png create mode 100644 app/src/main/res/drawable-mdpi/ic_share_black_24dp.png create mode 100644 app/src/main/res/drawable-v21/ic_menu_camera.xml create mode 100644 app/src/main/res/drawable-v21/ic_menu_gallery.xml create mode 100644 app/src/main/res/drawable-v21/ic_menu_manage.xml create mode 100644 app/src/main/res/drawable-v21/ic_menu_send.xml create mode 100644 app/src/main/res/drawable-v21/ic_menu_share.xml create mode 100644 app/src/main/res/drawable-v21/ic_menu_slideshow.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100755 app/src/main/res/drawable-xhdpi/baseline_add_white_24.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_arrow_downward_black_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_arrow_upward_black_24dp.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_link.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png create mode 100755 app/src/main/res/drawable-xxhdpi/baseline_add_white_24.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_arrow_downward_black_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_arrow_upward_black_24dp.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_link.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png create mode 100755 app/src/main/res/drawable-xxxhdpi/baseline_add_white_24.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_arrow_downward_black_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_arrow_upward_black_24dp.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png create mode 100644 app/src/main/res/drawable/ic_arrow_back_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_arrow_downward_black_12dp.xml create mode 100644 app/src/main/res/drawable/ic_arrow_upward_black_12dp.xml create mode 100644 app/src/main/res/drawable/ic_comment_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_file_download_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_reply_black_12dp.xml create mode 100644 app/src/main/res/drawable/ic_send_black_24dp.xml create mode 100644 app/src/main/res/drawable/nsfw_rounded_corner.xml create mode 100644 app/src/main/res/drawable/rounded_corner.xml create mode 100644 app/src/main/res/drawable/side_nav_bar.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_view_image.xml create mode 100644 app/src/main/res/layout/activity_view_post_detail.xml create mode 100644 app/src/main/res/layout/activity_view_video.xml create mode 100644 app/src/main/res/layout/app_bar_main.xml create mode 100644 app/src/main/res/layout/content_main.xml create mode 100644 app/src/main/res/layout/exo_playback_control_view.xml create mode 100644 app/src/main/res/layout/fragment_best_post.xml create mode 100644 app/src/main/res/layout/item_best_post.xml create mode 100644 app/src/main/res/layout/item_footer_progress_bar.xml create mode 100644 app/src/main/res/layout/item_post_comment.xml create mode 100644 app/src/main/res/layout/nav_header_main.xml create mode 100644 app/src/main/res/menu/activity_main_drawer.xml create mode 100644 app/src/main/res/menu/main.xml create mode 100644 app/src/main/res/menu/view_image.xml create mode 100644 app/src/main/res/menu/view_video.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100755 app/src/main/res/res/drawable-hdpi/baseline_comment_white_18.png create mode 100755 app/src/main/res/res/drawable-hdpi/baseline_comment_white_24.png create mode 100755 app/src/main/res/res/drawable-hdpi/baseline_comment_white_36.png create mode 100755 app/src/main/res/res/drawable-hdpi/baseline_comment_white_48.png create mode 100755 app/src/main/res/res/drawable-mdpi/baseline_comment_white_18.png create mode 100755 app/src/main/res/res/drawable-mdpi/baseline_comment_white_24.png create mode 100755 app/src/main/res/res/drawable-mdpi/baseline_comment_white_36.png create mode 100755 app/src/main/res/res/drawable-mdpi/baseline_comment_white_48.png create mode 100755 app/src/main/res/res/drawable-xhdpi/baseline_comment_white_18.png create mode 100755 app/src/main/res/res/drawable-xhdpi/baseline_comment_white_24.png create mode 100755 app/src/main/res/res/drawable-xhdpi/baseline_comment_white_36.png create mode 100755 app/src/main/res/res/drawable-xhdpi/baseline_comment_white_48.png create mode 100755 app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_18.png create mode 100755 app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_24.png create mode 100755 app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_36.png create mode 100755 app/src/main/res/res/drawable-xxhdpi/baseline_comment_white_48.png create mode 100755 app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_18.png create mode 100755 app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_24.png create mode 100755 app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_36.png create mode 100755 app/src/main/res/res/drawable-xxxhdpi/baseline_comment_white_48.png create mode 100755 app/src/main/res/res/drawable/baseline_comment_24.xml create mode 100644 app/src/main/res/values-v21/styles.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/drawables.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/test/java/ml/docilealligator/infinityforreddit/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..39fb081a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml new file mode 100644 index 00000000..ade91cd7 --- /dev/null +++ b/.idea/assetWizardSettings.xml @@ -0,0 +1,52 @@ + + + + + + \ No newline at end of file diff --git a/.idea/caches/build_file_checksums.ser b/.idea/caches/build_file_checksums.ser new file mode 100644 index 0000000000000000000000000000000000000000..82d6499fc97e3033b3ca2978ea08dfe6c42a3651 GIT binary patch literal 533 zcmZ4UmVvdnh`~NNKUXg?FQq6yGexf?KR>5fFEb@IQ7^qHF(oHeub?PDD>b=9F91S2 zm1gFoxMk*~I%lLNXBU^|7Q2L-Ts|(GuF1r}QPHR4k|6ts>s-<=$u40K$jMJm%mIZ~L26M+W@>RMT(M)K ziKWZL>yj4**y6t(o!C~wprW6VpPQemK5!@ICT#@a$+0U?vPi>{@u=R?8z+42YsaJ56TuH$Z=|jRx-GGwKp(qDe(u@AH7P7pS|iGL=H{0&iWLA$ Cq_} + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..7ac24c77 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..cc51e58e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..28a716a6 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..7f68460d --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file 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 Testing documentation + */ +@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 @@ + + + + + + + + + + + + + + + + + + + + + + \ 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() { + @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 getParams() { + Map 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 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 CREATOR = new Creator() { + @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 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() { + @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, 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 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 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, 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() { + @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, 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 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 { + private ArrayList 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, 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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 { + private Context mContext; + private ArrayList mCommentData; + private RequestQueue mVoteThingRequestQueue; + + CommentRecyclerViewAdapter(Context context, ArrayList 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() { + @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() { + @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 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() { + @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 getParams() throws AuthFailureError { + Map 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 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 CREATOR = new Creator() { + @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, 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) { + new ParseBestPostDataAsyncTask(response, bestPostData).execute(); + } + + private class ParseBestPostDataAsyncTask extends AsyncTask { + private JSONObject jsonResponse; + private ArrayList bestPostData; + private ArrayList newBestPostData; + private String lastItem; + private boolean parseFailed; + + ParseBestPostDataAsyncTask(String response, ArrayList 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, + 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, + 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, int moreCommentCount); + void onParseCommentFail(); + } + + private Context mContext; + private ParseCommentListener mParseCommentListener; + + void parseComment(Context context, String response, ArrayList commentData, ParseCommentListener parseCommentListener) { + mParseCommentListener = parseCommentListener; + mContext = context; + new ParseCommentAsyncTask(response, commentData).execute(); + } + + private class ParseCommentAsyncTask extends AsyncTask { + private JSONArray jsonResponse; + private ArrayList commentData; + private ArrayList newcommentData; + private boolean parseFailed; + int moreCommentCount; + + ParseCommentAsyncTask(String response, ArrayList 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 { + 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 getHttpBasicAuthHeader() { + Map 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 getOAuthHeader(String accessToken) { + Map 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @SuppressLint("StaticFieldLeak") + @Override + public void onResourceReady(@NonNull final Bitmap resource, @Nullable Transition transition) { + new AsyncTask() { + 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + //Need to be implemented + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target 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(), new ParseComment.ParseCommentListener() { + @Override + public void onParseCommentSuccess(ArrayList 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() { + @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 getParams() { + HashMap 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 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 0000000000000000000000000000000000000000..ea78104da5e4b1504121a330ce5b332deb318f90 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB0zF+ELn;{GUSniqP~dTU*tc!* z+l<{e<&Q9kb|3vMArKcN#Zcfm$@7!xHN_KC=O{FBD7`qkc>BzNuI8^{TYqc)^A7m0 m{CL_`uf|k0wldT1B8K8gQtsQNCo5DE3Qn<0Rqeyu2jE? z{P+1v|1qxX36f@CAAH-#_~W^Dvd4r2am;@g+HWa!l#cz!?9TbNu|k0wldT1B8K8kEe@cNCo5D%e%Rl40u`}GJC!D z?wvF0hs4*34?w v?@Z%ydDUlXrCS$u>*@V%_KMR2%%b!2mQMA!WnH2RbQgoCtDnm{r-UW|C3r`J literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..151e101df7c642d840cdba6bbc8ae657209b48dd GIT binary patch literal 429 zcmV;e0aE^nP)qGly&wCG z`h3VBet>6uGmQbKQ8XVFCkWTt=Kr`1e=~pI1vLrkE2wy4y z$#aGegUSm7dx%d+HQA=ms;YXW_TiTc&+^;GU?`tcPasP#=aD*{gaqEm7Fm`(Uh7{2 zH#DI2MZFkHm}@zHRp0w0bW3B~m|KP-%}=Y-wz_ zDsv+1vMM)4)(sUp?ux8SiZxz|tk1fN9Pr475L!RBytVTR>~P(h5H{SmS7YR>FP5B8 z;GETT{!7OcX!H%zSx1c0$Op4J{#e&gq=xRKr}5owI~5rUd5=vh)QGG%POH!tS$$Oo zBI}AOCq>qZGpdY)yr-rV>sk`2C+4jSVcoo4iZ!mf<~W%0EX{s2rvvrQdYi_U(|on< z2HJFKAKGuyqIXs-I;_|}57K93O)FAkJr#PEMb@eb6*_K-teL;{7nxP%l*pP^Wt;ce sC7MvFXDm{m9HrhN_q^V0!9o9q-!{wmnqcRJ!2kdN07*qoM6N<$f+kh3$p8QV literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..eeba9f669699f91976da46162196156260db3b6c GIT binary patch literal 92 zcmeAS@N?(olHy`uVBq!ia0vp^5+KY7Bp6QcFoXgrrjj7PU<$B+uf mzIFeIHkVZ{*4!C0fVQjpUXO@geCw9oibqn literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..368eb65f6e5e93d71ed2a38fdcb7bc090dcf843a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iC{Gv1kP61Prvtef40u>BTJ}G` zSS5XOZ}bX*2`x%`e?#uvpL@JL{YK2amp4M@b)K93+;`%M?&esb^V+O33p$?X6f`Pm v@qcjWImIQZQdF>+l}(`k7Q>H&ugjU%v^WK|m>kOl+QQ)J>gTe~DWM4f1MxPL literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..d407acb525eade9b1e7ef8bbbfe99e6dd783f3d6 GIT binary patch literal 299 zcmV+`0o4A9P)ptV~QyB8`lUQh{_j8g_-L1+mfPp?V@gW`N8h$#S4Z2O#bT z;+c4GI+FTy0%}2KIS^}kFtTQ#PbLHDR-iAMk+GqnVFXMMh>b1}(gW3t?2ur*mYbQG z=_6U(38W1}LqnO#jYg1OkQq?3z~+MNz!&eeAVolW0?=2Y)Jjhvvp{A-&923%2q~!i z(B*-NNdTzXh!_k~izRCPVCG}>oW8!kHc~LDVF?{00tpR{L29u?uNq7qWY^$xz<@`= xfG5I1kqrmDAQ}`!@t~-TM!_f;1*3qT005gW_CeGJD|`R|002ovPDHLkV1k+scD?`r literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f02d360aa290e52f16769babe8a7b73fc8133d37 GIT binary patch literal 262 zcmV+h0r~!kP)z!E9V%weI_yd=Fl&Tqx@ zU4J%T|887QWvJ8?(v!Z|6;kD3AYj^i4*F^|tb3C3I)1h6LNc`Bz(8Nem>Ns1e8+Vqcw#ZP2I2i_O(=!b4$Wz9T^!pHYaS+ zndHTmJ$G*HYN%0I^&sW#2$=MogKHr-Ik*(k&cUXT!nUi=wG{ry2jCQLp-zXh2LJ#7 M07*qoM6N<$f + + + 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 @@ + + + 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 @@ + + + \ 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 @@ + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + + + + + + + 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 0000000000000000000000000000000000000000..67bb598e52a36b6caba846efe733501479965a41 GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZA`BpB)|k7xlYrjj7PUEL+yDRo literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3406229654bbe2a6550011d82d16579695bf968b GIT binary patch literal 476 zcmV<20VDp2P)#pN%Fq$-ol7Z7jvy?9euC zl)O3dirU_sznLYD;~f9i5~SGDAT341K{?cNJ|2&cesKJt-}|x_ON%rWJ*Zk*ZAeZE zT5HzVO^LJ>%sG=fv;I1#nlEXZ9`>~W(k6{L9lNeNw$Ab@+1VP~@?IrRyK<`1{?|>a zX#c&jC$VhPU7)bX8PvpMCyj1<@L zk_wJ!3Plg>lVDHjd_C9D?zol5B&k{u#QWlcjS^iJqOG}YYxl0>XNS9cyuR_zENF`V zL^YY57oxS@wViYo^MZIwM)XOxQ>%0x?{xgp-Z&vypCOZ$d^P#|I@U}HMUNbiU60c~ z6f0KT&@a1w*R2#wK02dEcKbaPil)^-UBkSVhPrf)d;Q1fdtc~iLRGR}!xn{j#$~b1 zn6?mYP$o6+gh*quX$g^LyS-pWHVq-tsBG#&qyZUC2&L}Hs%pG6z9PFpV`j}5HQ+C_ Z?icFHxy#pVb)^6R002ovPDHLkV1k48;SK-* literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0fdced8fce76a0f9a527388935adecebf63d0dae GIT binary patch literal 97 zcmeAS@N?(olHy`uVBq!ia0vp^9w5vJBp7O^^}Pa8OeH~n!3+##lh0ZJc~YJ(jv*C{ s$r5`OFVdQ&MBb@07vc`*Z=?k literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ace750c4d31cab6c54b58e3b00704ac25b183b87 GIT binary patch literal 283 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw9(lSrhEy=VJ$IGy5Q6~A#cYP( z#};1H7Fo`JN2$H#;LV%!E9Pk~of33vTIXKdMiwCt1t_t~RwTr1#_c=5_?b;}68bD= z&sXu_+43-FUg5EuIcc_RUIk{w2RtWmteE*A=NZqInU7tA>9Ah@#HnxmLOldbTa z?-I`@kp>3#30CIKv&^2%x$SA#QNXZLwpoFF!m(?8j&qtl8CS4cG?;7(<9Hh cyS>&ja3qE{`K2*00Q#1})78&qol`;+0L@QubpQYW literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..99622e7cf0778b50bac047f1b056598dd4ac17eb GIT binary patch literal 302 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawzIwVihEy=Vy=3Xf>>$8;(Tagl zG48;O;!S6CBYA(QsO2?Yo#d??`6&FGyYQX`j2@axrwBC{#?4`7KNeXilF4*@vRZ)+ zh_X38Ijz{{WU&qB`HxF}%J_ehDNC)jn)JM;Aog3YY9-%OR)3>8rhk|Yx=mntKG*aQ zXA^IHlZs`?1O?4=Tzw6G3X$iewKE(GPMgnjIUni7ETnQW(_!9~f;b?1lFIp)4jXKp zF3n;6vY>JaS5+dTdUL|*eSfN%9JS`iU70L*SJbwcl`F)Pzr*dRhQOVT&*g5iS_HhD qB*`hj$SM-x042Wc_Xc{Pj=@0v@6owP9_^k^;6xnJgBITeI2h!KS6# z?cTS)Y3EeY)-xe>sEv&`^k_8NvGQ~>ne2|oiRA7m@ptW3}Ed*;3$R_XKWl2nvdY4(hQ{W2?D z=T)Yy`E|?eO0#DatVoFJE+2%9QL<%2h2mZ3$775u><{(+-%Lm!6qn zw~YCJl{3tKnuup1y+}jV%2}kc4jH}DE*s=HVHLa?u?miIrk@X;PUmJwd@VynN*$;J zorUaIq+K@1`TwJ!O{hEBT31y;(;eXJpl)>UggD@imCt!uz|b9L!yVAd%L>bOvLOy= zumhNaVopIU!jTtY!qE6(C>ycZ8!=K5ky8;Q<02>HNJl4-j^;on@_|f|1&1Ta3XdcO zhw_mR<=x!JwUk)bQZi29Vpgn+SxzKZ6JuUY6kN`YcsVyaM{+$q()IML6L>)*fdGS7 zl+xLv^O9a7j^H)b6yZf}000000000000000000000001hO8EoHL)Zd^a=V}a0000< KMNUMnLSTX?{)LDD literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..784933ad55a011a9b829767844969f36ecd98b80 GIT binary patch literal 675 zcmV;U0$lxxP)UZPF>#3%k9#08-1VeKX7y3y)WkyiA4SZF@~9C4WBJmnPCX0 z4zY{`TA~+6`Y9urmFRP`J7pxKlKw8mSVjmhhq7VB(jcm45lbGb)(}etRQZUd3aYBj zwxF+wr95hqT&Ij!%AjPVwmX&vP;ii!NL{_7;o5nKkJMF>12j~Oi~K+Wz2{fRa*okZ zNBMvR`p!9G?B}idu^PKOT|8ym_`}>|Cp80j6!58#C&K^@)XEJ?NTBEJ!->-rkU%S( zz=aK6bWB+xg`IvdW7 zD%@pS~Mow4tCcWJy2G*_n05h-vIn4hi&{y~G$}-F#he1O2C?)6Rf~wFc~mVRmgbu6lTD*4jaWL5s!q#2`%N3lZX@Aw6Lm>) zNJwvp<3K0bkmYpXNPT$m`Z~Df*BafCZDwc^5i{T978G? xlN*`>5BzUTIj7P7-(I3-fns}2dtZ?oBSS_(`pg9u0+k>GJYD@<);T3K0RZru9Krwq literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..2690b20268543d801e39f0a8ce9a670caf007544 GIT binary patch literal 376 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z-aI3;uuoF`1ZVc+2I5pmWR1h zmkRItVp?GIT;`n1#E|c=4>I;Y-qAcQ(cbd;oZ@qq&+j}bJu8{ez{n}N`s`8l4Ef9Gn=bm5G=UdK<%g={CD^NSz(!<^X7@XRXz9%s_O zg7dfh7(AYtv#?xv>3Q3eu{d^xH-kiCF#Ctkr(2j39M7NU?B`TuWcbYJ#I)e~bx!`M z>5Sa5MW0`{upBtQUih7~6Vrib%uvpjUj_*awX5ppPGETOlR=SLr~c5NIR&djnG%3X zq;-Q21YJK-(@?4vRU()h3y0LX2mHO@JZF;OXk;vd$@?2>>VYoNNF9 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fca5022c7415c7e6fd08636abf3285eb4dfa2869 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z$oMC;uuoF`1bs1&O-?Tt`9X8 zzOAVJ@uT|l#jqo5I}5}(i(8AQGc!M5J@?t1;&YbI=M*nKR{#9GDYH|Nor219Fty}) zu~S{072}iT!VVzHT{xmnr|}sxP*8>usHCorf$_=xgG>7vJ}p0*yULx1@yYc^QI;$d z_Op2?HvB1d;9`8Tp1Fxb;lKHUR~OtFellEe0TK)xj6jwXl*REu(S~uMqC5iwh>+q? zkY|+QP?^sRCU$W!KQCrbzmwI>JAt2};lr$~+rZ#r@O1Ta JS?83{1OVokc%c9Q literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a8544ce5d6c8bf471c8fbd54cf4f88254ea9ccb GIT binary patch literal 888 zcmV-;1Bd*HP)9LMqRypZ5w`JO>))98$f6)oE2rdCPO(4t&OEy|HVqeUTEVJbno>6L`orcEYB zO@<!#gj`DvJ3WY+U zOXmgB9rTlB8$sA6%WE_c81+o!A$fUBQ49J#&EJTj15zm0gJ7>4#oCY}dJ?5-IY5eN zpBhw}M2c)2g=+DTB0D3FI(>*kub|EX;?NxGY$6V=qfQQS=oocQ5r>XYXB~0qXVl3e z4tVzmNT;dsiN1Pi)h5PviagRT!#CK8B$bZVC8K2!qANxqb ze#NtY#_=pZYi128*dhH~rkNeY&<>B`tGoCTF*HdH08qt9fnCb+E*1Ewj3MJ(e#-+~ zR7EGBvc?{}{Kz!VQ|a$qwzFkC7SC}5s@=!e##izkZb7x%_y94q$UP_!vCL%;d&aNH zC-7Aruae<6&hXeGLoabIdX!b`T>2=%Bwm5@oF)bU+{qV~xs2nhR?GF%E-l>5TjY#$ z*@VwtMyz>kYo5!e5PQ1kGR{6?PWxQK$A~#ab7_#^3~}m^0WPCPFJegBTpG+H4$TMs z?(%xj?=Ew}e*kqhf_{YNg8mYBBTjun9ja7wia2$|>y)C%hlq0X|_d z%}}r7A-edCpE%%<>&)^3Q7%gIgYj>?Z%_*QmGGkRxNVXm0{T@jVtnnFXe1CCSu$?c zC>7{OgcO^`%dnRyK|kxfF7pWa614L+0CA`db%qd! z22p1macB&6(m{WK;h-O(4#c4p>Li?IGzvlf-`xdNN+Lzpgi=#T5q*SO<*Xp~_>M9Z zt7h5$>vKEGU1h>@c*+QbhsfaN4d(()1WJrH1{phVkQ7lup-?CkD#AaVcIHkIiEiWo O0000 + + 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 @@ + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + 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 @@ + + + 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 @@ + + + + + + + + + + \ 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 @@ + + + + + + + + + + \ 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 @@ + + + \ 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 @@ + + + + + + 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 @@ + + + + + + + + + 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 @@ + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + 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 @@ + + + + + + + + + + + + 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 @@ + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + +