Austin Huang
4 years ago
commit
13beabf741
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
275 changed files with 22638 additions and 0 deletions
-
16.gitignore
-
54.gitlab-ci.yml
-
1.idea/.name
-
119.idea/codeStyles
-
21.idea/gradle.xml
-
7.idea/inspectionProfiles/profiles_settings.xml
-
35.idea/jarRepositories.xml
-
49.idea/misc.xml
-
6.idea/render.experimental.xml
-
12.idea/runConfigurations.xml
-
53.idea/runConfigurations/app.xml
-
6.idea/vcs.xml
-
193CHANGELOG
-
674LICENSE
-
5README.md
-
1app/.gitignore
-
50app/build.gradle
-
29app/lint.xml
-
BINapp/play_icon.png
-
21app/proguard-rules.pro
-
218app/src/main/AndroidManifest.xml
-
BINapp/src/main/ic_launcher-playstore.png
-
66app/src/main/java/awais/instagrabber/InstaApp.java
-
864app/src/main/java/awais/instagrabber/MainHelper.java
-
11app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.java
-
146app/src/main/java/awais/instagrabber/activities/CommentsViewer.java
-
113app/src/main/java/awais/instagrabber/activities/DirectMessages.java
-
101app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
-
348app/src/main/java/awais/instagrabber/activities/FollowViewer.java
-
130app/src/main/java/awais/instagrabber/activities/Login.java
-
480app/src/main/java/awais/instagrabber/activities/Main.java
-
639app/src/main/java/awais/instagrabber/activities/PostViewer.java
-
215app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
-
354app/src/main/java/awais/instagrabber/activities/StoryViewer.java
-
136app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java
-
116app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java
-
87app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
-
486app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
-
57app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
-
144app/src/main/java/awais/instagrabber/adapters/FollowAdapter.java
-
53app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
-
354app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java
-
92app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java
-
68app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
-
75app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java
-
84app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java
-
60app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
-
85app/src/main/java/awais/instagrabber/adapters/viewholder/CommentViewHolder.java
-
43app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageViewHolder.java
-
22app/src/main/java/awais/instagrabber/adapters/viewholder/DiscoverViewHolder.java
-
52app/src/main/java/awais/instagrabber/adapters/viewholder/FeedItemViewHolder.java
-
22app/src/main/java/awais/instagrabber/adapters/viewholder/FollowsViewHolder.java
-
21app/src/main/java/awais/instagrabber/adapters/viewholder/HighlightViewHolder.java
-
20app/src/main/java/awais/instagrabber/adapters/viewholder/PostMediaViewHolder.java
-
23app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
-
91app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/TextMessageViewHolder.java
-
265app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java
-
194app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java
-
248app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
-
194app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
-
103app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java
-
101app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java
-
87app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java
-
146app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
-
134app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
-
83app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
-
120app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java
-
102app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java
-
98app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
-
54app/src/main/java/awais/instagrabber/asyncs/UsernameFetcher.java
-
100app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
-
76app/src/main/java/awais/instagrabber/asyncs/direct_messages/UserInboxFetcher.java
-
105app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
-
17app/src/main/java/awais/instagrabber/customviews/CommentMentionClickSpan.java
-
25app/src/main/java/awais/instagrabber/customviews/FixedImageView.java
-
986app/src/main/java/awais/instagrabber/customviews/MouseDrawer.java
-
179app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
-
182app/src/main/java/awais/instagrabber/customviews/RemixDrawerLayout.java
-
37app/src/main/java/awais/instagrabber/customviews/helpers/GridAutofitLayoutManager.java
-
31app/src/main/java/awais/instagrabber/customviews/helpers/GridSpacingItemDecoration.java
-
67app/src/main/java/awais/instagrabber/customviews/helpers/RecyclerLazyLoader.java
-
34app/src/main/java/awais/instagrabber/customviews/helpers/SwipeGestureListener.java
-
282app/src/main/java/awais/instagrabber/customviews/helpers/VideoAwareRecyclerScroller.java
-
252app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/SoundParser.java
-
5app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveFormProgressChangeListener.java
-
7app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveGravity.java
-
225app/src/main/java/awais/instagrabber/customviews/masoudss_waveform/WaveformSeekBar.java
-
98app/src/main/java/awais/instagrabber/dialogs/AboutDialog.java
-
62app/src/main/java/awais/instagrabber/dialogs/ProfileSettingsDialog.java
-
169app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
-
213app/src/main/java/awais/instagrabber/dialogs/SettingsDialog.java
-
173app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java
-
153app/src/main/java/awais/instagrabber/directdownload/DirectDownload.java
-
117app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
-
6app/src/main/java/awais/instagrabber/interfaces/FetchListener.java
-
11app/src/main/java/awais/instagrabber/interfaces/ItemGetter.java
-
5app/src/main/java/awais/instagrabber/interfaces/LazyLoadListener.java
-
7app/src/main/java/awais/instagrabber/interfaces/MentionClickListener.java
-
5app/src/main/java/awais/instagrabber/interfaces/OnGroupClickListener.java
-
5app/src/main/java/awais/instagrabber/interfaces/SwipeEvent.java
@ -0,0 +1,16 @@ |
|||
*.iml |
|||
.gradle |
|||
/local.properties |
|||
/.idea/caches |
|||
/.idea/libraries |
|||
/.idea/modules.xml |
|||
/.idea/markdown-navigator.xml |
|||
/.idea/markdown-navigator-enh.xml |
|||
/.idea/workspace.xml |
|||
/.idea/navEditor.xml |
|||
/.idea/assetWizardSettings.xml |
|||
.DS_Store |
|||
/build |
|||
/captures |
|||
.externalNativeBuild |
|||
.cxx |
@ -0,0 +1,54 @@ |
|||
image: openjdk:8-jdk |
|||
|
|||
variables: |
|||
ANDROID_COMPILE_SDK: "29" |
|||
ANDROID_BUILD_TOOLS: "29.0.2" |
|||
ANDROID_SDK_TOOLS: "4333796" |
|||
|
|||
before_script: |
|||
# - export vercode=$(cat ./app/build.gradle | grep versionName) |
|||
# - export vercode=$(echo $vercode | awk -F[=\'] '{print $2}') |
|||
# - echo $vercode > vercode.txt |
|||
- apt-get --quiet update --yes |
|||
- apt-get --quiet install --yes wget tar unzip lib32stdc++6 lib32z1 |
|||
- if [ -f "android-sdk.zip" ]; then echo "exists!!" ; else wget --quiet --output-document=android-sdk.zip https://dl.google.com/android/repository/sdk-tools-linux-${ANDROID_SDK_TOOLS}.zip ; fi |
|||
- unzip -d android-sdk-linux android-sdk.zip |
|||
- echo y | android-sdk-linux/tools/bin/sdkmanager "platforms;android-${ANDROID_COMPILE_SDK}" >/dev/null |
|||
- echo y | android-sdk-linux/tools/bin/sdkmanager "platform-tools" >/dev/null |
|||
- echo y | android-sdk-linux/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null |
|||
- export ANDROID_HOME=$PWD/android-sdk-linux |
|||
- export PATH=$PATH:$PWD/android-sdk-linux/platform-tools/ |
|||
- chmod +x ./gradlew |
|||
- set +o pipefail |
|||
- yes | android-sdk-linux/tools/bin/sdkmanager --licenses |
|||
- set -o pipefail |
|||
|
|||
stages: |
|||
- release |
|||
|
|||
|
|||
# lintDebug: |
|||
# stage: build |
|||
# script: |
|||
# - ./gradlew -Pci --console=plain :app:lintDebug -PbuildDir=lint |
|||
|
|||
assembleDebug: |
|||
stage: release |
|||
script: |
|||
- ./gradlew assembleDebug |
|||
artifacts: |
|||
expire_in: 4 days |
|||
paths: |
|||
- app/build/outputs/ |
|||
|
|||
# assembleRelease: |
|||
# stage: release |
|||
# script: |
|||
# - ./gradlew assembleRelease |
|||
# -Pandroid.injected.signing.store.file=$(pwd)/.RELEASE.jks |
|||
# -Pandroid.injected.signing.store.password=$PSWD |
|||
# -Pandroid.injected.signing.key.alias=$AIZA |
|||
# -Pandroid.injected.signing.key.password=$PSWD |
|||
# artifacts: |
|||
# paths: |
|||
# - app/build/outputs/ |
@ -0,0 +1 @@ |
|||
InstaGrabber |
@ -0,0 +1,119 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ProjectCodeStyleConfiguration"> |
|||
<code_scheme name="Project" version="173"> |
|||
<codeStyleSettings language="XML"> |
|||
<indentOptions> |
|||
<option name="CONTINUATION_INDENT_SIZE" value="4" /> |
|||
</indentOptions> |
|||
<arrangement> |
|||
<rules> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>xmlns:android</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>^$</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>xmlns:.*</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>^$</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
<order>BY_NAME</order> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>.*:id</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>.*:name</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>name</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>^$</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>style</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>^$</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>.*</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>^$</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
<order>BY_NAME</order> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>.*</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
<order>ANDROID_ATTRIBUTE_ORDER</order> |
|||
</rule> |
|||
</section> |
|||
<section> |
|||
<rule> |
|||
<match> |
|||
<AND> |
|||
<NAME>.*</NAME> |
|||
<XML_ATTRIBUTE /> |
|||
<XML_NAMESPACE>.*</XML_NAMESPACE> |
|||
</AND> |
|||
</match> |
|||
<order>BY_NAME</order> |
|||
</rule> |
|||
</section> |
|||
</rules> |
|||
</arrangement> |
|||
</codeStyleSettings> |
|||
</code_scheme> |
|||
</component> |
|||
</project> |
@ -0,0 +1,21 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="GradleMigrationSettings" migrationVersion="1" /> |
|||
<component name="GradleSettings"> |
|||
<option name="linkedExternalProjectsSettings"> |
|||
<GradleProjectSettings> |
|||
<option name="testRunner" value="PLATFORM" /> |
|||
<option name="distributionType" value="DEFAULT_WRAPPED" /> |
|||
<option name="externalProjectPath" value="$PROJECT_DIR$" /> |
|||
<option name="gradleJvm" value="1.8" /> |
|||
<option name="modules"> |
|||
<set> |
|||
<option value="$PROJECT_DIR$" /> |
|||
<option value="$PROJECT_DIR$/app" /> |
|||
</set> |
|||
</option> |
|||
<option name="resolveModulePerSourceSet" value="false" /> |
|||
</GradleProjectSettings> |
|||
</option> |
|||
</component> |
|||
</project> |
@ -0,0 +1,7 @@ |
|||
<component name="InspectionProjectProfileManager"> |
|||
<settings> |
|||
<option name="PROJECT_PROFILE" value="Default" /> |
|||
<option name="USE_PROJECT_PROFILE" value="false" /> |
|||
<version value="1.0" /> |
|||
</settings> |
|||
</component> |
@ -0,0 +1,35 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="RemoteRepositoriesConfiguration"> |
|||
<remote-repository> |
|||
<option name="id" value="central" /> |
|||
<option name="name" value="Maven Central repository" /> |
|||
<option name="url" value="https://repo1.maven.org/maven2" /> |
|||
</remote-repository> |
|||
<remote-repository> |
|||
<option name="id" value="jboss.community" /> |
|||
<option name="name" value="JBoss Community repository" /> |
|||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" /> |
|||
</remote-repository> |
|||
<remote-repository> |
|||
<option name="id" value="MavenRepo" /> |
|||
<option name="name" value="MavenRepo" /> |
|||
<option name="url" value="https://repo.maven.apache.org/maven2/" /> |
|||
</remote-repository> |
|||
<remote-repository> |
|||
<option name="id" value="BintrayJCenter" /> |
|||
<option name="name" value="BintrayJCenter" /> |
|||
<option name="url" value="https://jcenter.bintray.com/" /> |
|||
</remote-repository> |
|||
<remote-repository> |
|||
<option name="id" value="maven" /> |
|||
<option name="name" value="maven" /> |
|||
<option name="url" value="https://jitpack.io" /> |
|||
</remote-repository> |
|||
<remote-repository> |
|||
<option name="id" value="Google" /> |
|||
<option name="name" value="Google" /> |
|||
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> |
|||
</remote-repository> |
|||
</component> |
|||
</project> |
@ -0,0 +1,49 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="ExternalStorageConfigurationManager" enabled="true" /> |
|||
<component name="NullableNotNullManager"> |
|||
<option name="myDefaultNullable" value="androidx.annotation.Nullable" /> |
|||
<option name="myDefaultNotNull" value="androidx.annotation.NonNull" /> |
|||
<option name="myNullables"> |
|||
<value> |
|||
<list size="12"> |
|||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> |
|||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> |
|||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" /> |
|||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> |
|||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> |
|||
<item index="5" class="java.lang.String" itemvalue="androidx.annotation.Nullable" /> |
|||
<item index="6" class="java.lang.String" itemvalue="android.annotation.Nullable" /> |
|||
<item index="7" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNullable" /> |
|||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.Nullable" /> |
|||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableDecl" /> |
|||
<item index="10" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NullableType" /> |
|||
<item index="11" class="java.lang.String" itemvalue="com.android.annotations.Nullable" /> |
|||
</list> |
|||
</value> |
|||
</option> |
|||
<option name="myNotNulls"> |
|||
<value> |
|||
<list size="11"> |
|||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> |
|||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> |
|||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> |
|||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> |
|||
<item index="4" class="java.lang.String" itemvalue="androidx.annotation.NonNull" /> |
|||
<item index="5" class="java.lang.String" itemvalue="android.annotation.NonNull" /> |
|||
<item index="6" class="java.lang.String" itemvalue="androidx.annotation.RecentlyNonNull" /> |
|||
<item index="7" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.qual.NonNull" /> |
|||
<item index="8" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullDecl" /> |
|||
<item index="9" class="java.lang.String" itemvalue="org.checkerframework.checker.nullness.compatqual.NonNullType" /> |
|||
<item index="10" class="java.lang.String" itemvalue="com.android.annotations.NonNull" /> |
|||
</list> |
|||
</value> |
|||
</option> |
|||
</component> |
|||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK"> |
|||
<output url="file://$PROJECT_DIR$/build/classes" /> |
|||
</component> |
|||
<component name="ProjectType"> |
|||
<option name="id" value="Android" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="RenderSettings"> |
|||
<option name="showDecorations" value="true" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,12 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="RunConfigurationProducerService"> |
|||
<option name="ignoredProducers"> |
|||
<set> |
|||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> |
|||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> |
|||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> |
|||
</set> |
|||
</option> |
|||
</component> |
|||
</project> |
@ -0,0 +1,53 @@ |
|||
<component name="ProjectRunConfigurationManager"> |
|||
<configuration default="false" name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false"> |
|||
<module name="app" /> |
|||
<option name="DEPLOY" value="true" /> |
|||
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" /> |
|||
<option name="DEPLOY_AS_INSTANT" value="false" /> |
|||
<option name="ARTIFACT_NAME" value="" /> |
|||
<option name="PM_INSTALL_OPTIONS" value="" /> |
|||
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" /> |
|||
<option name="ACTIVITY_EXTRA_FLAGS" value="" /> |
|||
<option name="MODE" value="default_activity" /> |
|||
<option name="CLEAR_LOGCAT" value="false" /> |
|||
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" /> |
|||
<option name="SKIP_NOOP_APK_INSTALLATIONS" value="true" /> |
|||
<option name="FORCE_STOP_RUNNING_APP" value="true" /> |
|||
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" /> |
|||
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" /> |
|||
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" /> |
|||
<option name="DEBUGGER_TYPE" value="Auto" /> |
|||
<Auto> |
|||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> |
|||
<option name="SHOW_STATIC_VARS" value="true" /> |
|||
<option name="WORKING_DIR" value="" /> |
|||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> |
|||
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> |
|||
</Auto> |
|||
<Hybrid> |
|||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> |
|||
<option name="SHOW_STATIC_VARS" value="true" /> |
|||
<option name="WORKING_DIR" value="" /> |
|||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> |
|||
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> |
|||
</Hybrid> |
|||
<Java /> |
|||
<Native> |
|||
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" /> |
|||
<option name="SHOW_STATIC_VARS" value="true" /> |
|||
<option name="WORKING_DIR" value="" /> |
|||
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" /> |
|||
<option name="SHOW_OPTIMIZED_WARNING" value="true" /> |
|||
</Native> |
|||
<Profilers> |
|||
<option name="ADVANCED_PROFILING_ENABLED" value="false" /> |
|||
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" /> |
|||
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" /> |
|||
</Profilers> |
|||
<option name="DEEP_LINK" value="" /> |
|||
<option name="ACTIVITY_CLASS" value="awais.instagrabber.activities.Main" /> |
|||
<method v="2"> |
|||
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> |
|||
</method> |
|||
</configuration> |
|||
</component> |
@ -0,0 +1,6 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<project version="4"> |
|||
<component name="VcsDirectoryMappings"> |
|||
<mapping directory="" vcs="Git" /> |
|||
</component> |
|||
</project> |
@ -0,0 +1,193 @@ |
|||
|
|||
v15.9 |
|||
note: there will be no F-Droid updates from this version (v15.9) and onward, download updates from repo's Releases page. |
|||
|
|||
+ added user stories in Feed |
|||
+ added frame to currently showing slider item |
|||
+ removed tap to pause/resume from Post viewer, replaced with controller |
|||
+ fixed swipe not working when posts are opened from stories |
|||
+ fixed comments not showing for slider items |
|||
|
|||
v15.8 |
|||
+ added user's website in profile |
|||
+ fixed caption mentions length (@kernoeb) |
|||
+ fixed some translations (@kernoeb) |
|||
+ fixd feed captions merging with "... more" |
|||
|
|||
v15.4 |
|||
+ ADDED FRENCH AND SPANISH!!! |
|||
+-> Huge thanks to @kernoeb (Telegram) for French translation and @sguinetti (GitLab) for Spanish translation!! |
|||
|
|||
+ added custom post time format support! |
|||
+ fixed flickering after changing settings |
|||
+ fixed posts not showing after searching from a private profile |
|||
+ fixed stories and profile pictures not downloading in user folders even when option was enabled |
|||
+ fixed issues with feed, discover and post viewer |
|||
+ fixed search suggestions crashes |
|||
|
|||
v15.2 |
|||
+ fixed feed video not pausing when opened in post viewer |
|||
+ added 1 new profile picture view mode |
|||
+ added fields in LogCollector to better understand how things went wrong |
|||
+ better comments, story, feed, discover and suggestion fetchers |
|||
|
|||
v15.0 |
|||
+ added support for Instagram.com urls! (: |
|||
+ added "Downloaded" check on posts if they've already been downloaded (as per suggestion) |
|||
+ fixed highlights scrolling issues |
|||
+ fixed comments issues |
|||
+ fixed posts weren't showing after searching anything from a private account |
|||
+ fixed suggestions not showing sometimes |
|||
+ fixed Stories viewer swipe issues |
|||
+ fixed Import/Export dialog not showing on old phones |
|||
+ added user's name in suggestions search list |
|||
+ added comment likes in Comments viewer |
|||
|
|||
+ sending logs won't add empty logs to archive anymore! |
|||
+ fixed no new line in logs |
|||
+ handled Login WebView lifecycles |
|||
+ a better way of handling highlight swipes |
|||
+ removed useless code parts |
|||
|
|||
v14.5 |
|||
+ added changelog after update |
|||
+ added swipe support in both Discover/Explore and Feed pages |
|||
+ added Send Logs button in Settings to send logs when something doesn't work |
|||
+ added clickable user profile in Post Viewer |
|||
+ fixed weirdly collapsing toolbar when toolbar is shown at the top |
|||
|
|||
v14.0 |
|||
+ added theme selection support |
|||
+ added import/export settings, favorites and logins functionality (thanks to Airikr [@edgren] on Telegram) |
|||
+ added support for downloading slider items from User Feed page |
|||
+ added support for usernames and hashtags in user's biography/about text |
|||
+ added multiple selection in Discover/Explore page |
|||
+ added post date for feed items |
|||
+ added some more date formats |
|||
+ copyable feed item caption (long tap) |
|||
+ fixed late refresh indicator in Followers/Following comparison mode |
|||
+ changed feed item size to squares |
|||
+ fixed some caption text issues having mentions and hashtags |
|||
+ removed clipboard listener (stopped working after some changes) |
|||
+ added log collector for different crash scenarios |
|||
|
|||
v13.7 |
|||
+ fixed custom download folder selection issues |
|||
|
|||
v13.3 |
|||
+ added discover/explore page (only for logged in users) |
|||
+ added function to remove IPTC tracking data from downloaded pictures (thanks to Airikr [@edgren] on Telegram) |
|||
+ added multiple accounts support (quick access) and favorites (a suggestion from Saurabh on Telegram) |
|||
+ added custom download folder option, you can select where to download posts (a suggestion from Airikr) |
|||
+ added desktop mode toggle in Login activity (a suggestion from Eymen on Telegram) |
|||
+ added post date in post viewer (a suggestion from W on Telegram) |
|||
+ added post time format settings [ Settings > Post Time Settings ] |
|||
+ fixed some icons and layouts |
|||
+ removed color from slider items in feed |
|||
+ removed useless methods and properties |
|||
+ better way of handling multiple stories and posts (sliders) in post viewer |
|||
+ tried to make notifications grouped together.. hope they work |
|||
+ some other fixes and additions which i probably forgot, cause i'm a human ffs |
|||
|
|||
v13.0 |
|||
+ fixed crash when searching hashtags |
|||
+ added lazy loading for hashtags |
|||
+ added Show Feed option in Settings (feed may crash app on some phones) |
|||
+ added null check in Download async |
|||
+ better/adapatable icon |
|||
+ fixed sheet dialog themes |
|||
|
|||
v12.7 |
|||
+ (probably) fixed inflating issue in some devices |
|||
|
|||
v12.5 |
|||
+ some small performance improvements |
|||
|
|||
v12.0 |
|||
+ added feed!! (only for logged in users) |
|||
+ fixed multiple hashtags with no spaces between them |
|||
+ stopped activity from recreating when nothing changed |
|||
+ changed highlights to RecyclerView instead of HorizontalScrollView |
|||
+ changed some numbers and precisions cause she left me on read |
|||
|
|||
v11.0 |
|||
+ added crash reporting library |
|||
+ added mute/unmute for session |
|||
+ better profile picture viewer + profile picture info |
|||
+ better swipe gesture |
|||
+ fixed mention and hashtag issues |
|||
|
|||
v10.0 |
|||
NOTE: YOU MAY NEED TO LOGIN TO VIEW PROFILES CAUSE OF INSTAGRAM CHANGES. |
|||
+ added direct download multiple posts dialog |
|||
+ fixed notification problems |
|||
+ fixed some direct download problems |
|||
+ fixed batch download and username folder not creating in some activities |
|||
+ fixed update checker |
|||
|
|||
v9.0 |
|||
+ added search in comments viewer |
|||
+ added settings to auto or lazy load posts |
|||
+ added user & hashtag stack when back pressed |
|||
+ added loading icon when loading posts |
|||
+ profile info bar sticks to top |
|||
+ fixed highlights and profile picture size in portrait and landscape mode |
|||
+ fixed posts loading from other user when restarting app |
|||
+ fixed posts size changing when scrolled |
|||
+ users & hashtag search shows only users or hashtag when first char is @ or # |
|||
+ scrolls to top when back pressed |
|||
|
|||
v8.0 |
|||
+ added pull-to-refresh layout in main posts, followers/following viewer |
|||
+ added search in followers/following viewer |
|||
+ added video views in post viewer |
|||
+ added animation when showing highlights (if available) |
|||
+ fixed long usernames not animating (marquee) |
|||
+ fixed padding and size in portrait and landscape modes |
|||
+ fixed accounts showing personal followers/following when account has 0 followers/following |
|||
+ fixed double ripple when tapped on profile picture |
|||
+ smaller story border around profile picture |
|||
|
|||
v7.0 |
|||
+ added comments viewer!! |
|||
+ added highest quality post fetcher |
|||
+ added loading indicator where it was missing before |
|||
+ fixed highlight name alignment |
|||
+ fixed swiping on posts opened via Share or link |
|||
|
|||
v6.0 |
|||
+ added story highlights!! (issue #5) |
|||
+ added button to view posts posted in stories |
|||
+ added verified badge for accounts |
|||
+ fixed posts & story swiping issues |
|||
+ fixed slow loading and stuff |
|||
+ fixed different margins and sizes |
|||
+ fixed activity not recreating after settings dialogs closed |
|||
|
|||
v5.0 |
|||
+ added followers / following checker |
|||
+ added search view suggestions for usernames & hashtags |
|||
+ added sliding profile container |
|||
+ fixed batch download permission issue (issue #4) |
|||
+ fixed some small screen panning issues |
|||
+ fixed search view width |
|||
+ fixed update checker |
|||
|
|||
v4.0 |
|||
+ fixed Login and Visit project page button codes. |
|||
|
|||
v3.0 |
|||
+ fixed posts merged from different accounts when searched while posts are loading! |
|||
+ view stories (only if you're logged in) |
|||
+ directly download posts |
|||
+ choose between two pfp (profile picture) viewer methods |
|||
+ fixed search box not showing up when toolbar is at bottom |
|||
+ automatically checks for updates |
|||
|
|||
v2.0 |
|||
+ fixed Login crashes |
|||
|
|||
v1.0 |
|||
+ first ever changelog |
|||
+ basic stuff like downloading profile pics, posts and copying captions and bio. |
|||
+ batch download posts |
@ -0,0 +1,674 @@ |
|||
GNU GENERAL PUBLIC LICENSE |
|||
Version 3, 29 June 2007 |
|||
|
|||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> |
|||
Everyone is permitted to copy and distribute verbatim copies |
|||
of this license document, but changing it is not allowed. |
|||
|
|||
Preamble |
|||
|
|||
The GNU General Public License is a free, copyleft license for |
|||
software and other kinds of works. |
|||
|
|||
The licenses for most software and other practical works are designed |
|||
to take away your freedom to share and change the works. By contrast, |
|||
the GNU General Public License is intended to guarantee your freedom to |
|||
share and change all versions of a program--to make sure it remains free |
|||
software for all its users. We, the Free Software Foundation, use the |
|||
GNU General Public License for most of our software; it applies also to |
|||
any other work released this way by its authors. You can apply it to |
|||
your programs, too. |
|||
|
|||
When we speak of free software, we are referring to freedom, not |
|||
price. Our General Public Licenses are designed to make sure that you |
|||
have the freedom to distribute copies of free software (and charge for |
|||
them if you wish), that you receive source code or can get it if you |
|||
want it, that you can change the software or use pieces of it in new |
|||
free programs, and that you know you can do these things. |
|||
|
|||
To protect your rights, we need to prevent others from denying you |
|||
these rights or asking you to surrender the rights. Therefore, you have |
|||
certain responsibilities if you distribute copies of the software, or if |
|||
you modify it: responsibilities to respect the freedom of others. |
|||
|
|||
For example, if you distribute copies of such a program, whether |
|||
gratis or for a fee, you must pass on to the recipients the same |
|||
freedoms that you received. You must make sure that they, too, receive |
|||
or can get the source code. And you must show them these terms so they |
|||
know their rights. |
|||
|
|||
Developers that use the GNU GPL protect your rights with two steps: |
|||
(1) assert copyright on the software, and (2) offer you this License |
|||
giving you legal permission to copy, distribute and/or modify it. |
|||
|
|||
For the developers' and authors' protection, the GPL clearly explains |
|||
that there is no warranty for this free software. For both users' and |
|||
authors' sake, the GPL requires that modified versions be marked as |
|||
changed, so that their problems will not be attributed erroneously to |
|||
authors of previous versions. |
|||
|
|||
Some devices are designed to deny users access to install or run |
|||
modified versions of the software inside them, although the manufacturer |
|||
can do so. This is fundamentally incompatible with the aim of |
|||
protecting users' freedom to change the software. The systematic |
|||
pattern of such abuse occurs in the area of products for individuals to |
|||
use, which is precisely where it is most unacceptable. Therefore, we |
|||
have designed this version of the GPL to prohibit the practice for those |
|||
products. If such problems arise substantially in other domains, we |
|||
stand ready to extend this provision to those domains in future versions |
|||
of the GPL, as needed to protect the freedom of users. |
|||
|
|||
Finally, every program is threatened constantly by software patents. |
|||
States should not allow patents to restrict development and use of |
|||
software on general-purpose computers, but in those that do, we wish to |
|||
avoid the special danger that patents applied to a free program could |
|||
make it effectively proprietary. To prevent this, the GPL assures that |
|||
patents cannot be used to render the program non-free. |
|||
|
|||
The precise terms and conditions for copying, distribution and |
|||
modification follow. |
|||
|
|||
TERMS AND CONDITIONS |
|||
|
|||
0. Definitions. |
|||
|
|||
"This License" refers to version 3 of the GNU General Public License. |
|||
|
|||
"Copyright" also means copyright-like laws that apply to other kinds of |
|||
works, such as semiconductor masks. |
|||
|
|||
"The Program" refers to any copyrightable work licensed under this |
|||
License. Each licensee is addressed as "you". "Licensees" and |
|||
"recipients" may be individuals or organizations. |
|||
|
|||
To "modify" a work means to copy from or adapt all or part of the work |
|||
in a fashion requiring copyright permission, other than the making of an |
|||
exact copy. The resulting work is called a "modified version" of the |
|||
earlier work or a work "based on" the earlier work. |
|||
|
|||
A "covered work" means either the unmodified Program or a work based |
|||
on the Program. |
|||
|
|||
To "propagate" a work means to do anything with it that, without |
|||
permission, would make you directly or secondarily liable for |
|||
infringement under applicable copyright law, except executing it on a |
|||
computer or modifying a private copy. Propagation includes copying, |
|||
distribution (with or without modification), making available to the |
|||
public, and in some countries other activities as well. |
|||
|
|||
To "convey" a work means any kind of propagation that enables other |
|||
parties to make or receive copies. Mere interaction with a user through |
|||
a computer network, with no transfer of a copy, is not conveying. |
|||
|
|||
An interactive user interface displays "Appropriate Legal Notices" |
|||
to the extent that it includes a convenient and prominently visible |
|||
feature that (1) displays an appropriate copyright notice, and (2) |
|||
tells the user that there is no warranty for the work (except to the |
|||
extent that warranties are provided), that licensees may convey the |
|||
work under this License, and how to view a copy of this License. If |
|||
the interface presents a list of user commands or options, such as a |
|||
menu, a prominent item in the list meets this criterion. |
|||
|
|||
1. Source Code. |
|||
|
|||
The "source code" for a work means the preferred form of the work |
|||
for making modifications to it. "Object code" means any non-source |
|||
form of a work. |
|||
|
|||
A "Standard Interface" means an interface that either is an official |
|||
standard defined by a recognized standards body, or, in the case of |
|||
interfaces specified for a particular programming language, one that |
|||
is widely used among developers working in that language. |
|||
|
|||
The "System Libraries" of an executable work include anything, other |
|||
than the work as a whole, that (a) is included in the normal form of |
|||
packaging a Major Component, but which is not part of that Major |
|||
Component, and (b) serves only to enable use of the work with that |
|||
Major Component, or to implement a Standard Interface for which an |
|||
implementation is available to the public in source code form. A |
|||
"Major Component", in this context, means a major essential component |
|||
(kernel, window system, and so on) of the specific operating system |
|||
(if any) on which the executable work runs, or a compiler used to |
|||
produce the work, or an object code interpreter used to run it. |
|||
|
|||
The "Corresponding Source" for a work in object code form means all |
|||
the source code needed to generate, install, and (for an executable |
|||
work) run the object code and to modify the work, including scripts to |
|||
control those activities. However, it does not include the work's |
|||
System Libraries, or general-purpose tools or generally available free |
|||
programs which are used unmodified in performing those activities but |
|||
which are not part of the work. For example, Corresponding Source |
|||
includes interface definition files associated with source files for |
|||
the work, and the source code for shared libraries and dynamically |
|||
linked subprograms that the work is specifically designed to require, |
|||
such as by intimate data communication or control flow between those |
|||
subprograms and other parts of the work. |
|||
|
|||
The Corresponding Source need not include anything that users |
|||
can regenerate automatically from other parts of the Corresponding |
|||
Source. |
|||
|
|||
The Corresponding Source for a work in source code form is that |
|||
same work. |
|||
|
|||
2. Basic Permissions. |
|||
|
|||
All rights granted under this License are granted for the term of |
|||
copyright on the Program, and are irrevocable provided the stated |
|||
conditions are met. This License explicitly affirms your unlimited |
|||
permission to run the unmodified Program. The output from running a |
|||
covered work is covered by this License only if the output, given its |
|||
content, constitutes a covered work. This License acknowledges your |
|||
rights of fair use or other equivalent, as provided by copyright law. |
|||
|
|||
You may make, run and propagate covered works that you do not |
|||
convey, without conditions so long as your license otherwise remains |
|||
in force. You may convey covered works to others for the sole purpose |
|||
of having them make modifications exclusively for you, or provide you |
|||
with facilities for running those works, provided that you comply with |
|||
the terms of this License in conveying all material for which you do |
|||
not control copyright. Those thus making or running the covered works |
|||
for you must do so exclusively on your behalf, under your direction |
|||
and control, on terms that prohibit them from making any copies of |
|||
your copyrighted material outside their relationship with you. |
|||
|
|||
Conveying under any other circumstances is permitted solely under |
|||
the conditions stated below. Sublicensing is not allowed; section 10 |
|||
makes it unnecessary. |
|||
|
|||
3. Protecting Users' Legal Rights From Anti-Circumvention Law. |
|||
|
|||
No covered work shall be deemed part of an effective technological |
|||
measure under any applicable law fulfilling obligations under article |
|||
11 of the WIPO copyright treaty adopted on 20 December 1996, or |
|||
similar laws prohibiting or restricting circumvention of such |
|||
measures. |
|||
|
|||
When you convey a covered work, you waive any legal power to forbid |
|||
circumvention of technological measures to the extent such circumvention |
|||
is effected by exercising rights under this License with respect to |
|||
the covered work, and you disclaim any intention to limit operation or |
|||
modification of the work as a means of enforcing, against the work's |
|||
users, your or third parties' legal rights to forbid circumvention of |
|||
technological measures. |
|||
|
|||
4. Conveying Verbatim Copies. |
|||
|
|||
You may convey verbatim copies of the Program's source code as you |
|||
receive it, in any medium, provided that you conspicuously and |
|||
appropriately publish on each copy an appropriate copyright notice; |
|||
keep intact all notices stating that this License and any |
|||
non-permissive terms added in accord with section 7 apply to the code; |
|||
keep intact all notices of the absence of any warranty; and give all |
|||
recipients a copy of this License along with the Program. |
|||
|
|||
You may charge any price or no price for each copy that you convey, |
|||
and you may offer support or warranty protection for a fee. |
|||
|
|||
5. Conveying Modified Source Versions. |
|||
|
|||
You may convey a work based on the Program, or the modifications to |
|||
produce it from the Program, in the form of source code under the |
|||
terms of section 4, provided that you also meet all of these conditions: |
|||
|
|||
a) The work must carry prominent notices stating that you modified |
|||
it, and giving a relevant date. |
|||
|
|||
b) The work must carry prominent notices stating that it is |
|||
released under this License and any conditions added under section |
|||
7. This requirement modifies the requirement in section 4 to |
|||
"keep intact all notices". |
|||
|
|||
c) You must license the entire work, as a whole, under this |
|||
License to anyone who comes into possession of a copy. This |
|||
License will therefore apply, along with any applicable section 7 |
|||
additional terms, to the whole of the work, and all its parts, |
|||
regardless of how they are packaged. This License gives no |
|||
permission to license the work in any other way, but it does not |
|||
invalidate such permission if you have separately received it. |
|||
|
|||
d) If the work has interactive user interfaces, each must display |
|||
Appropriate Legal Notices; however, if the Program has interactive |
|||
interfaces that do not display Appropriate Legal Notices, your |
|||
work need not make them do so. |
|||
|
|||
A compilation of a covered work with other separate and independent |
|||
works, which are not by their nature extensions of the covered work, |
|||
and which are not combined with it such as to form a larger program, |
|||
in or on a volume of a storage or distribution medium, is called an |
|||
"aggregate" if the compilation and its resulting copyright are not |
|||
used to limit the access or legal rights of the compilation's users |
|||
beyond what the individual works permit. Inclusion of a covered work |
|||
in an aggregate does not cause this License to apply to the other |
|||
parts of the aggregate. |
|||
|
|||
6. Conveying Non-Source Forms. |
|||
|
|||
You may convey a covered work in object code form under the terms |
|||
of sections 4 and 5, provided that you also convey the |
|||
machine-readable Corresponding Source under the terms of this License, |
|||
in one of these ways: |
|||
|
|||
a) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by the |
|||
Corresponding Source fixed on a durable physical medium |
|||
customarily used for software interchange. |
|||
|
|||
b) Convey the object code in, or embodied in, a physical product |
|||
(including a physical distribution medium), accompanied by a |
|||
written offer, valid for at least three years and valid for as |
|||
long as you offer spare parts or customer support for that product |
|||
model, to give anyone who possesses the object code either (1) a |
|||
copy of the Corresponding Source for all the software in the |
|||
product that is covered by this License, on a durable physical |
|||
medium customarily used for software interchange, for a price no |
|||
more than your reasonable cost of physically performing this |
|||
conveying of source, or (2) access to copy the |
|||
Corresponding Source from a network server at no charge. |
|||
|
|||
c) Convey individual copies of the object code with a copy of the |
|||
written offer to provide the Corresponding Source. This |
|||
alternative is allowed only occasionally and noncommercially, and |
|||
only if you received the object code with such an offer, in accord |
|||
with subsection 6b. |
|||
|
|||
d) Convey the object code by offering access from a designated |
|||
place (gratis or for a charge), and offer equivalent access to the |
|||
Corresponding Source in the same way through the same place at no |
|||
further charge. You need not require recipients to copy the |
|||
Corresponding Source along with the object code. If the place to |
|||
copy the object code is a network server, the Corresponding Source |
|||
may be on a different server (operated by you or a third party) |
|||
that supports equivalent copying facilities, provided you maintain |
|||
clear directions next to the object code saying where to find the |
|||
Corresponding Source. Regardless of what server hosts the |
|||
Corresponding Source, you remain obligated to ensure that it is |
|||
available for as long as needed to satisfy these requirements. |
|||
|
|||
e) Convey the object code using peer-to-peer transmission, provided |
|||
you inform other peers where the object code and Corresponding |
|||
Source of the work are being offered to the general public at no |
|||
charge under subsection 6d. |
|||
|
|||
A separable portion of the object code, whose source code is excluded |
|||
from the Corresponding Source as a System Library, need not be |
|||
included in conveying the object code work. |
|||
|
|||
A "User Product" is either (1) a "consumer product", which means any |
|||
tangible personal property which is normally used for personal, family, |
|||
or household purposes, or (2) anything designed or sold for incorporation |
|||
into a dwelling. In determining whether a product is a consumer product, |
|||
doubtful cases shall be resolved in favor of coverage. For a particular |
|||
product received by a particular user, "normally used" refers to a |
|||
typical or common use of that class of product, regardless of the status |
|||
of the particular user or of the way in which the particular user |
|||
actually uses, or expects or is expected to use, the product. A product |
|||
is a consumer product regardless of whether the product has substantial |
|||
commercial, industrial or non-consumer uses, unless such uses represent |
|||
the only significant mode of use of the product. |
|||
|
|||
"Installation Information" for a User Product means any methods, |
|||
procedures, authorization keys, or other information required to install |
|||
and execute modified versions of a covered work in that User Product from |
|||
a modified version of its Corresponding Source. The information must |
|||
suffice to ensure that the continued functioning of the modified object |
|||
code is in no case prevented or interfered with solely because |
|||
modification has been made. |
|||
|
|||
If you convey an object code work under this section in, or with, or |
|||
specifically for use in, a User Product, and the conveying occurs as |
|||
part of a transaction in which the right of possession and use of the |
|||
User Product is transferred to the recipient in perpetuity or for a |
|||
fixed term (regardless of how the transaction is characterized), the |
|||
Corresponding Source conveyed under this section must be accompanied |
|||
by the Installation Information. But this requirement does not apply |
|||
if neither you nor any third party retains the ability to install |
|||
modified object code on the User Product (for example, the work has |
|||
been installed in ROM). |
|||
|
|||
The requirement to provide Installation Information does not include a |
|||
requirement to continue to provide support service, warranty, or updates |
|||
for a work that has been modified or installed by the recipient, or for |
|||
the User Product in which it has been modified or installed. Access to a |
|||
network may be denied when the modification itself materially and |
|||
adversely affects the operation of the network or violates the rules and |
|||
protocols for communication across the network. |
|||
|
|||
Corresponding Source conveyed, and Installation Information provided, |
|||
in accord with this section must be in a format that is publicly |
|||
documented (and with an implementation available to the public in |
|||
source code form), and must require no special password or key for |
|||
unpacking, reading or copying. |
|||
|
|||
7. Additional Terms. |
|||
|
|||
"Additional permissions" are terms that supplement the terms of this |
|||
License by making exceptions from one or more of its conditions. |
|||
Additional permissions that are applicable to the entire Program shall |
|||
be treated as though they were included in this License, to the extent |
|||
that they are valid under applicable law. If additional permissions |
|||
apply only to part of the Program, that part may be used separately |
|||
under those permissions, but the entire Program remains governed by |
|||
this License without regard to the additional permissions. |
|||
|
|||
When you convey a copy of a covered work, you may at your option |
|||
remove any additional permissions from that copy, or from any part of |
|||
it. (Additional permissions may be written to require their own |
|||
removal in certain cases when you modify the work.) You may place |
|||
additional permissions on material, added by you to a covered work, |
|||
for which you have or can give appropriate copyright permission. |
|||
|
|||
Notwithstanding any other provision of this License, for material you |
|||
add to a covered work, you may (if authorized by the copyright holders of |
|||
that material) supplement the terms of this License with terms: |
|||
|
|||
a) Disclaiming warranty or limiting liability differently from the |
|||
terms of sections 15 and 16 of this License; or |
|||
|
|||
b) Requiring preservation of specified reasonable legal notices or |
|||
author attributions in that material or in the Appropriate Legal |
|||
Notices displayed by works containing it; or |
|||
|
|||
c) Prohibiting misrepresentation of the origin of that material, or |
|||
requiring that modified versions of such material be marked in |
|||
reasonable ways as different from the original version; or |
|||
|
|||
d) Limiting the use for publicity purposes of names of licensors or |
|||
authors of the material; or |
|||
|
|||
e) Declining to grant rights under trademark law for use of some |
|||
trade names, trademarks, or service marks; or |
|||
|
|||
f) Requiring indemnification of licensors and authors of that |
|||
material by anyone who conveys the material (or modified versions of |
|||
it) with contractual assumptions of liability to the recipient, for |
|||
any liability that these contractual assumptions directly impose on |
|||
those licensors and authors. |
|||
|
|||
All other non-permissive additional terms are considered "further |
|||
restrictions" within the meaning of section 10. If the Program as you |
|||
received it, or any part of it, contains a notice stating that it is |
|||
governed by this License along with a term that is a further |
|||
restriction, you may remove that term. If a license document contains |
|||
a further restriction but permits relicensing or conveying under this |
|||
License, you may add to a covered work material governed by the terms |
|||
of that license document, provided that the further restriction does |
|||
not survive such relicensing or conveying. |
|||
|
|||
If you add terms to a covered work in accord with this section, you |
|||
must place, in the relevant source files, a statement of the |
|||
additional terms that apply to those files, or a notice indicating |
|||
where to find the applicable terms. |
|||
|
|||
Additional terms, permissive or non-permissive, may be stated in the |
|||
form of a separately written license, or stated as exceptions; |
|||
the above requirements apply either way. |
|||
|
|||
8. Termination. |
|||
|
|||
You may not propagate or modify a covered work except as expressly |
|||
provided under this License. Any attempt otherwise to propagate or |
|||
modify it is void, and will automatically terminate your rights under |
|||
this License (including any patent licenses granted under the third |
|||
paragraph of section 11). |
|||
|
|||
However, if you cease all violation of this License, then your |
|||
license from a particular copyright holder is reinstated (a) |
|||
provisionally, unless and until the copyright holder explicitly and |
|||
finally terminates your license, and (b) permanently, if the copyright |
|||
holder fails to notify you of the violation by some reasonable means |
|||
prior to 60 days after the cessation. |
|||
|
|||
Moreover, your license from a particular copyright holder is |
|||
reinstated permanently if the copyright holder notifies you of the |
|||
violation by some reasonable means, this is the first time you have |
|||
received notice of violation of this License (for any work) from that |
|||
copyright holder, and you cure the violation prior to 30 days after |
|||
your receipt of the notice. |
|||
|
|||
Termination of your rights under this section does not terminate the |
|||
licenses of parties who have received copies or rights from you under |
|||
this License. If your rights have been terminated and not permanently |
|||
reinstated, you do not qualify to receive new licenses for the same |
|||
material under section 10. |
|||
|
|||
9. Acceptance Not Required for Having Copies. |
|||
|
|||
You are not required to accept this License in order to receive or |
|||
run a copy of the Program. Ancillary propagation of a covered work |
|||
occurring solely as a consequence of using peer-to-peer transmission |
|||
to receive a copy likewise does not require acceptance. However, |
|||
nothing other than this License grants you permission to propagate or |
|||
modify any covered work. These actions infringe copyright if you do |
|||
not accept this License. Therefore, by modifying or propagating a |
|||
covered work, you indicate your acceptance of this License to do so. |
|||
|
|||
10. Automatic Licensing of Downstream Recipients. |
|||
|
|||
Each time you convey a covered work, the recipient automatically |
|||
receives a license from the original licensors, to run, modify and |
|||
propagate that work, subject to this License. You are not responsible |
|||
for enforcing compliance by third parties with this License. |
|||
|
|||
An "entity transaction" is a transaction transferring control of an |
|||
organization, or substantially all assets of one, or subdividing an |
|||
organization, or merging organizations. If propagation of a covered |
|||
work results from an entity transaction, each party to that |
|||
transaction who receives a copy of the work also receives whatever |
|||
licenses to the work the party's predecessor in interest had or could |
|||
give under the previous paragraph, plus a right to possession of the |
|||
Corresponding Source of the work from the predecessor in interest, if |
|||
the predecessor has it or can get it with reasonable efforts. |
|||
|
|||
You may not impose any further restrictions on the exercise of the |
|||
rights granted or affirmed under this License. For example, you may |
|||
not impose a license fee, royalty, or other charge for exercise of |
|||
rights granted under this License, and you may not initiate litigation |
|||
(including a cross-claim or counterclaim in a lawsuit) alleging that |
|||
any patent claim is infringed by making, using, selling, offering for |
|||
sale, or importing the Program or any portion of it. |
|||
|
|||
11. Patents. |
|||
|
|||
A "contributor" is a copyright holder who authorizes use under this |
|||
License of the Program or a work on which the Program is based. The |
|||
work thus licensed is called the contributor's "contributor version". |
|||
|
|||
A contributor's "essential patent claims" are all patent claims |
|||
owned or controlled by the contributor, whether already acquired or |
|||
hereafter acquired, that would be infringed by some manner, permitted |
|||
by this License, of making, using, or selling its contributor version, |
|||
but do not include claims that would be infringed only as a |
|||
consequence of further modification of the contributor version. For |
|||
purposes of this definition, "control" includes the right to grant |
|||
patent sublicenses in a manner consistent with the requirements of |
|||
this License. |
|||
|
|||
Each contributor grants you a non-exclusive, worldwide, royalty-free |
|||
patent license under the contributor's essential patent claims, to |
|||
make, use, sell, offer for sale, import and otherwise run, modify and |
|||
propagate the contents of its contributor version. |
|||
|
|||
In the following three paragraphs, a "patent license" is any express |
|||
agreement or commitment, however denominated, not to enforce a patent |
|||
(such as an express permission to practice a patent or covenant not to |
|||
sue for patent infringement). To "grant" such a patent license to a |
|||
party means to make such an agreement or commitment not to enforce a |
|||
patent against the party. |
|||
|
|||
If you convey a covered work, knowingly relying on a patent license, |
|||
and the Corresponding Source of the work is not available for anyone |
|||
to copy, free of charge and under the terms of this License, through a |
|||
publicly available network server or other readily accessible means, |
|||
then you must either (1) cause the Corresponding Source to be so |
|||
available, or (2) arrange to deprive yourself of the benefit of the |
|||
patent license for this particular work, or (3) arrange, in a manner |
|||
consistent with the requirements of this License, to extend the patent |
|||
license to downstream recipients. "Knowingly relying" means you have |
|||
actual knowledge that, but for the patent license, your conveying the |
|||
covered work in a country, or your recipient's use of the covered work |
|||
in a country, would infringe one or more identifiable patents in that |
|||
country that you have reason to believe are valid. |
|||
|
|||
If, pursuant to or in connection with a single transaction or |
|||
arrangement, you convey, or propagate by procuring conveyance of, a |
|||
covered work, and grant a patent license to some of the parties |
|||
receiving the covered work authorizing them to use, propagate, modify |
|||
or convey a specific copy of the covered work, then the patent license |
|||
you grant is automatically extended to all recipients of the covered |
|||
work and works based on it. |
|||
|
|||
A patent license is "discriminatory" if it does not include within |
|||
the scope of its coverage, prohibits the exercise of, or is |
|||
conditioned on the non-exercise of one or more of the rights that are |
|||
specifically granted under this License. You may not convey a covered |
|||
work if you are a party to an arrangement with a third party that is |
|||
in the business of distributing software, under which you make payment |
|||
to the third party based on the extent of your activity of conveying |
|||
the work, and under which the third party grants, to any of the |
|||
parties who would receive the covered work from you, a discriminatory |
|||
patent license (a) in connection with copies of the covered work |
|||
conveyed by you (or copies made from those copies), or (b) primarily |
|||
for and in connection with specific products or compilations that |
|||
contain the covered work, unless you entered into that arrangement, |
|||
or that patent license was granted, prior to 28 March 2007. |
|||
|
|||
Nothing in this License shall be construed as excluding or limiting |
|||
any implied license or other defenses to infringement that may |
|||
otherwise be available to you under applicable patent law. |
|||
|
|||
12. No Surrender of Others' Freedom. |
|||
|
|||
If conditions are imposed on you (whether by court order, agreement or |
|||
otherwise) that contradict the conditions of this License, they do not |
|||
excuse you from the conditions of this License. If you cannot convey a |
|||
covered work so as to satisfy simultaneously your obligations under this |
|||
License and any other pertinent obligations, then as a consequence you may |
|||
not convey it at all. For example, if you agree to terms that obligate you |
|||
to collect a royalty for further conveying from those to whom you convey |
|||
the Program, the only way you could satisfy both those terms and this |
|||
License would be to refrain entirely from conveying the Program. |
|||
|
|||
13. Use with the GNU Affero General Public License. |
|||
|
|||
Notwithstanding any other provision of this License, you have |
|||
permission to link or combine any covered work with a work licensed |
|||
under version 3 of the GNU Affero General Public License into a single |
|||
combined work, and to convey the resulting work. The terms of this |
|||
License will continue to apply to the part which is the covered work, |
|||
but the special requirements of the GNU Affero General Public License, |
|||
section 13, concerning interaction through a network will apply to the |
|||
combination as such. |
|||
|
|||
14. Revised Versions of this License. |
|||
|
|||
The Free Software Foundation may publish revised and/or new versions of |
|||
the GNU General Public License from time to time. Such new versions will |
|||
be similar in spirit to the present version, but may differ in detail to |
|||
address new problems or concerns. |
|||
|
|||
Each version is given a distinguishing version number. If the |
|||
Program specifies that a certain numbered version of the GNU General |
|||
Public License "or any later version" applies to it, you have the |
|||
option of following the terms and conditions either of that numbered |
|||
version or of any later version published by the Free Software |
|||
Foundation. If the Program does not specify a version number of the |
|||
GNU General Public License, you may choose any version ever published |
|||
by the Free Software Foundation. |
|||
|
|||
If the Program specifies that a proxy can decide which future |
|||
versions of the GNU General Public License can be used, that proxy's |
|||
public statement of acceptance of a version permanently authorizes you |
|||
to choose that version for the Program. |
|||
|
|||
Later license versions may give you additional or different |
|||
permissions. However, no additional obligations are imposed on any |
|||
author or copyright holder as a result of your choosing to follow a |
|||
later version. |
|||
|
|||
15. Disclaimer of Warranty. |
|||
|
|||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY |
|||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT |
|||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY |
|||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, |
|||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM |
|||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF |
|||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION. |
|||
|
|||
16. Limitation of Liability. |
|||
|
|||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING |
|||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS |
|||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY |
|||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE |
|||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF |
|||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD |
|||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), |
|||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF |
|||
SUCH DAMAGES. |
|||
|
|||
17. Interpretation of Sections 15 and 16. |
|||
|
|||
If the disclaimer of warranty and limitation of liability provided |
|||
above cannot be given local legal effect according to their terms, |
|||
reviewing courts shall apply local law that most closely approximates |
|||
an absolute waiver of all civil liability in connection with the |
|||
Program, unless a warranty or assumption of liability accompanies a |
|||
copy of the Program in return for a fee. |
|||
|
|||
END OF TERMS AND CONDITIONS |
|||
|
|||
How to Apply These Terms to Your New Programs |
|||
|
|||
If you develop a new program, and you want it to be of the greatest |
|||
possible use to the public, the best way to achieve this is to make it |
|||
free software which everyone can redistribute and change under these terms. |
|||
|
|||
To do so, attach the following notices to the program. It is safest |
|||
to attach them to the start of each source file to most effectively |
|||
state the exclusion of warranty; and each file should have at least |
|||
the "copyright" line and a pointer to where the full notice is found. |
|||
|
|||
InstaGrabber |
|||
Copyright (C) 2019 AWAiS |
|||
|
|||
This program is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
This program is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
Also add information on how to contact you by electronic and paper mail. |
|||
|
|||
If the program does terminal interaction, make it output a short |
|||
notice like this when it starts in an interactive mode: |
|||
|
|||
InstaGrabber Copyright (C) 2019 AWAiS |
|||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. |
|||
This is free software, and you are welcome to redistribute it |
|||
under certain conditions; type `show c' for details. |
|||
|
|||
The hypothetical commands `show w' and `show c' should show the appropriate |
|||
parts of the General Public License. Of course, your program's commands |
|||
might be different; for a GUI interface, you would use an "about box". |
|||
|
|||
You should also get your employer (if you work as a programmer) or school, |
|||
if any, to sign a "copyright disclaimer" for the program, if necessary. |
|||
For more information on this, and how to apply and follow the GNU GPL, see |
|||
<http://www.gnu.org/licenses/>. |
|||
|
|||
The GNU General Public License does not permit incorporating your program |
|||
into proprietary programs. If your program is a subroutine library, you |
|||
may consider it more useful to permit linking proprietary applications with |
|||
the library. If this is what you want to do, use the GNU Lesser General |
|||
Public License instead of this License. But first, please read |
|||
<http://www.gnu.org/philosophy/why-not-lgpl.html>. |
@ -0,0 +1,5 @@ |
|||
![InstaGrabber](./app/src/main/res/mipmap-hdpi/ic_launcher.png "InstaGrabber") InstaGrabber |
|||
|
|||
Revived Version by Austin. |
|||
|
|||
Coming soon™ |
@ -0,0 +1 @@ |
|||
/build |
@ -0,0 +1,50 @@ |
|||
apply plugin: 'com.android.application' |
|||
|
|||
android { |
|||
compileSdkVersion 29 |
|||
|
|||
defaultConfig { |
|||
applicationId 'awais.instagrabber' |
|||
|
|||
minSdkVersion 16 |
|||
targetSdkVersion 29 |
|||
|
|||
versionCode 27 |
|||
versionName '16.5-a1' |
|||
|
|||
multiDexEnabled true |
|||
|
|||
vectorDrawables.useSupportLibrary = true |
|||
vectorDrawables.generatedDensities = [] |
|||
} |
|||
|
|||
compileOptions { |
|||
targetCompatibility JavaVersion.VERSION_1_8 |
|||
sourceCompatibility JavaVersion.VERSION_1_8 |
|||
} |
|||
|
|||
buildFeatures { viewBinding true } |
|||
|
|||
aaptOptions { additionalParameters '--no-version-vectors' } |
|||
|
|||
buildTypes { |
|||
release { |
|||
minifyEnabled false |
|||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' |
|||
} |
|||
} |
|||
} |
|||
|
|||
dependencies { |
|||
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true } |
|||
implementation('androidx.recyclerview:recyclerview:1.2.0-alpha03@aar') { transitive true } |
|||
implementation('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true } |
|||
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { transitive true } |
|||
|
|||
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' |
|||
|
|||
implementation('org.jsoup:jsoup:1.13.1') { transitive true } |
|||
implementation('com.github.bumptech.glide:glide:4.11.0') { transitive true } |
|||
implementation('com.github.chrisbanes:PhotoView:v2.0.0@aar') { transitive true } |
|||
implementation('com.google.android.exoplayer:exoplayer:2.11.1@aar') { transitive true } |
|||
} |
@ -0,0 +1,29 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<lint> |
|||
<issue id="IconColors"> |
|||
<ignore path="src/main/res/drawable-night/download.png" /> |
|||
</issue> |
|||
<issue id="IconLocation"> |
|||
<ignore path="src/main/res/drawable/adm.png" /> |
|||
<ignore path="src/main/res/drawable/check.png" /> |
|||
<ignore path="src/main/res/drawable/collapse.png" /> |
|||
<ignore path="src/main/res/drawable/comments.png" /> |
|||
<ignore path="src/main/res/drawable/download.png" /> |
|||
<ignore path="src/main/res/drawable/expand.png" /> |
|||
<ignore path="src/main/res/drawable/lock.png" /> |
|||
<ignore path="src/main/res/drawable/lw.png" /> |
|||
<ignore path="src/main/res/drawable/ms.png" /> |
|||
<ignore path="src/main/res/drawable/mute.png" /> |
|||
<ignore path="src/main/res/drawable/qdb.png" /> |
|||
<ignore path="src/main/res/drawable/rev.png" /> |
|||
<ignore path="src/main/res/drawable/revl.png" /> |
|||
<ignore path="src/main/res/drawable/settings.png" /> |
|||
<ignore path="src/main/res/drawable/slider.png" /> |
|||
<ignore path="src/main/res/drawable/tesv.png" /> |
|||
<ignore path="src/main/res/drawable/vdz.png" /> |
|||
<ignore path="src/main/res/drawable/verified.png" /> |
|||
<ignore path="src/main/res/drawable/video.png" /> |
|||
<ignore path="src/main/res/drawable/video_views.png" /> |
|||
<ignore path="src/main/res/drawable/vol.png" /> |
|||
</issue> |
|||
</lint> |
After Width: 512 | Height: 512 | Size: 142 KiB |
@ -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 |
@ -0,0 +1,218 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
|||
xmlns:tools="http://schemas.android.com/tools" |
|||
package="awais.instagrabber"> |
|||
|
|||
<uses-permission android:name="android.permission.INTERNET" /> |
|||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> |
|||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> |
|||
|
|||
<application |
|||
android:name=".InstaApp" |
|||
android:allowBackup="true" |
|||
android:fullBackupContent="@xml/backup_descriptor" |
|||
android:icon="@mipmap/ic_launcher" |
|||
android:label="@string/app_name" |
|||
android:requestLegacyExternalStorage="true" |
|||
android:supportsRtl="true" |
|||
android:theme="@style/AppTheme" |
|||
tools:ignore="UnusedAttribute"> |
|||
|
|||
<activity |
|||
android:name=".activities.Main" |
|||
android:launchMode="singleTop" |
|||
android:taskAffinity=".Main" |
|||
android:windowSoftInputMode="adjustResize"> |
|||
<intent-filter> |
|||
<action android:name="android.intent.action.MAIN" /> |
|||
<action android:name="android.intent.action.VIEW" /> |
|||
|
|||
<category android:name="android.intent.category.LAUNCHER" /> |
|||
</intent-filter> |
|||
|
|||
<intent-filter> |
|||
<action android:name="android.intent.action.SEND" /> |
|||
<action android:name="android.intent.action.SEARCH" /> |
|||
<action android:name="android.intent.action.WEB_SEARCH" /> |
|||
|
|||
<category android:name="android.intent.category.DEFAULT" /> |
|||
<category android:name="android.intent.category.BROWSABLE" /> |
|||
|
|||
<data android:mimeType="text/plain" /> |
|||
</intent-filter> |
|||
|
|||
<intent-filter> |
|||
<action android:name="android.intent.action.VIEW" /> |
|||
|
|||
<category android:name="android.intent.category.DEFAULT" /> |
|||
<category android:name="android.intent.category.BROWSABLE" /> |
|||
|
|||
<data android:scheme="http" /> |
|||
<data android:scheme="https" /> |
|||
|
|||
<data android:host="ig.me" /> |
|||
<data android:host="www.ig.me" /> |
|||
<data android:host="instagram.com" /> |
|||
<data android:host="www.instagram.com" /> |
|||
|
|||
<data android:pathPrefix="/" /> |
|||
<data android:pathPrefix="/p" /> |
|||
<data android:pathPrefix="/explore/tags" /> |
|||
</intent-filter> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name="awaisomereport.ErrorReporterActivity" |
|||
android:allowEmbedded="false" |
|||
android:allowTaskReparenting="false" |
|||
android:autoRemoveFromRecents="true" |
|||
android:clearTaskOnLaunch="true" |
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" |
|||
android:documentLaunchMode="never" |
|||
android:excludeFromRecents="true" |
|||
android:finishOnTaskLaunch="true" |
|||
android:launchMode="singleTask" |
|||
android:lockTaskMode="never" |
|||
android:noHistory="false" |
|||
android:screenOrientation="portrait" |
|||
android:taskAffinity="awais.instagrabber.errorreport" |
|||
android:theme="@android:style/Theme.DeviceDefault.Dialog" /> |
|||
|
|||
<activity |
|||
android:name=".directdownload.MultiDirectDialog" |
|||
android:allowEmbedded="false" |
|||
android:allowTaskReparenting="false" |
|||
android:autoRemoveFromRecents="true" |
|||
android:clearTaskOnLaunch="true" |
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" |
|||
android:documentLaunchMode="never" |
|||
android:excludeFromRecents="true" |
|||
android:finishOnTaskLaunch="true" |
|||
android:launchMode="singleTask" |
|||
android:lockTaskMode="never" |
|||
android:noHistory="false" |
|||
android:taskAffinity="awais.instagrabber.multidialog" |
|||
android:theme="@style/FlyingGayDialog" /> |
|||
|
|||
<activity |
|||
android:name=".directdownload.DirectDownload" |
|||
android:allowEmbedded="false" |
|||
android:allowTaskReparenting="false" |
|||
android:autoRemoveFromRecents="true" |
|||
android:clearTaskOnLaunch="true" |
|||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" |
|||
android:description="@string/direct_download_desc" |
|||
android:documentLaunchMode="never" |
|||
android:excludeFromRecents="true" |
|||
android:finishOnTaskLaunch="true" |
|||
android:label="@string/direct_download" |
|||
android:launchMode="singleTask" |
|||
android:lockTaskMode="never" |
|||
android:noHistory="false" |
|||
android:theme="@style/CompletelyTransparent"> |
|||
|
|||
<intent-filter> |
|||
<action android:name="android.intent.action.SEND" /> |
|||
<action android:name="android.intent.action.SEARCH" /> |
|||
<action android:name="android.intent.action.WEB_SEARCH" /> |
|||
|
|||
<category android:name="android.intent.category.DEFAULT" /> |
|||
<category android:name="android.intent.category.BROWSABLE" /> |
|||
|
|||
<data android:mimeType="text/plain" /> |
|||
</intent-filter> |
|||
|
|||
<intent-filter> |
|||
<category android:name="android.intent.category.DEFAULT" /> |
|||
<category android:name="android.intent.category.BROWSABLE" /> |
|||
|
|||
<data android:host="ig.me" /> |
|||
<data android:scheme="http" /> |
|||
<data android:scheme="https" /> |
|||
</intent-filter> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.PostViewer" |
|||
android:parentActivityName=".activities.Main"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.Main" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.CommentsViewer" |
|||
android:parentActivityName=".activities.PostViewer"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.PostViewer" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.StoryViewer" |
|||
android:parentActivityName=".activities.Main"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.Main" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.FollowViewer" |
|||
android:parentActivityName=".activities.Main"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.Main" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.ProfileViewer" |
|||
android:parentActivityName=".activities.Main"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.Main" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.Login" |
|||
android:label="@string/login" |
|||
android:parentActivityName=".activities.Main"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.Main" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.DirectMessages" |
|||
android:parentActivityName=".activities.Main"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.Main" /> |
|||
</activity> |
|||
|
|||
<activity |
|||
android:name=".activities.DirectMessagesUserInbox" |
|||
android:parentActivityName=".activities.DirectMessages"> |
|||
|
|||
<meta-data |
|||
android:name="android.support.PARENT_ACTIVITY" |
|||
android:value=".activities.DirectMessages" /> |
|||
</activity> |
|||
|
|||
<provider |
|||
android:name="androidx.core.content.FileProvider" |
|||
android:authorities="${applicationId}.provider" |
|||
android:exported="false" |
|||
android:grantUriPermissions="true"> |
|||
<meta-data |
|||
android:name="android.support.FILE_PROVIDER_PATHS" |
|||
android:resource="@xml/provider_paths" /> |
|||
</provider> |
|||
</application> |
|||
</manifest> |
After Width: 512 | Height: 512 | Size: 70 KiB |
@ -0,0 +1,66 @@ |
|||
package awais.instagrabber; |
|||
|
|||
import android.content.ClipboardManager; |
|||
import android.content.Context; |
|||
|
|||
import androidx.core.app.NotificationManagerCompat; |
|||
import androidx.multidex.MultiDexApplication; |
|||
|
|||
import java.net.CookieHandler; |
|||
import java.text.SimpleDateFormat; |
|||
|
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DataBox; |
|||
import awais.instagrabber.utils.LocaleUtils; |
|||
import awais.instagrabber.utils.SettingsHelper; |
|||
import awaisomereport.CrashReporter; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.NET_COOKIE_MANAGER; |
|||
import static awais.instagrabber.utils.Utils.changeTheme; |
|||
import static awais.instagrabber.utils.Utils.clipboardManager; |
|||
import static awais.instagrabber.utils.Utils.dataBox; |
|||
import static awais.instagrabber.utils.Utils.datetimeParser; |
|||
import static awais.instagrabber.utils.Utils.getInstalledTelegramPackage; |
|||
import static awais.instagrabber.utils.Utils.isInstaInstalled; |
|||
import static awais.instagrabber.utils.Utils.isInstagramInstalled; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awais.instagrabber.utils.Utils.notificationManager; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
import static awais.instagrabber.utils.Utils.telegramPackage; |
|||
|
|||
public final class InstaApp extends MultiDexApplication { |
|||
@Override |
|||
public void onCreate() { |
|||
super.onCreate(); |
|||
|
|||
if (!BuildConfig.DEBUG) CrashReporter.get(this).start(); |
|||
logCollector = new LogCollector(this); |
|||
|
|||
CookieHandler.setDefault(NET_COOKIE_MANAGER); |
|||
|
|||
final Context appContext = getApplicationContext(); |
|||
|
|||
isInstagramInstalled = isInstaInstalled(appContext); |
|||
telegramPackage = getInstalledTelegramPackage(appContext); |
|||
|
|||
if (dataBox == null) |
|||
dataBox = DataBox.getInstance(appContext); |
|||
|
|||
if (settingsHelper == null) |
|||
settingsHelper = new SettingsHelper(this); |
|||
|
|||
LocaleUtils.setLocale(getBaseContext()); |
|||
|
|||
if (notificationManager == null) |
|||
notificationManager = NotificationManagerCompat.from(appContext); |
|||
|
|||
if (clipboardManager == null) |
|||
clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); |
|||
|
|||
if (datetimeParser == null) |
|||
datetimeParser = new SimpleDateFormat(settingsHelper.getString(Constants.DATE_TIME_FORMAT), LocaleUtils.getCurrentLocale()); |
|||
|
|||
changeTheme(); |
|||
} |
|||
} |
@ -0,0 +1,864 @@ |
|||
package awais.instagrabber; |
|||
|
|||
import android.content.Intent; |
|||
import android.content.res.ColorStateList; |
|||
import android.content.res.Resources; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.Canvas; |
|||
import android.graphics.Typeface; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.os.Handler; |
|||
import android.text.SpannableStringBuilder; |
|||
import android.text.method.LinkMovementMethod; |
|||
import android.text.style.RelativeSizeSpan; |
|||
import android.text.style.StyleSpan; |
|||
import android.util.Log; |
|||
import android.util.TypedValue; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageView; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.core.view.GravityCompat; |
|||
import androidx.core.widget.ImageViewCompat; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; |
|||
|
|||
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 com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.material.appbar.AppBarLayout; |
|||
import com.google.android.material.shape.MaterialShapeDrawable; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
import awais.instagrabber.activities.FollowViewer; |
|||
import awais.instagrabber.activities.Main; |
|||
import awais.instagrabber.activities.PostViewer; |
|||
import awais.instagrabber.activities.StoryViewer; |
|||
import awais.instagrabber.adapters.DiscoverAdapter; |
|||
import awais.instagrabber.adapters.FeedAdapter; |
|||
import awais.instagrabber.adapters.FeedStoriesAdapter; |
|||
import awais.instagrabber.adapters.PostsAdapter; |
|||
import awais.instagrabber.asyncs.DiscoverFetcher; |
|||
import awais.instagrabber.asyncs.FeedFetcher; |
|||
import awais.instagrabber.asyncs.FeedStoriesFetcher; |
|||
import awais.instagrabber.asyncs.HighlightsFetcher; |
|||
import awais.instagrabber.asyncs.PostsFetcher; |
|||
import awais.instagrabber.asyncs.ProfileFetcher; |
|||
import awais.instagrabber.asyncs.StoryStatusFetcher; |
|||
import awais.instagrabber.customviews.MouseDrawer; |
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager; |
|||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; |
|||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; |
|||
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.BasePostModel; |
|||
import awais.instagrabber.models.DiscoverItemModel; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.FeedStoryModel; |
|||
import awais.instagrabber.models.IntentModel; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.StoryModel; |
|||
import awais.instagrabber.models.enums.IntentModelType; |
|||
import awais.instagrabber.models.enums.ItemGetType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS; |
|||
import static awais.instagrabber.utils.Constants.BOTTOM_TOOLBAR; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class MainHelper implements SwipeRefreshLayout.OnRefreshListener { |
|||
private static AsyncTask<?, ?, ?> currentlyExecuting; |
|||
private AsyncTask<Void, Void, FeedStoryModel[]> prevStoriesFetcher; |
|||
private final boolean autoloadPosts; |
|||
private boolean hasNextPage = false, feedHasNextPage = false, discoverHasMore = false; |
|||
private String endCursor = null, feedEndCursor = null, discoverEndMaxId = null; |
|||
private final FetchListener<PostModel[]> postsFetchListener = new FetchListener<PostModel[]>() { |
|||
@Override |
|||
public void onResult(final PostModel[] result) { |
|||
if (result != null) { |
|||
final int oldSize = main.allItems.size(); |
|||
main.allItems.addAll(Arrays.asList(result)); |
|||
|
|||
postsAdapter.notifyItemRangeInserted(oldSize, result.length); |
|||
|
|||
main.mainBinding.mainPosts.post(() -> { |
|||
main.mainBinding.mainPosts.setNestedScrollingEnabled(true); |
|||
main.mainBinding.mainPosts.setVisibility(View.VISIBLE); |
|||
}); |
|||
|
|||
final String username; |
|||
final String postFix; |
|||
if (!isHashtag) { |
|||
username = main.profileModel.getUsername(); |
|||
postFix = "/" + main.profileModel.getPostCount() + ')'; |
|||
} else { |
|||
username = null; |
|||
postFix = null; |
|||
} |
|||
|
|||
if (isHashtag) |
|||
main.mainBinding.toolbar.toolbar.setTitle(main.getString(R.string.title_hashtag_prefix) + main.userQuery); |
|||
else main.mainBinding.toolbar.toolbar.setTitle(username + " (" + main.allItems.size() + postFix); |
|||
|
|||
final PostModel model = result[result.length - 1]; |
|||
if (model != null) { |
|||
endCursor = model.getEndCursor(); |
|||
|
|||
if (endCursor == null && !isHashtag) { |
|||
main.mainBinding.toolbar.toolbar.setTitle(username + " (" + main.profileModel.getPostCount() + postFix); |
|||
final Handler handler = new Handler(); |
|||
handler.postDelayed(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
main.mainBinding.toolbar.toolbar.setTitle(username); |
|||
handler.removeCallbacks(this); |
|||
} |
|||
}, 1000); |
|||
} |
|||
|
|||
hasNextPage = model.hasNextPage(); |
|||
if ((autoloadPosts && hasNextPage) && !isHashtag) |
|||
currentlyExecuting = new PostsFetcher(main.profileModel.getId(), endCursor, this) |
|||
.setUsername(main.profileModel.getUsername()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
else |
|||
main.mainBinding.swipeRefreshLayout.setRefreshing(false); |
|||
model.setPageCursor(false, null); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
main.mainBinding.feedSwipeRefreshLayout.post(() -> main.mainBinding.feedSwipeRefreshLayout.setRefreshing(true)); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final FeedModel[] result) { |
|||
if (result != null) { |
|||
final int oldSize = main.feedItems.size(); |
|||
main.feedItems.addAll(Arrays.asList(result)); |
|||
feedAdapter.notifyItemRangeInserted(oldSize, result.length); |
|||
|
|||
main.mainBinding.feedPosts.post(() -> main.mainBinding.feedPosts.setNestedScrollingEnabled(true)); |
|||
|
|||
final PostModel feedPostModel = result[result.length - 1]; |
|||
if (feedPostModel != null) { |
|||
feedEndCursor = feedPostModel.getEndCursor(); |
|||
feedHasNextPage = feedPostModel.hasNextPage(); |
|||
feedPostModel.setPageCursor(false, null); |
|||
} |
|||
} |
|||
|
|||
main.mainBinding.feedSwipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
}; |
|||
private final FetchListener<DiscoverItemModel[]> discoverFetchListener = new FetchListener<DiscoverItemModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
main.mainBinding.discoverSwipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final DiscoverItemModel[] result) { |
|||
if (result != null) { |
|||
final int oldSize = main.discoverItems.size(); |
|||
main.discoverItems.addAll(Arrays.asList(result)); |
|||
discoverAdapter.notifyItemRangeInserted(oldSize, result.length); |
|||
|
|||
final DiscoverItemModel discoverItemModel = result[result.length - 1]; |
|||
if (discoverItemModel != null) { |
|||
discoverEndMaxId = discoverItemModel.getNextMaxId(); |
|||
discoverHasMore = discoverItemModel.hasMore(); |
|||
discoverItemModel.setMore(false, null); |
|||
} |
|||
} |
|||
|
|||
main.mainBinding.discoverSwipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
}; |
|||
private final FetchListener<FeedStoryModel[]> feedStoriesListener = new FetchListener<FeedStoryModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
main.mainBinding.feedStories.setVisibility(View.GONE); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final FeedStoryModel[] result) { |
|||
feedStoriesAdapter.setData(result); |
|||
if (result != null && result.length > 0) |
|||
main.mainBinding.feedStories.setVisibility(View.VISIBLE); |
|||
} |
|||
}; |
|||
private final MentionClickListener mentionClickListener = new MentionClickListener() { |
|||
@Override |
|||
public void onClick(final RamboTextView view, final String text, final boolean isHashtag) { |
|||
new AlertDialog.Builder(main).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) |
|||
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> { |
|||
if (Main.scanHack != null) Main.scanHack.onResult(text); |
|||
}).show(); |
|||
} |
|||
}; |
|||
private final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter(null, new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(final View v) { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof FeedStoryModel) { |
|||
final FeedStoryModel feedStoryModel = (FeedStoryModel) tag; |
|||
final StoryModel[] storyModels = feedStoryModel.getStoryModels(); |
|||
|
|||
main.startActivity(new Intent(main, StoryViewer.class) |
|||
.putExtra(Constants.EXTRAS_STORIES, storyModels) |
|||
.putExtra(Constants.EXTRAS_USERNAME, feedStoryModel.getProfileModel().getUsername()) |
|||
); |
|||
} |
|||
} |
|||
}); |
|||
@NonNull |
|||
private final Main main; |
|||
private final Resources resources; |
|||
private final View collapsingToolbar; |
|||
private final RecyclerLazyLoader lazyLoader; |
|||
private boolean isHashtag; |
|||
private PostsAdapter postsAdapter; |
|||
private FeedAdapter feedAdapter; |
|||
private RecyclerLazyLoader feedLazyLoader, discoverLazyLoader; |
|||
private DiscoverAdapter discoverAdapter; |
|||
public SimpleExoPlayer currentFeedPlayer; // hack for remix drawer layout |
|||
|
|||
public MainHelper(@NonNull final Main main) { |
|||
stopCurrentExecutor(); |
|||
|
|||
this.main = main; |
|||
this.resources = main.getResources(); |
|||
this.autoloadPosts = Utils.settingsHelper.getBoolean(AUTOLOAD_POSTS); |
|||
|
|||
main.mainBinding.swipeRefreshLayout.setOnRefreshListener(this); |
|||
main.mainBinding.mainUrl.setMovementMethod(new LinkMovementMethod()); |
|||
|
|||
final boolean isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE)); |
|||
|
|||
final LinearLayout iconSlider = main.findViewById(R.id.iconSlider); |
|||
final ImageView iconFeed = (ImageView) iconSlider.getChildAt(0); |
|||
final ImageView iconProfile = (ImageView) iconSlider.getChildAt(1); |
|||
final ImageView iconDiscover = (ImageView) iconSlider.getChildAt(2); |
|||
|
|||
final boolean isBottomToolbar = Utils.settingsHelper.getBoolean(BOTTOM_TOOLBAR); |
|||
if (!isLoggedIn) { |
|||
main.mainBinding.drawerLayout.removeView(main.mainBinding.feedLayout); |
|||
main.mainBinding.drawerLayout.removeView(main.mainBinding.discoverSwipeRefreshLayout); |
|||
iconFeed.setAlpha(0.4f); |
|||
iconDiscover.setAlpha(0.4f); |
|||
} else { |
|||
iconFeed.setAlpha(1f); |
|||
iconDiscover.setAlpha(1f); |
|||
|
|||
setupExplore(); |
|||
|
|||
final boolean showFeed = Utils.settingsHelper.getBoolean(Constants.SHOW_FEED); |
|||
if (showFeed) setupFeed(); |
|||
else { |
|||
iconFeed.setAlpha(0.4f); |
|||
main.mainBinding.drawerLayout.removeView(main.mainBinding.feedLayout); |
|||
} |
|||
|
|||
final TypedValue resolvedAttr = new TypedValue(); |
|||
main.getTheme().resolveAttribute(android.R.attr.textColorPrimary, resolvedAttr, true); |
|||
|
|||
final int selectedItem = ContextCompat.getColor(main, resolvedAttr.resourceId != 0 ? resolvedAttr.resourceId : resolvedAttr.data); |
|||
final ColorStateList colorStateList = ColorStateList.valueOf(selectedItem); |
|||
|
|||
main.mainBinding.toolbar.toolbar.measure(0, -1); |
|||
final int toolbarMeasuredHeight = main.mainBinding.toolbar.toolbar.getMeasuredHeight(); |
|||
|
|||
final ViewGroup.LayoutParams layoutParams = main.mainBinding.toolbar.toolbar.getLayoutParams(); |
|||
final MouseDrawer.DrawerListener simpleDrawerListener = new MouseDrawer.DrawerListener() { |
|||
private final String titleDiscover = resources.getString(R.string.title_discover); |
|||
|
|||
@Override |
|||
public void onDrawerSlide(final View drawerView, @MouseDrawer.EdgeGravity final int gravity, final float slideOffset) { |
|||
final int currentIconAlpha = (int) Math.max(100, 255 - 255 * slideOffset); |
|||
final int otherIconAlpha = (int) Math.max(100, 255 * slideOffset); |
|||
|
|||
ImageViewCompat.setImageTintList(iconProfile, colorStateList.withAlpha(currentIconAlpha)); |
|||
|
|||
final boolean drawerOpening = slideOffset > 0.0f; |
|||
final int alpha; |
|||
final ColorStateList imageTintList; |
|||
|
|||
if (gravity == GravityCompat.START) { |
|||
// this helps hide the toolbar when opening feed |
|||
|
|||
final int roundedToolbarHeight; |
|||
final float toolbarHeight; |
|||
|
|||
if (isBottomToolbar) { |
|||
toolbarHeight = toolbarMeasuredHeight * slideOffset; |
|||
roundedToolbarHeight = -Math.round(toolbarHeight); |
|||
} else { |
|||
toolbarHeight = -toolbarMeasuredHeight * slideOffset; |
|||
roundedToolbarHeight = Math.round(toolbarHeight); |
|||
} |
|||
|
|||
layoutParams.height = Math.max(0, Math.min(toolbarMeasuredHeight, toolbarMeasuredHeight + roundedToolbarHeight)); |
|||
main.mainBinding.toolbar.toolbar.setLayoutParams(layoutParams); |
|||
main.mainBinding.toolbar.toolbar.setTranslationY(toolbarHeight); |
|||
|
|||
imageTintList = ImageViewCompat.getImageTintList(iconDiscover); |
|||
alpha = imageTintList != null ? (imageTintList.getDefaultColor() & 0xFF_000000) >> 24 : 0; |
|||
|
|||
if (drawerOpening && alpha > 100) |
|||
ImageViewCompat.setImageTintList(iconDiscover, colorStateList.withAlpha(currentIconAlpha)); |
|||
|
|||
if (showFeed) ImageViewCompat.setImageTintList(iconFeed, colorStateList.withAlpha(otherIconAlpha)); |
|||
} else { |
|||
// this changes toolbar title |
|||
main.mainBinding.toolbar.toolbar.setTitle(slideOffset >= 0.466 ? titleDiscover : main.userQuery); |
|||
|
|||
if (showFeed) { |
|||
imageTintList = ImageViewCompat.getImageTintList(iconFeed); |
|||
alpha = imageTintList != null ? (imageTintList.getDefaultColor() & 0xFF_000000) >> 24 : 0; |
|||
|
|||
if (drawerOpening && alpha > 100) |
|||
ImageViewCompat.setImageTintList(iconFeed, colorStateList.withAlpha(currentIconAlpha)); |
|||
} |
|||
|
|||
ImageViewCompat.setImageTintList(iconDiscover, colorStateList.withAlpha(otherIconAlpha)); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDrawerOpened(@NonNull final View drawerView, @MouseDrawer.EdgeGravity final int gravity) { |
|||
if (gravity == GravityCompat.START || drawerView == main.mainBinding.feedLayout) { |
|||
if (currentFeedPlayer != null) { |
|||
currentFeedPlayer.setPlayWhenReady(true); |
|||
currentFeedPlayer.getPlaybackState(); |
|||
} |
|||
} else { |
|||
// clear selection |
|||
isSelectionCleared(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDrawerClosed(@NonNull final View drawerView, @MouseDrawer.EdgeGravity final int gravity) { |
|||
if (gravity == GravityCompat.START || drawerView == main.mainBinding.feedLayout) { |
|||
if (currentFeedPlayer != null) { |
|||
currentFeedPlayer.setPlayWhenReady(false); |
|||
currentFeedPlayer.getPlaybackState(); |
|||
} |
|||
} else { |
|||
// clear selection |
|||
isSelectionCleared(); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
ImageViewCompat.setImageTintList(iconFeed, colorStateList.withAlpha(100)); // to change colors when created |
|||
ImageViewCompat.setImageTintList(iconProfile, colorStateList.withAlpha(255)); // to change colors when created |
|||
ImageViewCompat.setImageTintList(iconDiscover, colorStateList.withAlpha(100)); // to change colors when created |
|||
|
|||
main.mainBinding.drawerLayout.addDrawerListener(simpleDrawerListener); |
|||
} |
|||
|
|||
collapsingToolbar = main.mainBinding.appBarLayout.getChildAt(0); |
|||
|
|||
main.mainBinding.mainPosts.setNestedScrollingEnabled(false); |
|||
main.mainBinding.highlightsList.setLayoutManager(new LinearLayoutManager(main, LinearLayoutManager.HORIZONTAL, false)); |
|||
main.mainBinding.highlightsList.setAdapter(main.highlightsAdapter); |
|||
|
|||
int color = -1; |
|||
final Drawable background = main.mainBinding.appBarLayout.getBackground(); |
|||
if (background instanceof MaterialShapeDrawable) { |
|||
final MaterialShapeDrawable drawable = (MaterialShapeDrawable) background; |
|||
final ColorStateList fillColor = drawable.getFillColor(); |
|||
if (fillColor != null) color = fillColor.getDefaultColor(); |
|||
} else { |
|||
final Bitmap bitmap = Bitmap.createBitmap(9, 9, Bitmap.Config.ARGB_8888); |
|||
final Canvas canvas = new Canvas(); |
|||
canvas.setBitmap(bitmap); |
|||
background.draw(canvas); |
|||
color = bitmap.getPixel(4, 4); |
|||
if (!bitmap.isRecycled()) bitmap.recycle(); |
|||
} |
|||
if (color == -1 || color == 0) color = resources.getBoolean(R.bool.isNight) ? 0xff212121 : 0xfff5f5f5; |
|||
main.mainBinding.profileInfo.setBackgroundColor(color); |
|||
main.mainBinding.profileInfo.setClickable(true); |
|||
if (!isBottomToolbar) main.mainBinding.toolbar.toolbar.setBackgroundColor(color); |
|||
|
|||
main.mainBinding.appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { |
|||
private int height; |
|||
|
|||
@Override |
|||
public void onOffsetChanged(final AppBarLayout appBarLayout, final int verticalOffset) { |
|||
if (height == 0) { |
|||
height = main.mainBinding.profileInfo.getHeight(); |
|||
collapsingToolbar.setMinimumHeight(height); |
|||
} |
|||
main.mainBinding.profileInfo.setTranslationY(-Math.min(0, verticalOffset)); |
|||
} |
|||
}); |
|||
|
|||
main.setSupportActionBar(main.mainBinding.toolbar.toolbar); |
|||
if (isBottomToolbar) { |
|||
final LinearLayout linearLayout = (LinearLayout) main.mainBinding.toolbar.toolbar.getParent(); |
|||
linearLayout.removeView(main.mainBinding.toolbar.toolbar); |
|||
linearLayout.addView(main.mainBinding.toolbar.toolbar, linearLayout.getChildCount()); |
|||
} |
|||
|
|||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(130)); |
|||
main.mainBinding.mainPosts.setLayoutManager(layoutManager); |
|||
main.mainBinding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); |
|||
main.mainBinding.mainPosts.setAdapter(postsAdapter = new PostsAdapter(main.allItems, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof PostModel) { |
|||
final PostModel postModel = (PostModel) tag; |
|||
|
|||
if (postsAdapter.isSelecting) toggleSelection(postModel); |
|||
else main.startActivity(new Intent(main, PostViewer.class) |
|||
.putExtra(Constants.EXTRAS_INDEX, postModel.getPosition()) |
|||
.putExtra(Constants.EXTRAS_POST, postModel) |
|||
.putExtra(Constants.EXTRAS_USER, main.userQuery) |
|||
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.MAIN_ITEMS)); |
|||
} |
|||
}, v -> { // long click listener |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof PostModel) { |
|||
postsAdapter.isSelecting = true; |
|||
toggleSelection((PostModel) tag); |
|||
} |
|||
return true; |
|||
})); |
|||
|
|||
this.lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { |
|||
if ((!autoloadPosts || isHashtag) && hasNextPage) { |
|||
main.mainBinding.swipeRefreshLayout.setRefreshing(true); |
|||
stopCurrentExecutor(); |
|||
currentlyExecuting = new PostsFetcher(isHashtag ? main.userQuery : main.profileModel.getId(), endCursor, postsFetchListener) |
|||
.setUsername(isHashtag ? null : main.profileModel.getUsername()) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
endCursor = null; |
|||
} |
|||
}); |
|||
main.mainBinding.mainPosts.addOnScrollListener(lazyLoader); |
|||
} |
|||
|
|||
private void setupFeed() { |
|||
main.mainBinding.feedStories.setLayoutManager(new LinearLayoutManager(main, LinearLayoutManager.HORIZONTAL, false)); |
|||
main.mainBinding.feedStories.setAdapter(feedStoriesAdapter); |
|||
refreshFeedStories(); |
|||
|
|||
final LinearLayoutManager layoutManager = new LinearLayoutManager(main); |
|||
main.mainBinding.feedPosts.setLayoutManager(layoutManager); |
|||
main.mainBinding.feedPosts.setAdapter(feedAdapter = new FeedAdapter(main, main.feedItems, (view, text, isHashtag) -> |
|||
new AlertDialog.Builder(main).setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) |
|||
.setTitle(text).setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, (dialog, which) -> { |
|||
if (Main.scanHack != null) { |
|||
main.mainBinding.drawerLayout.closeDrawers(); |
|||
Main.scanHack.onResult(text); |
|||
} |
|||
}).show())); |
|||
|
|||
main.mainBinding.feedSwipeRefreshLayout.setOnRefreshListener(() -> { |
|||
refreshFeedStories(); |
|||
|
|||
if (feedLazyLoader != null) feedLazyLoader.resetState(); |
|||
main.feedItems.clear(); |
|||
if (feedAdapter != null) feedAdapter.notifyDataSetChanged(); |
|||
new FeedFetcher(feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
}); |
|||
|
|||
main.mainBinding.feedPosts.addOnScrollListener(feedLazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { |
|||
if (feedHasNextPage) { |
|||
main.mainBinding.feedSwipeRefreshLayout.setRefreshing(true); |
|||
new FeedFetcher(feedEndCursor, feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
feedEndCursor = null; |
|||
} |
|||
})); |
|||
|
|||
main.mainBinding.feedPosts.addOnScrollListener(new VideoAwareRecyclerScroller(main, main.feedItems, |
|||
(itemPos, player) -> currentFeedPlayer = player)); |
|||
|
|||
new FeedFetcher(feedFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void refreshFeedStories() { |
|||
// todo setup feed stories |
|||
if (prevStoriesFetcher != null) { |
|||
try { |
|||
prevStoriesFetcher.cancel(true); |
|||
} catch (final Exception e) { |
|||
// ignore |
|||
} |
|||
} |
|||
prevStoriesFetcher = new FeedStoriesFetcher(feedStoriesListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void setupExplore() { |
|||
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(main, Utils.convertDpToPx(130)); |
|||
main.mainBinding.discoverPosts.setLayoutManager(layoutManager); |
|||
main.mainBinding.discoverPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); |
|||
|
|||
main.mainBinding.discoverSwipeRefreshLayout.setOnRefreshListener(() -> { |
|||
if (discoverLazyLoader != null) discoverLazyLoader.resetState(); |
|||
main.discoverItems.clear(); |
|||
if (discoverAdapter != null) discoverAdapter.notifyDataSetChanged(); |
|||
new DiscoverFetcher(null, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
}); |
|||
|
|||
main.mainBinding.discoverPosts.setAdapter(discoverAdapter = new DiscoverAdapter(main.discoverItems, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof DiscoverItemModel) { |
|||
final DiscoverItemModel itemModel = (DiscoverItemModel) tag; |
|||
|
|||
if (discoverAdapter.isSelecting) toggleDiscoverSelection(itemModel); |
|||
else main.startActivity(new Intent(main, PostViewer.class) |
|||
.putExtra(Constants.EXTRAS_INDEX, itemModel.getPosition()) |
|||
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.DISCOVER_ITEMS) |
|||
.putExtra(Constants.EXTRAS_POST, new PostModel(itemModel.getShortCode()))); |
|||
} |
|||
}, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof DiscoverItemModel) { |
|||
discoverAdapter.isSelecting = true; |
|||
toggleDiscoverSelection((DiscoverItemModel) tag); |
|||
} |
|||
return true; |
|||
})); |
|||
|
|||
main.mainBinding.discoverPosts.addOnScrollListener(discoverLazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { |
|||
if (discoverHasMore) { |
|||
main.mainBinding.discoverSwipeRefreshLayout.setRefreshing(true); |
|||
new DiscoverFetcher(discoverEndMaxId, discoverFetchListener, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
discoverEndMaxId = null; |
|||
} |
|||
})); |
|||
|
|||
new DiscoverFetcher(null, discoverFetchListener, true).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
public void onIntent(final Intent intent) { |
|||
if (intent != null) { |
|||
final String action = intent.getAction(); |
|||
if (!Utils.isEmpty(action) && !Intent.ACTION_MAIN.equals(action)) { |
|||
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); |
|||
|
|||
boolean error = true; |
|||
|
|||
String data = null; |
|||
final Bundle extras = intent.getExtras(); |
|||
if (extras != null) { |
|||
final Object extraData = extras.get(Intent.EXTRA_TEXT); |
|||
if (extraData != null) { |
|||
error = false; |
|||
data = extraData.toString(); |
|||
} |
|||
} |
|||
|
|||
if (error) { |
|||
final Uri intentData = intent.getData(); |
|||
if (intentData != null) data = intentData.toString(); |
|||
} |
|||
|
|||
if (data != null && !Utils.isEmpty(data)) { |
|||
if (data.indexOf('\n') > 0) data = data.substring(data.lastIndexOf('\n') + 1); |
|||
|
|||
final IntentModel model = Utils.stripString(data); |
|||
if (model != null) { |
|||
final String modelText = model.getText(); |
|||
final IntentModelType modelType = model.getType(); |
|||
|
|||
if (modelType == IntentModelType.POST) { |
|||
main.startActivityForResult(new Intent(main, PostViewer.class) |
|||
.putExtra(Constants.EXTRAS_USER, main.userQuery) |
|||
.putExtra(Constants.EXTRAS_POST, new PostModel(modelText)), 9629); |
|||
} else { |
|||
main.addToStack(); |
|||
main.userQuery = modelType == IntentModelType.HASHTAG ? '#' + modelText : modelText; |
|||
onRefresh(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onRefresh() { |
|||
main.mainBinding.drawerLayout.closeDrawers(); |
|||
if (lazyLoader != null) lazyLoader.resetState(); |
|||
stopCurrentExecutor(); |
|||
main.allItems.clear(); |
|||
main.selectedItems.clear(); |
|||
if (postsAdapter != null) { |
|||
postsAdapter.isSelecting = false; |
|||
postsAdapter.notifyDataSetChanged(); |
|||
} |
|||
main.mainBinding.appBarLayout.setExpanded(true, true); |
|||
main.mainBinding.privatePage.setVisibility(View.GONE); |
|||
main.mainBinding.mainProfileImage.setImageBitmap(null); |
|||
main.mainBinding.mainProfileImage.setImageDrawable(null); |
|||
main.mainBinding.mainUrl.setText(null); |
|||
main.mainBinding.mainFullName.setText(null); |
|||
main.mainBinding.mainPostCount.setText(null); |
|||
main.mainBinding.mainFollowers.setText(null); |
|||
main.mainBinding.mainFollowing.setText(null); |
|||
main.mainBinding.mainBiography.setText(null); |
|||
main.mainBinding.mainBiography.setEnabled(false); |
|||
main.mainBinding.mainProfileImage.setEnabled(false); |
|||
main.mainBinding.mainBiography.setMentionClickListener(null); |
|||
main.mainBinding.mainUrl.setVisibility(View.GONE); |
|||
main.mainBinding.isVerified.setVisibility(View.GONE); |
|||
|
|||
main.mainBinding.mainPosts.setNestedScrollingEnabled(false); |
|||
main.mainBinding.highlightsList.setVisibility(View.GONE); |
|||
collapsingToolbar.setVisibility(View.GONE); |
|||
main.highlightsAdapter.setData(null); |
|||
|
|||
main.mainBinding.swipeRefreshLayout.setRefreshing(main.userQuery != null); |
|||
if (main.userQuery == null) { |
|||
main.mainBinding.toolbar.toolbar.setTitle(R.string.app_name); |
|||
return; |
|||
} |
|||
|
|||
isHashtag = main.userQuery.charAt(0) == '#'; |
|||
collapsingToolbar.setVisibility(isHashtag ? View.GONE : View.VISIBLE); |
|||
|
|||
if (isHashtag) { |
|||
main.mainBinding.toolbar.toolbar.setTitle(resources.getString(R.string.title_hashtag_prefix) + main.userQuery); |
|||
main.mainBinding.infoContainer.setVisibility(View.GONE); |
|||
|
|||
currentlyExecuting = new PostsFetcher(main.userQuery, postsFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
|
|||
} else { |
|||
main.mainBinding.toolbar.toolbar.setTitle(main.userQuery); |
|||
main.mainBinding.infoContainer.setVisibility(View.VISIBLE); |
|||
|
|||
currentlyExecuting = new ProfileFetcher(main.userQuery, profileModel -> { |
|||
main.profileModel = profileModel; |
|||
|
|||
if (profileModel == null) { |
|||
main.mainBinding.swipeRefreshLayout.setRefreshing(false); |
|||
Toast.makeText(main, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); |
|||
main.mainBinding.toolbar.toolbar.setTitle(R.string.app_name); |
|||
return; |
|||
} |
|||
|
|||
main.mainBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); |
|||
final String profileId = profileModel.getId(); |
|||
|
|||
final boolean isLoggedIn = !Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE)); |
|||
if (isLoggedIn) { |
|||
new StoryStatusFetcher(profileId, result -> { |
|||
main.storyModels = result; |
|||
if (result != null && result.length > 0) main.mainBinding.mainProfileImage.setStoriesBorder(); |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
|
|||
new HighlightsFetcher(profileId, result -> { |
|||
if (result != null && result.length > 0) { |
|||
main.mainBinding.highlightsList.setVisibility(View.VISIBLE); |
|||
main.highlightsAdapter.setData(result); |
|||
} |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
main.mainBinding.mainProfileImage.setEnabled(false); |
|||
Glide.with(main).load(profileModel.getSdProfilePic()).listener(new RequestListener<Drawable>() { |
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
main.mainBinding.mainProfileImage.setEnabled(false); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
main.mainBinding.mainProfileImage.setEnabled(true); |
|||
return false; |
|||
} |
|||
}).into(main.mainBinding.mainProfileImage); |
|||
|
|||
final long followersCount = profileModel.getFollowersCount(); |
|||
final long followingCount = profileModel.getFollowingCount(); |
|||
|
|||
final String postCount = String.valueOf(profileModel.getPostCount()); |
|||
|
|||
SpannableStringBuilder span = new SpannableStringBuilder(resources.getString(R.string.main_posts_count, postCount)); |
|||
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); |
|||
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); |
|||
main.mainBinding.mainPostCount.setText(span); |
|||
|
|||
final String followersCountStr = String.valueOf(followersCount); |
|||
final int followersCountStrLen = followersCountStr.length(); |
|||
span = new SpannableStringBuilder(resources.getString(R.string.main_posts_followers, followersCountStr)); |
|||
span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0); |
|||
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0); |
|||
main.mainBinding.mainFollowers.setText(span); |
|||
|
|||
final String followingCountStr = String.valueOf(followingCount); |
|||
final int followingCountStrLen = followingCountStr.length(); |
|||
span = new SpannableStringBuilder(resources.getString(R.string.main_posts_following, followingCountStr)); |
|||
span.setSpan(new RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0); |
|||
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0); |
|||
main.mainBinding.mainFollowing.setText(span); |
|||
|
|||
main.mainBinding.mainFullName.setText(profileModel.getName()); |
|||
|
|||
CharSequence biography = profileModel.getBiography(); |
|||
main.mainBinding.mainBiography.setCaptionIsExpandable(true); |
|||
main.mainBinding.mainBiography.setCaptionIsExpanded(true); |
|||
if (Utils.hasMentions(biography)) { |
|||
biography = Utils.getMentionText(biography); |
|||
main.mainBinding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE); |
|||
main.mainBinding.mainBiography.setMentionClickListener(mentionClickListener); |
|||
} else { |
|||
main.mainBinding.mainBiography.setText(biography); |
|||
main.mainBinding.mainBiography.setMentionClickListener(null); |
|||
} |
|||
|
|||
final String url = profileModel.getUrl(); |
|||
if (Utils.isEmpty(url)) { |
|||
main.mainBinding.mainUrl.setVisibility(View.GONE); |
|||
} else { |
|||
main.mainBinding.mainUrl.setVisibility(View.VISIBLE); |
|||
main.mainBinding.mainUrl.setText(Utils.getSpannableUrl(url)); |
|||
} |
|||
|
|||
main.mainBinding.mainFullName.setSelected(true); |
|||
main.mainBinding.mainBiography.setEnabled(true); |
|||
|
|||
if (!profileModel.isPrivate()) { |
|||
main.mainBinding.swipeRefreshLayout.setRefreshing(true); |
|||
main.mainBinding.mainPosts.setVisibility(View.VISIBLE); |
|||
main.mainBinding.privatePage.setVisibility(View.GONE); |
|||
|
|||
if (isLoggedIn) { |
|||
final View.OnClickListener followClickListener = v -> main.startActivity(new Intent(main, FollowViewer.class) |
|||
.putExtra(Constants.EXTRAS_FOLLOWERS, v == main.mainBinding.mainFollowers) |
|||
.putExtra(Constants.EXTRAS_NAME, profileModel.getUsername()) |
|||
.putExtra(Constants.EXTRAS_ID, profileId)); |
|||
|
|||
main.mainBinding.mainFollowers.setOnClickListener(followersCount > 0 ? followClickListener : null); |
|||
main.mainBinding.mainFollowing.setOnClickListener(followingCount > 0 ? followClickListener : null); |
|||
} |
|||
|
|||
currentlyExecuting = new PostsFetcher(profileId, postsFetchListener).setUsername(profileModel.getUsername()) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} else { |
|||
main.mainBinding.swipeRefreshLayout.setRefreshing(false); |
|||
main.mainBinding.privatePage.setVisibility(View.VISIBLE); |
|||
main.mainBinding.mainPosts.setVisibility(View.GONE); |
|||
} |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
} |
|||
|
|||
public static void stopCurrentExecutor() { |
|||
if (currentlyExecuting != null) { |
|||
try { |
|||
currentlyExecuting.cancel(true); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void toggleSelection(final PostModel postModel) { |
|||
if (postModel != null && postsAdapter != null) { |
|||
if (postModel.isSelected()) main.selectedItems.remove(postModel); |
|||
else main.selectedItems.add(postModel); |
|||
postModel.setSelected(!postModel.isSelected()); |
|||
notifyAdapter(postModel); |
|||
} |
|||
} |
|||
|
|||
private void notifyAdapter(final PostModel postModel) { |
|||
if (main.selectedItems.size() < 1) postsAdapter.isSelecting = false; |
|||
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged(); |
|||
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel); |
|||
|
|||
if (main.downloadAction != null) main.downloadAction.setVisible(postsAdapter.isSelecting); |
|||
} |
|||
|
|||
/////////////////////////////////////////////////// |
|||
private void toggleDiscoverSelection(final DiscoverItemModel itemModel) { |
|||
if (itemModel != null && discoverAdapter != null) { |
|||
if (itemModel.isSelected()) main.selectedDiscoverItems.remove(itemModel); |
|||
else main.selectedDiscoverItems.add(itemModel); |
|||
itemModel.setSelected(!itemModel.isSelected()); |
|||
notifyDiscoverAdapter(itemModel); |
|||
} |
|||
} |
|||
|
|||
private void notifyDiscoverAdapter(final DiscoverItemModel itemModel) { |
|||
if (main.selectedDiscoverItems.size() < 1) discoverAdapter.isSelecting = false; |
|||
if (itemModel.getPosition() < 0) discoverAdapter.notifyDataSetChanged(); |
|||
else discoverAdapter.notifyItemChanged(itemModel.getPosition(), itemModel); |
|||
|
|||
if (main.downloadAction != null) main.downloadAction.setVisible(discoverAdapter.isSelecting); |
|||
} |
|||
|
|||
public boolean isSelectionCleared() { |
|||
if (postsAdapter != null && postsAdapter.isSelecting) { |
|||
for (final PostModel postModel : main.selectedItems) postModel.setSelected(false); |
|||
main.selectedItems.clear(); |
|||
postsAdapter.isSelecting = false; |
|||
postsAdapter.notifyDataSetChanged(); |
|||
if (main.downloadAction != null) main.downloadAction.setVisible(false); |
|||
return false; |
|||
} else if (discoverAdapter != null && discoverAdapter.isSelecting) { |
|||
for (final DiscoverItemModel itemModel : main.selectedDiscoverItems) itemModel.setSelected(false); |
|||
main.selectedDiscoverItems.clear(); |
|||
discoverAdapter.isSelecting = false; |
|||
discoverAdapter.notifyDataSetChanged(); |
|||
if (main.downloadAction != null) main.downloadAction.setVisible(false); |
|||
return false; |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
public void deselectSelection(final BasePostModel postModel) { |
|||
if (postModel instanceof PostModel) { |
|||
main.selectedItems.remove(postModel); |
|||
postModel.setSelected(false); |
|||
if (postsAdapter != null) notifyAdapter((PostModel) postModel); |
|||
} else if (postModel instanceof DiscoverItemModel) { |
|||
main.selectedDiscoverItems.remove(postModel); |
|||
postModel.setSelected(false); |
|||
if (discoverAdapter != null) notifyDiscoverAdapter((DiscoverItemModel) postModel); |
|||
} |
|||
} |
|||
|
|||
public void onPause() { |
|||
if (currentFeedPlayer != null) { |
|||
currentFeedPlayer.setPlayWhenReady(false); |
|||
currentFeedPlayer.getPlaybackState(); |
|||
} |
|||
} |
|||
|
|||
public void onResume() { |
|||
if (currentFeedPlayer != null) { |
|||
currentFeedPlayer.setPlayWhenReady(true); |
|||
currentFeedPlayer.getPlaybackState(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import androidx.appcompat.app.AppCompatActivity; |
|||
|
|||
import awais.instagrabber.utils.LocaleUtils; |
|||
|
|||
public abstract class BaseLanguageActivity extends AppCompatActivity { |
|||
protected BaseLanguageActivity() { |
|||
LocaleUtils.updateConfig(this); |
|||
} |
|||
} |
@ -0,0 +1,146 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.content.res.Resources; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.text.SpannableString; |
|||
import android.text.Spanned; |
|||
import android.text.style.RelativeSizeSpan; |
|||
import android.view.Menu; |
|||
import android.view.MenuItem; |
|||
import android.view.View; |
|||
import android.widget.ArrayAdapter; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.appcompat.app.AppCompatActivity; |
|||
import androidx.appcompat.widget.SearchView; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.CommentsAdapter; |
|||
import awais.instagrabber.asyncs.CommentsFetcher; |
|||
import awais.instagrabber.databinding.ActivityCommentsBinding; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.CommentModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class CommentsViewer extends AppCompatActivity { |
|||
private CommentsAdapter commentsAdapter; |
|||
private CommentModel commentModel; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
final ActivityCommentsBinding commentsBinding = ActivityCommentsBinding.inflate(getLayoutInflater()); |
|||
setContentView(commentsBinding.getRoot()); |
|||
|
|||
final String shortCode; |
|||
final Intent intent = getIntent(); |
|||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_SHORTCODE) |
|||
|| Utils.isEmpty((shortCode = intent.getStringExtra(Constants.EXTRAS_SHORTCODE)))) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
setSupportActionBar(commentsBinding.toolbar.toolbar); |
|||
commentsBinding.toolbar.toolbar.setTitle(R.string.title_comments); |
|||
commentsBinding.toolbar.toolbar.setSubtitle(shortCode); |
|||
|
|||
final Resources resources = getResources(); |
|||
|
|||
final ArrayAdapter<String> commmentDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, |
|||
new String[]{resources.getString(R.string.open_profile), |
|||
resources.getString(R.string.view_pfp), |
|||
resources.getString(R.string.comment_viewer_copy_user), |
|||
resources.getString(R.string.comment_viewer_copy_comment)}); |
|||
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { |
|||
final ProfileModel profileModel = commentModel.getProfileModel(); |
|||
|
|||
if (which == 0) { |
|||
searchUsername(profileModel.getUsername()); |
|||
} else if (which == 1) { |
|||
startActivity(new Intent(this, ProfileViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel)); |
|||
} else if (which == 2) { |
|||
Utils.copyText(this, profileModel.getUsername()); |
|||
} else if (which == 3) { |
|||
Utils.copyText(this, commentModel.getText().toString()); |
|||
} |
|||
}; |
|||
|
|||
final View.OnClickListener clickListener = v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof CommentModel) { |
|||
commentModel = (CommentModel) tag; |
|||
|
|||
final String username = commentModel.getProfileModel().getUsername(); |
|||
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText()); |
|||
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); |
|||
|
|||
new AlertDialog.Builder(this).setTitle(title) |
|||
.setAdapter(commmentDialogAdapter, profileDialogListener) |
|||
.setNeutralButton(R.string.cancel, null) |
|||
.show(); |
|||
} |
|||
}; |
|||
|
|||
final MentionClickListener mentionClickListener = (view, text, isHashtag) -> |
|||
new AlertDialog.Builder(this).setTitle(text) |
|||
.setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) |
|||
.setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, |
|||
(dialog, which) -> searchUsername(text)).show(); |
|||
|
|||
new CommentsFetcher(shortCode, new FetchListener<CommentModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
commentsBinding.toolbar.progressCircular.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final CommentModel[] commentModels) { |
|||
commentsBinding.toolbar.progressCircular.setVisibility(View.GONE); |
|||
|
|||
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener); |
|||
|
|||
commentsBinding.rvComments.setAdapter(commentsAdapter); |
|||
} |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void searchUsername(final String text) { |
|||
if (Main.scanHack != null) { |
|||
Main.scanHack.onResult(text); |
|||
setResult(6969); |
|||
finish(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean onCreateOptionsMenu(final Menu menu) { |
|||
getMenuInflater().inflate(R.menu.follow, menu); |
|||
|
|||
final MenuItem menuSearch = menu.findItem(R.id.action_search); |
|||
final SearchView searchView = (SearchView) menuSearch.getActionView(); |
|||
searchView.setQueryHint(getResources().getString(R.string.action_search)); |
|||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
|||
@Override |
|||
public boolean onQueryTextSubmit(final String query) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onQueryTextChange(final String query) { |
|||
if (commentsAdapter != null) commentsAdapter.getFilter().filter(query); |
|||
return true; |
|||
} |
|||
}); |
|||
|
|||
menu.findItem(R.id.action_compare).setVisible(false); |
|||
|
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,113 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
import androidx.recyclerview.widget.DividerItemDecoration; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.adapters.DirectMessagesAdapter; |
|||
import awais.instagrabber.asyncs.direct_messages.InboxFetcher; |
|||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; |
|||
import awais.instagrabber.databinding.ActivityDmsBinding; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.direct_messages.InboxModel; |
|||
import awais.instagrabber.models.direct_messages.InboxThreadModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class DirectMessages extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { |
|||
private final ArrayList<InboxThreadModel> inboxThreadModelList = new ArrayList<>(); |
|||
private final DirectMessagesAdapter messagesAdapter = new DirectMessagesAdapter(inboxThreadModelList, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof InboxThreadModel) { |
|||
startActivity(new Intent(this, DirectMessagesUserInbox.class) |
|||
.putExtra(Constants.EXTRAS_THREAD_MODEL, (InboxThreadModel) tag) |
|||
); |
|||
} |
|||
}); |
|||
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() { |
|||
@Override |
|||
public void doBefore() { |
|||
dmsBinding.swipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final InboxModel inboxModel) { |
|||
if (inboxModel != null) { |
|||
endCursor = inboxModel.getOldestCursor(); |
|||
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null; |
|||
// todo get request / unseen count from inboxModel |
|||
|
|||
final InboxThreadModel[] threads = inboxModel.getThreads(); |
|||
if (threads != null) { |
|||
final int oldSize = inboxThreadModelList.size(); |
|||
inboxThreadModelList.addAll(Arrays.asList(threads)); |
|||
|
|||
messagesAdapter.notifyItemRangeInserted(oldSize, threads.length); |
|||
} |
|||
} |
|||
|
|||
dmsBinding.swipeRefreshLayout.setRefreshing(false); |
|||
stopCurrentExecutor(); |
|||
} |
|||
}; |
|||
private String endCursor; |
|||
private RecyclerLazyLoader lazyLoader; |
|||
private AsyncTask<Void, Void, InboxModel> currentlyRunning; |
|||
private ActivityDmsBinding dmsBinding; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater()); |
|||
setContentView(dmsBinding.getRoot()); |
|||
|
|||
dmsBinding.swipeRefreshLayout.setOnRefreshListener(this); |
|||
|
|||
final LinearLayoutManager layoutManager = new LinearLayoutManager(this); |
|||
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager); |
|||
dmsBinding.rvDirectMessages.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); |
|||
dmsBinding.rvDirectMessages.setAdapter(messagesAdapter); |
|||
|
|||
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { |
|||
if (!Utils.isEmpty(endCursor)) |
|||
currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
endCursor = null; |
|||
}); |
|||
|
|||
dmsBinding.rvDirectMessages.addOnScrollListener(lazyLoader); |
|||
|
|||
stopCurrentExecutor(); |
|||
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
@Override |
|||
public void onRefresh() { |
|||
endCursor = null; |
|||
lazyLoader.resetState(); |
|||
inboxThreadModelList.clear(); |
|||
messagesAdapter.notifyDataSetChanged(); |
|||
|
|||
stopCurrentExecutor(); |
|||
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void stopCurrentExecutor() { |
|||
if (currentlyRunning != null) { |
|||
try { |
|||
currentlyRunning.cancel(true); |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,101 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.util.Log; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AppCompatActivity; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.MessageItemsAdapter; |
|||
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher; |
|||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; |
|||
import awais.instagrabber.databinding.ActivityDmsBinding; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel; |
|||
import awais.instagrabber.models.direct_messages.InboxThreadModel; |
|||
import awais.instagrabber.models.enums.UserInboxDirection; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class DirectMessagesUserInbox extends AppCompatActivity { |
|||
private final ArrayList<ProfileModel> users = new ArrayList<>(); |
|||
private final ArrayList<DirectItemModel> directItemModels = new ArrayList<>(); |
|||
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() { |
|||
@Override |
|||
public void doBefore() { |
|||
dmsBinding.swipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final InboxThreadModel result) { |
|||
if (result == null && "MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor) || Utils.isEmpty(endCursor)) |
|||
Toast.makeText(DirectMessagesUserInbox.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); |
|||
|
|||
if (result != null) { |
|||
endCursor = result.getPrevCursor(); |
|||
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null; |
|||
|
|||
users.clear(); |
|||
users.addAll(Arrays.asList(result.getUsers())); |
|||
|
|||
final int oldSize = directItemModels.size(); |
|||
final List<DirectItemModel> itemModels = Arrays.asList(result.getItems()); |
|||
directItemModels.addAll(itemModels); |
|||
messageItemsAdapter.notifyItemRangeInserted(oldSize, itemModels.size()); |
|||
} |
|||
|
|||
dmsBinding.swipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
}; |
|||
private String endCursor; |
|||
private ActivityDmsBinding dmsBinding; |
|||
private MessageItemsAdapter messageItemsAdapter; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater()); |
|||
setContentView(dmsBinding.getRoot()); |
|||
|
|||
final InboxThreadModel threadModel; |
|||
final Intent intent = getIntent(); |
|||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_THREAD_MODEL) || |
|||
(threadModel = (InboxThreadModel) intent.getSerializableExtra(Constants.EXTRAS_THREAD_MODEL)) == null) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
dmsBinding.swipeRefreshLayout.setEnabled(false); |
|||
|
|||
final LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, true); |
|||
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager); |
|||
|
|||
dmsBinding.rvDirectMessages.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { |
|||
if (!Utils.isEmpty(endCursor)) { |
|||
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER, |
|||
endCursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered |
|||
} |
|||
})); |
|||
|
|||
dmsBinding.rvDirectMessages.setAdapter(messageItemsAdapter = new MessageItemsAdapter(directItemModels, users, v -> { |
|||
// todo do something with clicked message |
|||
Log.d("AWAISKING_APP", "--> " + v.getTag()); |
|||
}, (view, text, isHashtag) -> { |
|||
// todo mention click stuff |
|||
|
|||
})); |
|||
|
|||
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
} |
@ -0,0 +1,348 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.content.res.Resources; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.util.Log; |
|||
import android.view.Menu; |
|||
import android.view.MenuItem; |
|||
import android.view.View; |
|||
import android.widget.ArrayAdapter; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.appcompat.widget.SearchView; |
|||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Arrays; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.FollowAdapter; |
|||
import awais.instagrabber.asyncs.FollowFetcher; |
|||
import awais.instagrabber.databinding.ActivityFollowBinding; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.FollowModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
import thoughtbot.expandableadapter.ExpandableGroup; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class FollowViewer extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener { |
|||
private final ArrayList<FollowModel> followModels = new ArrayList<>(); |
|||
private final ArrayList<FollowModel> followingModels = new ArrayList<>(); |
|||
private final ArrayList<FollowModel> followersModels = new ArrayList<>(); |
|||
private final ArrayList<FollowModel> allFollowing = new ArrayList<>(); |
|||
private boolean followers, isCompare = false; |
|||
private String id, name, namePost, type; |
|||
private Resources resources; |
|||
private FollowModel model; |
|||
private FollowAdapter adapter; |
|||
private View.OnClickListener clickListener; |
|||
private ActivityFollowBinding followBinding; |
|||
private AsyncTask<Void, Void, FollowModel[]> currentlyExecuting; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
followBinding = ActivityFollowBinding.inflate(getLayoutInflater()); |
|||
setContentView(followBinding.getRoot()); |
|||
|
|||
final Intent intent = getIntent(); |
|||
if (intent == null || Utils.isEmpty(id = intent.getStringExtra(Constants.EXTRAS_ID))) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
setSupportActionBar(followBinding.toolbar.toolbar); |
|||
|
|||
followers = intent.getBooleanExtra(Constants.EXTRAS_FOLLOWERS, false); |
|||
name = intent.getStringExtra(Constants.EXTRAS_NAME); |
|||
namePost = name + " is"; |
|||
if (Utils.isEmpty(name)) { |
|||
name = "You"; |
|||
namePost = "You're"; |
|||
} |
|||
|
|||
followBinding.toolbar.toolbar.setTitle(name); |
|||
|
|||
resources = getResources(); |
|||
final ArrayAdapter<Object> adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{ |
|||
resources.getString(R.string.open_profile), resources.getString(R.string.followers_open_in_insta)}); |
|||
final AlertDialog alertDialog = new AlertDialog.Builder(this).setAdapter(adapter, (dialog, which) -> { |
|||
if (model != null) { |
|||
if (which == 0) { |
|||
if (Main.scanHack != null) { |
|||
Main.scanHack.onResult(model.getUsername()); |
|||
finish(); |
|||
} |
|||
} else { |
|||
final Intent actIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://instagram.com/" + model.getUsername())); |
|||
if (Utils.isInstagramInstalled) actIntent.setPackage("com.instagram.android"); |
|||
startActivity(actIntent); |
|||
} |
|||
} |
|||
}).setTitle(R.string.what_to_do_dialog).create(); |
|||
|
|||
clickListener = v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof FollowModel) { |
|||
model = (FollowModel) tag; |
|||
if (!alertDialog.isShowing()) alertDialog.show(); |
|||
} |
|||
}; |
|||
|
|||
followBinding.swipeRefreshLayout.setOnRefreshListener(this); |
|||
|
|||
onRefresh(); |
|||
} |
|||
|
|||
@Override |
|||
public void onRefresh() { |
|||
if (isCompare) listCompare(); |
|||
else listFollows(); |
|||
} |
|||
|
|||
private void listFollows() { |
|||
stopCurrentExecutor(); |
|||
|
|||
type = resources.getString(followers ? R.string.followers_type_followers : R.string.followers_type_following); |
|||
followBinding.toolbar.toolbar.setSubtitle(type); |
|||
|
|||
followModels.clear(); |
|||
|
|||
final FetchListener<FollowModel[]> fetchListener = new FetchListener<FollowModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
followBinding.swipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final FollowModel[] result) { |
|||
if (result == null) followBinding.swipeRefreshLayout.setRefreshing(false); |
|||
else { |
|||
followModels.addAll(Arrays.asList(result)); |
|||
|
|||
final FollowModel model = result[result.length - 1]; |
|||
if (model != null && model.hasNextPage()) { |
|||
stopCurrentExecutor(); |
|||
currentlyExecuting = new FollowFetcher(id, followers, model.getEndCursor(), this) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
model.setPageCursor(false, null); |
|||
} else { |
|||
followBinding.swipeRefreshLayout.setRefreshing(false); |
|||
|
|||
refreshAdapter(followModels, null, null, null); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
currentlyExecuting = new FollowFetcher(id, followers, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void listCompare() { |
|||
stopCurrentExecutor(); |
|||
|
|||
followBinding.toolbar.toolbar.setSubtitle(R.string.followers_compare); |
|||
|
|||
allFollowing.clear(); |
|||
followersModels.clear(); |
|||
followingModels.clear(); |
|||
|
|||
final FetchListener<FollowModel[]> followingFetchListener = new FetchListener<FollowModel[]>() { |
|||
@Override |
|||
public void onResult(final FollowModel[] result) { |
|||
if (result != null) { |
|||
followingModels.addAll(Arrays.asList(result)); |
|||
|
|||
final FollowModel model = result[result.length - 1]; |
|||
if (model != null && model.hasNextPage()) { |
|||
stopCurrentExecutor(); |
|||
currentlyExecuting = new FollowFetcher(id, false, model.getEndCursor(), this) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
model.setPageCursor(false, null); |
|||
} else { |
|||
allFollowing.addAll(followersModels); |
|||
allFollowing.retainAll(followingModels); |
|||
|
|||
for (final FollowModel followModel : allFollowing) { |
|||
followersModels.remove(followModel); |
|||
followingModels.remove(followModel); |
|||
} |
|||
|
|||
allFollowing.trimToSize(); |
|||
followersModels.trimToSize(); |
|||
followingModels.trimToSize(); |
|||
|
|||
followBinding.swipeRefreshLayout.setRefreshing(false); |
|||
|
|||
refreshAdapter(null, followingModels, followersModels, allFollowing); |
|||
} |
|||
} else followBinding.swipeRefreshLayout.setRefreshing(false); |
|||
} |
|||
}; |
|||
final FetchListener<FollowModel[]> followersFetchListener = new FetchListener<FollowModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
followBinding.swipeRefreshLayout.setRefreshing(true); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final FollowModel[] result) { |
|||
if (result != null) { |
|||
followersModels.addAll(Arrays.asList(result)); |
|||
final FollowModel model = result[result.length - 1]; |
|||
if (model == null || !model.hasNextPage()) { |
|||
stopCurrentExecutor(); |
|||
currentlyExecuting = new FollowFetcher(id, false, followingFetchListener) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} else { |
|||
stopCurrentExecutor(); |
|||
currentlyExecuting = new FollowFetcher(id, true, model.getEndCursor(), this) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
model.setPageCursor(false, null); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
|
|||
currentlyExecuting = new FollowFetcher(id, true, followersFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onCreateOptionsMenu(final Menu menu) { |
|||
getMenuInflater().inflate(R.menu.follow, menu); |
|||
|
|||
final MenuItem menuSearch = menu.findItem(R.id.action_search); |
|||
|
|||
final SearchView searchView = (SearchView) menuSearch.getActionView(); |
|||
searchView.setQueryHint(getResources().getString(R.string.action_search)); |
|||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
|||
// private final Filter filter = new Filter() { |
|||
// private final ArrayList<FollowModel> searchFollowModels = new ArrayList<>(followModels.size() / 2); |
|||
// private final ArrayList<FollowModel> searchFollowingModels = new ArrayList<>(followingModels.size() / 2); |
|||
// private final ArrayList<FollowModel> searchFollowersModels = new ArrayList<>(followersModels.size() / 2); |
|||
// private final ArrayList<FollowModel> searchAllFollowing = new ArrayList<>(allFollowing.size() / 2); |
|||
// |
|||
// @Nullable |
|||
// @Override |
|||
// protected FilterResults performFiltering(@NonNull final CharSequence constraint) { |
|||
// searchFollowModels.clear(); |
|||
// searchFollowingModels.clear(); |
|||
// searchFollowersModels.clear(); |
|||
// searchAllFollowing.clear(); |
|||
// |
|||
// final int followModelsSize = followModels.size(); |
|||
// final int followingModelsSize = followingModels.size(); |
|||
// final int followersModelsSize = followersModels.size(); |
|||
// final int allFollowingSize = allFollowing.size(); |
|||
// |
|||
// int maxSize = followModelsSize; |
|||
// if (maxSize < followingModelsSize) maxSize = followingModelsSize; |
|||
// if (maxSize < followersModelsSize) maxSize = followersModelsSize; |
|||
// if (maxSize < allFollowingSize) maxSize = allFollowingSize; |
|||
// |
|||
// final String query = constraint.toString().toLowerCase(); |
|||
// FollowModel followModel; |
|||
// while (maxSize != -1) { |
|||
// if (maxSize < followModelsSize) { |
|||
// followModel = followModels.get(maxSize); |
|||
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName())) |
|||
// searchFollowModels.add(followModel); |
|||
// } |
|||
// |
|||
// if (maxSize < followingModelsSize) { |
|||
// followModel = followingModels.get(maxSize); |
|||
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName())) |
|||
// searchFollowingModels.add(followModel); |
|||
// } |
|||
// |
|||
// if (maxSize < followersModelsSize) { |
|||
// followModel = followersModels.get(maxSize); |
|||
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName())) |
|||
// searchFollowersModels.add(followModel); |
|||
// } |
|||
// |
|||
// if (maxSize < allFollowingSize) { |
|||
// followModel = allFollowing.get(maxSize); |
|||
// if (Utils.hasKey(query, followModel.getUsername(), followModel.getFullName())) |
|||
// searchAllFollowing.add(followModel); |
|||
// } |
|||
// |
|||
// --maxSize; |
|||
// } |
|||
// |
|||
// return null; |
|||
// } |
|||
// |
|||
// @Override |
|||
// protected void publishResults(final CharSequence query, final FilterResults results) { |
|||
// refreshAdapter(searchFollowModels, searchFollowingModels, searchFollowersModels, searchAllFollowing); |
|||
// } |
|||
// }; |
|||
|
|||
@Override |
|||
public boolean onQueryTextSubmit(final String query) { |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onQueryTextChange(final String query) { |
|||
// if (Utils.isEmpty(query)) refreshAdapter(followModels, followingModels, followersModels, allFollowing); |
|||
// else filter.filter(query.toLowerCase()); |
|||
if (adapter != null) adapter.getFilter().filter(query); |
|||
return true; |
|||
} |
|||
}); |
|||
|
|||
final MenuItem menuCompare = menu.findItem(R.id.action_compare); |
|||
menuCompare.setOnMenuItemClickListener(item -> { |
|||
followBinding.rvFollow.setAdapter(null); |
|||
if (isCompare) listFollows(); |
|||
else listCompare(); |
|||
isCompare = !isCompare; |
|||
return true; |
|||
}); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
private void refreshAdapter(final ArrayList<FollowModel> followModels, final ArrayList<FollowModel> followingModels, |
|||
final ArrayList<FollowModel> followersModels, final ArrayList<FollowModel> allFollowing) { |
|||
final ArrayList<ExpandableGroup> groups = new ArrayList<>(1); |
|||
|
|||
if (isCompare) { |
|||
if (followingModels.size() > 0) |
|||
groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_following, name), followingModels)); |
|||
if (followersModels.size() > 0) |
|||
groups.add(new ExpandableGroup(resources.getString(R.string.followers_not_follower, namePost), followersModels)); |
|||
if (allFollowing.size() > 0) |
|||
groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing)); |
|||
} else { |
|||
final ExpandableGroup group = new ExpandableGroup(type, followModels); |
|||
groups.add(group); |
|||
} |
|||
|
|||
adapter = new FollowAdapter(this, clickListener, groups); |
|||
adapter.toggleGroup(0); |
|||
followBinding.rvFollow.setAdapter(adapter); |
|||
} |
|||
|
|||
public void stopCurrentExecutor() { |
|||
if (currentlyExecuting != null) { |
|||
try { |
|||
currentlyExecuting.cancel(true); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,130 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.graphics.Bitmap; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.view.View; |
|||
import android.webkit.WebChromeClient; |
|||
import android.webkit.WebSettings; |
|||
import android.webkit.WebView; |
|||
import android.webkit.WebViewClient; |
|||
import android.widget.CompoundButton; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.databinding.ActivityLoginBinding; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class Login extends BaseLanguageActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener { |
|||
private final WebViewClient webViewClient = new WebViewClient() { |
|||
@Override |
|||
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) { |
|||
webViewUrl = url; |
|||
} |
|||
|
|||
@Override |
|||
public void onPageFinished(final WebView view, final String url) { |
|||
webViewUrl = url; |
|||
} |
|||
}; |
|||
private final WebChromeClient webChromeClient = new WebChromeClient(); |
|||
private String webViewUrl, defaultUserAgent; |
|||
private ActivityLoginBinding loginBinding; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
loginBinding = ActivityLoginBinding.inflate(getLayoutInflater()); |
|||
setContentView(loginBinding.getRoot()); |
|||
|
|||
initWebView(); |
|||
|
|||
loginBinding.desktopMode.setOnCheckedChangeListener(this); |
|||
loginBinding.cookies.setOnClickListener(this); |
|||
loginBinding.refresh.setOnClickListener(this); |
|||
} |
|||
|
|||
@Override |
|||
public void onClick(final View v) { |
|||
if (v == loginBinding.refresh) { |
|||
loginBinding.webView.loadUrl("https://instagram.com/"); |
|||
} else if (v == loginBinding.cookies) { |
|||
final String mainCookie = Utils.getCookie(webViewUrl); |
|||
if (Utils.isEmpty(mainCookie)) |
|||
Toast.makeText(this, R.string.login_error_loading_cookies, Toast.LENGTH_SHORT).show(); |
|||
else { |
|||
Utils.setupCookies(mainCookie); |
|||
settingsHelper.putString(Constants.COOKIE, mainCookie); |
|||
Toast.makeText(this, R.string.login_success_loading_cookies, Toast.LENGTH_SHORT).show(); |
|||
finish(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { |
|||
final WebSettings webSettings = loginBinding.webView.getSettings(); |
|||
|
|||
final String newUserAgent = isChecked ? "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36" |
|||
: defaultUserAgent; |
|||
|
|||
webSettings.setUserAgentString(newUserAgent); |
|||
webSettings.setUseWideViewPort(isChecked); |
|||
webSettings.setLoadWithOverviewMode(isChecked); |
|||
webSettings.setSupportZoom(isChecked); |
|||
webSettings.setBuiltInZoomControls(isChecked); |
|||
|
|||
loginBinding.webView.loadUrl("https://instagram.com/"); |
|||
} |
|||
|
|||
@SuppressLint("SetJavaScriptEnabled") |
|||
private void initWebView() { |
|||
if (loginBinding != null) { |
|||
loginBinding.webView.setWebChromeClient(webChromeClient); |
|||
loginBinding.webView.setWebViewClient(webViewClient); |
|||
final WebSettings webSettings = loginBinding.webView.getSettings(); |
|||
if (webSettings != null) { |
|||
if (defaultUserAgent == null) defaultUserAgent = webSettings.getUserAgentString(); |
|||
webSettings.setJavaScriptEnabled(true); |
|||
webSettings.setDomStorageEnabled(true); |
|||
webSettings.setSupportZoom(true); |
|||
webSettings.setBuiltInZoomControls(true); |
|||
webSettings.setDisplayZoomControls(false); |
|||
webSettings.setLoadWithOverviewMode(true); |
|||
webSettings.setUseWideViewPort(true); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
|||
webSettings.setAllowFileAccessFromFileURLs(true); |
|||
webSettings.setAllowUniversalAccessFromFileURLs(true); |
|||
} |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) |
|||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); |
|||
} |
|||
|
|||
loginBinding.webView.loadUrl("https://instagram.com/"); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPause() { |
|||
if (loginBinding != null) loginBinding.webView.onPause(); |
|||
super.onPause(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
super.onResume(); |
|||
if (loginBinding != null) loginBinding.webView.onResume(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDestroy() { |
|||
if (loginBinding != null) loginBinding.webView.destroy(); |
|||
super.onDestroy(); |
|||
} |
|||
} |
@ -0,0 +1,480 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.content.pm.PackageManager; |
|||
import android.content.res.Resources; |
|||
import android.database.MatrixCursor; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.os.PersistableBundle; |
|||
import android.provider.BaseColumns; |
|||
import android.view.Menu; |
|||
import android.view.MenuItem; |
|||
import android.view.View; |
|||
import android.widget.ArrayAdapter; |
|||
import android.widget.AutoCompleteTextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.appcompat.widget.SearchView; |
|||
import androidx.fragment.app.FragmentManager; |
|||
import androidx.recyclerview.widget.GridLayoutManager; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Stack; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.MainHelper; |
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.HighlightsAdapter; |
|||
import awais.instagrabber.adapters.SuggestionsAdapter; |
|||
import awais.instagrabber.asyncs.SuggestionsFetcher; |
|||
import awais.instagrabber.asyncs.UsernameFetcher; |
|||
import awais.instagrabber.customviews.MouseDrawer; |
|||
import awais.instagrabber.databinding.ActivityMainBinding; |
|||
import awais.instagrabber.dialogs.AboutDialog; |
|||
import awais.instagrabber.dialogs.QuickAccessDialog; |
|||
import awais.instagrabber.dialogs.SettingsDialog; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.interfaces.ItemGetter; |
|||
import awais.instagrabber.models.DiscoverItemModel; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.HighlightModel; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.StoryModel; |
|||
import awais.instagrabber.models.SuggestionModel; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
import awais.instagrabber.models.enums.ItemGetType; |
|||
import awais.instagrabber.models.enums.SuggestionType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DataBox; |
|||
import awais.instagrabber.utils.FlavorTown; |
|||
import awais.instagrabber.utils.MyApps; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class Main extends BaseLanguageActivity { |
|||
public static FetchListener<String> scanHack; |
|||
public static ItemGetter itemGetter; |
|||
// -------- items -------- |
|||
public final ArrayList<PostModel> allItems = new ArrayList<>(); |
|||
public final ArrayList<FeedModel> feedItems = new ArrayList<>(); |
|||
public final ArrayList<DiscoverItemModel> discoverItems = new ArrayList<>(); |
|||
// -------- items -------- |
|||
public final ArrayList<PostModel> selectedItems = new ArrayList<>(); |
|||
public final ArrayList<DiscoverItemModel> selectedDiscoverItems = new ArrayList<>(); |
|||
// -------- items -------- |
|||
public final HighlightsAdapter highlightsAdapter = new HighlightsAdapter(null, new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(final View v) { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof HighlightModel) { |
|||
final HighlightModel highlightModel = (HighlightModel) tag; |
|||
startActivity(new Intent(Main.this, StoryViewer.class) |
|||
.putExtra(Constants.EXTRAS_USERNAME, userQuery) |
|||
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle()) |
|||
.putExtra(Constants.EXTRAS_STORIES, highlightModel.getStoryModels())); |
|||
} |
|||
} |
|||
}); |
|||
private SuggestionsAdapter suggestionAdapter; |
|||
private MenuItem searchAction; |
|||
public ActivityMainBinding mainBinding; |
|||
public SearchView searchView; |
|||
public MenuItem downloadAction, settingsAction, dmsAction; |
|||
public StoryModel[] storyModels; |
|||
public String userQuery = null; |
|||
public MainHelper mainHelper; |
|||
public ProfileModel profileModel; |
|||
private AutoCompleteTextView searchAutoComplete; |
|||
private ArrayAdapter<String> profileDialogAdapter; |
|||
private DialogInterface.OnClickListener profileDialogListener; |
|||
private Stack<String> queriesStack; |
|||
|
|||
public Main() { |
|||
super(); |
|||
Utils.changeTheme(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle bundle) { |
|||
super.onCreate(bundle); |
|||
mainBinding = ActivityMainBinding.inflate(getLayoutInflater()); |
|||
setContentView(mainBinding.getRoot()); |
|||
|
|||
FlavorTown.updateCheck(this); |
|||
FlavorTown.changelogCheck(this); |
|||
|
|||
final String cookie = settingsHelper.getString(Constants.COOKIE); |
|||
final String uid = Utils.getUserIdFromCookie(cookie); |
|||
Utils.setupCookies(cookie); |
|||
|
|||
MainHelper.stopCurrentExecutor(); |
|||
mainHelper = new MainHelper(this); |
|||
if (bundle == null) { |
|||
queriesStack = new Stack<>(); |
|||
userQuery = null; |
|||
} else { |
|||
setStack(bundle); |
|||
userQuery = bundle.getString("query"); |
|||
} |
|||
|
|||
itemGetter = itemGetType -> { |
|||
if (itemGetType == ItemGetType.MAIN_ITEMS) return allItems; |
|||
if (itemGetType == ItemGetType.DISCOVER_ITEMS) return discoverItems; |
|||
if (itemGetType == ItemGetType.FEED_ITEMS) return feedItems; |
|||
return null; |
|||
}; |
|||
|
|||
scanHack = result -> { |
|||
if (mainHelper != null && !Utils.isEmpty(result)) { |
|||
closeAnyOpenDrawer(); |
|||
addToStack(); |
|||
userQuery = result; |
|||
mainHelper.onRefresh(); |
|||
} |
|||
}; |
|||
|
|||
// searches for your userid and returns username |
|||
if (uid != null) { |
|||
final FetchListener<String> fetchListener = username -> { |
|||
if (!Utils.isEmpty(username)) { |
|||
if (!BuildConfig.DEBUG) { |
|||
userQuery = username; |
|||
if (mainHelper != null && !mainBinding.swipeRefreshLayout.isRefreshing()) mainHelper.onRefresh(); |
|||
} |
|||
// adds cookies to database for quick access |
|||
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid); |
|||
if (Utils.dataBox.getCookieCount() == 0 || cookieModel == null || Utils.isEmpty(cookieModel.getUsername())) |
|||
Utils.dataBox.addUserCookie(new DataBox.CookieModel(uid, username, cookie)); |
|||
} |
|||
}; |
|||
boolean found = false; |
|||
final DataBox.CookieModel cookieModel = Utils.dataBox.getCookie(uid); |
|||
if (cookieModel != null) { |
|||
final String username = cookieModel.getUsername(); |
|||
if (username != null) { |
|||
found = true; |
|||
fetchListener.onResult(username); |
|||
} |
|||
} |
|||
|
|||
if (!found) // if not in database, fetch info from instagram |
|||
new UsernameFetcher(uid, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
suggestionAdapter = new SuggestionsAdapter(this, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof CharSequence) { |
|||
addToStack(); |
|||
userQuery = tag.toString(); |
|||
mainHelper.onRefresh(); |
|||
} |
|||
if (searchView != null && !searchView.isIconified()) { |
|||
if (searchAction != null) searchAction.collapseActionView(); |
|||
searchView.setIconified(true); |
|||
searchView.setIconified(true); |
|||
} |
|||
}); |
|||
|
|||
final Resources resources = getResources(); |
|||
profileDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, |
|||
new String[]{resources.getString(R.string.view_pfp), resources.getString(R.string.show_stories)}); |
|||
profileDialogListener = (dialog, which) -> { |
|||
final Intent intent; |
|||
if (which == 0 || storyModels == null || storyModels.length < 1) |
|||
intent = new Intent(this, ProfileViewer.class).putExtra(Constants.EXTRAS_PROFILE, profileModel); |
|||
else intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery) |
|||
.putExtra(Constants.EXTRAS_STORIES, storyModels); |
|||
startActivity(intent); |
|||
}; |
|||
|
|||
final View.OnClickListener onClickListener = v -> { |
|||
if (v == mainBinding.mainBiography) { |
|||
Utils.copyText(this, mainBinding.mainBiography.getText().toString()); |
|||
} else if (v == mainBinding.mainProfileImage) { |
|||
if (storyModels == null || storyModels.length <= 0) { |
|||
profileDialogListener.onClick(null, 0); |
|||
} else { |
|||
// because sometimes configuration changes made this crash on some phones |
|||
new AlertDialog.Builder(this).setAdapter(profileDialogAdapter, profileDialogListener) |
|||
.setNeutralButton(R.string.cancel, null).show(); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
mainBinding.mainBiography.setOnClickListener(onClickListener); |
|||
mainBinding.mainProfileImage.setOnClickListener(onClickListener); |
|||
|
|||
mainBinding.mainBiography.setEnabled(false); |
|||
mainBinding.mainProfileImage.setEnabled(false); |
|||
|
|||
final boolean isQueryNull = userQuery == null; |
|||
if (isQueryNull) allItems.clear(); |
|||
if (BuildConfig.DEBUG && isQueryNull) userQuery = "the.badak"; // todo |
|||
if (!mainBinding.swipeRefreshLayout.isRefreshing() && userQuery != null) mainHelper.onRefresh(); |
|||
|
|||
mainHelper.onIntent(getIntent()); |
|||
} |
|||
|
|||
private void downloadSelectedItems() { |
|||
if (selectedItems.size() > 0) { |
|||
Utils.batchDownload(this, userQuery, DownloadMethod.DOWNLOAD_MAIN, selectedItems); |
|||
} else if (selectedDiscoverItems.size() > 0) { |
|||
Utils.batchDownload(this, null, DownloadMethod.DOWNLOAD_DISCOVER, selectedDiscoverItems); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onNewIntent(final Intent intent) { |
|||
super.onNewIntent(intent); |
|||
mainHelper.onIntent(intent); |
|||
} |
|||
|
|||
@Override |
|||
public void onSaveInstanceState(@NonNull final Bundle outState, @NonNull final PersistableBundle outPersistentState) { |
|||
outState.putString("query", userQuery); |
|||
outState.putSerializable("stack", queriesStack); |
|||
super.onSaveInstanceState(outState, outPersistentState); |
|||
} |
|||
|
|||
@Override |
|||
public void onRestoreInstanceState(@Nullable final Bundle savedInstanceState, @Nullable final PersistableBundle persistentState) { |
|||
super.onRestoreInstanceState(savedInstanceState, persistentState); |
|||
if (savedInstanceState != null) { |
|||
userQuery = savedInstanceState.getString("query"); |
|||
setStack(savedInstanceState); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onSaveInstanceState(@NonNull final Bundle outState) { |
|||
outState.putString("query", userQuery); |
|||
outState.putSerializable("stack", queriesStack); |
|||
super.onSaveInstanceState(outState); |
|||
} |
|||
|
|||
@Override |
|||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) { |
|||
super.onRestoreInstanceState(savedInstanceState); |
|||
userQuery = savedInstanceState.getString("query"); |
|||
setStack(savedInstanceState); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onCreateOptionsMenu(final Menu menu) { |
|||
getMenuInflater().inflate(R.menu.menu, menu); |
|||
|
|||
final FragmentManager fragmentManager = getSupportFragmentManager(); |
|||
final MenuItem quickAccessAction = menu.findItem(R.id.action_quickaccess).setVisible(true); |
|||
|
|||
final MenuItem.OnMenuItemClickListener clickListener = item -> { |
|||
if (item == downloadAction) { |
|||
downloadSelectedItems(); |
|||
} else if (item == dmsAction) |
|||
startActivity(new Intent(this, DirectMessages.class)); |
|||
else if (item == settingsAction) |
|||
new SettingsDialog().show(fragmentManager, "settings"); |
|||
else if (item == quickAccessAction) |
|||
new QuickAccessDialog().setQuery(userQuery).show(fragmentManager, "quickAccess"); |
|||
else |
|||
new AboutDialog().show(fragmentManager, "about"); |
|||
return true; |
|||
}; |
|||
|
|||
quickAccessAction.setOnMenuItemClickListener(clickListener); |
|||
menu.findItem(R.id.action_about).setVisible(true).setOnMenuItemClickListener(clickListener); |
|||
dmsAction = menu.findItem(R.id.action_dms).setOnMenuItemClickListener(clickListener); |
|||
settingsAction = menu.findItem(R.id.action_settings).setVisible(true).setOnMenuItemClickListener(clickListener); |
|||
downloadAction = menu.findItem(R.id.action_download).setOnMenuItemClickListener(clickListener); |
|||
|
|||
if (!Utils.isEmpty(Utils.settingsHelper.getString(Constants.COOKIE))) { |
|||
settingsAction.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); |
|||
dmsAction.setVisible(true).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); |
|||
} |
|||
|
|||
searchAction = menu.findItem(R.id.action_search); |
|||
searchView = (SearchView) searchAction.getActionView(); |
|||
final View searchText = searchView.findViewById(R.id.search_src_text); |
|||
if (searchText instanceof AutoCompleteTextView) |
|||
searchAutoComplete = (AutoCompleteTextView) searchText; |
|||
|
|||
searchView.setQueryHint(getResources().getString(R.string.action_search)); |
|||
searchView.setSuggestionsAdapter(suggestionAdapter); |
|||
searchView.setOnSearchClickListener(v -> searchView.setQuery(userQuery, false)); |
|||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { |
|||
private boolean searchUser, searchHash; |
|||
private AsyncTask<?, ?, ?> prevSuggestionAsync; |
|||
private final String[] COLUMNS = {BaseColumns._ID, Constants.EXTRAS_USERNAME, Constants.EXTRAS_NAME, |
|||
Constants.EXTRAS_TYPE, "pfp", "verified"}; |
|||
private final FetchListener<SuggestionModel[]> fetchListener = new FetchListener<SuggestionModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
suggestionAdapter.changeCursor(null); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final SuggestionModel[] result) { |
|||
final MatrixCursor cursor; |
|||
if (result == null) cursor = null; |
|||
else { |
|||
cursor = new MatrixCursor(COLUMNS, 0); |
|||
for (int i = 0; i < result.length; i++) { |
|||
final SuggestionModel suggestionModel = result[i]; |
|||
if (suggestionModel != null) { |
|||
final SuggestionType suggestionType = suggestionModel.getSuggestionType(); |
|||
final Object[] objects = {i, suggestionModel.getUsername(), suggestionModel.getName(), |
|||
suggestionType, suggestionModel.getProfilePic(), suggestionModel.isVerified()}; |
|||
|
|||
if (!searchHash && !searchUser) cursor.addRow(objects); |
|||
else { |
|||
final boolean isCurrHash = suggestionType == SuggestionType.TYPE_HASHTAG; |
|||
if (searchHash && isCurrHash || !searchHash && !isCurrHash) |
|||
cursor.addRow(objects); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
suggestionAdapter.changeCursor(cursor); |
|||
} |
|||
}; |
|||
|
|||
private void cancelSuggestionsAsync() { |
|||
if (prevSuggestionAsync != null) |
|||
try { prevSuggestionAsync.cancel(true); } catch (final Exception ignored) { } |
|||
} |
|||
|
|||
@Override |
|||
public boolean onQueryTextSubmit(final String query) { |
|||
cancelSuggestionsAsync(); |
|||
|
|||
closeAnyOpenDrawer(); |
|||
addToStack(); |
|||
userQuery = query; |
|||
searchAction.collapseActionView(); |
|||
searchView.setIconified(true); |
|||
searchView.setIconified(true); |
|||
mainHelper.onRefresh(); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onQueryTextChange(final String newText) { |
|||
cancelSuggestionsAsync(); |
|||
|
|||
if (!Utils.isEmpty(newText)) { |
|||
searchUser = newText.charAt(0) == '@'; |
|||
searchHash = newText.charAt(0) == '#'; |
|||
|
|||
if (newText.length() == 1 && (searchHash || searchUser)) { |
|||
if (searchAutoComplete != null) searchAutoComplete.setThreshold(2); |
|||
} else { |
|||
if (searchAutoComplete != null) searchAutoComplete.setThreshold(1); |
|||
prevSuggestionAsync = new SuggestionsFetcher(fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, |
|||
searchUser || searchHash ? newText.substring(1) : newText); |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
}); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public void onBackPressed() { |
|||
if (closeAnyOpenDrawer()) return; |
|||
|
|||
if (searchView != null && !searchView.isIconified()) { |
|||
if (searchAction != null) searchAction.collapseActionView(); |
|||
searchView.setIconified(true); |
|||
searchView.setIconified(true); |
|||
return; |
|||
} |
|||
|
|||
if (!mainHelper.isSelectionCleared()) return; |
|||
|
|||
final GridLayoutManager layoutManager = (GridLayoutManager) mainBinding.mainPosts.getLayoutManager(); |
|||
if (layoutManager != null && layoutManager.findFirstCompletelyVisibleItemPosition() >= layoutManager.getSpanCount()) { |
|||
mainBinding.mainPosts.smoothScrollToPosition(0); |
|||
mainBinding.appBarLayout.setExpanded(true, true); |
|||
return; |
|||
} |
|||
|
|||
if (queriesStack != null && queriesStack.size() > 0) { |
|||
userQuery = queriesStack.pop(); |
|||
if (userQuery != null) { |
|||
mainHelper.onRefresh(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
MyApps.showAlertDialog(this, (parent, view, position, id) -> { |
|||
if (id == -1 && position == -1 && parent == null) super.onBackPressed(); |
|||
else MyApps.openAppStore(this, position); |
|||
}); |
|||
} |
|||
|
|||
@Override |
|||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { |
|||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
|||
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED) |
|||
downloadSelectedItems(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { |
|||
super.onActivityResult(requestCode, resultCode, data); |
|||
if (requestCode == 9629 && (resultCode == 1692 || resultCode == RESULT_CANCELED)) |
|||
finish(); |
|||
else if (requestCode == 6007) |
|||
Utils.showImportExportDialog(this); |
|||
else if (requestCode == 6969 && mainHelper.currentFeedPlayer != null) |
|||
mainHelper.currentFeedPlayer.setPlayWhenReady(true); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPause() { |
|||
if (mainHelper != null) mainHelper.onPause(); |
|||
super.onPause(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
if (mainHelper != null) mainHelper.onResume(); |
|||
super.onResume(); |
|||
} |
|||
|
|||
private void setStack(final Bundle bundle) { |
|||
final Object stack = bundle != null ? bundle.get("stack") : null; |
|||
if (stack instanceof Stack) //noinspection unchecked |
|||
queriesStack = (Stack<String>) stack; |
|||
} |
|||
|
|||
public void addToStack() { |
|||
if (userQuery != null) { |
|||
if (queriesStack == null) queriesStack = new Stack<>(); |
|||
queriesStack.add(userQuery); |
|||
} |
|||
} |
|||
|
|||
private boolean closeAnyOpenDrawer() { |
|||
final int childCount = mainBinding.drawerLayout.getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = mainBinding.drawerLayout.getChildAt(i); |
|||
final MouseDrawer.LayoutParams childLp = (MouseDrawer.LayoutParams) child.getLayoutParams(); |
|||
|
|||
if ((childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENED) == 1 || |
|||
(childLp.openState & MouseDrawer.LayoutParams.FLAG_IS_OPENING) == 2 || |
|||
childLp.onScreen >= 0.6 || childLp.isPeeking) { |
|||
mainBinding.drawerLayout.closeDrawer(child); |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,639 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.content.pm.PackageManager; |
|||
import android.content.res.Resources; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.os.Handler; |
|||
import android.text.SpannableString; |
|||
import android.text.method.LinkMovementMethod; |
|||
import android.util.Log; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.widget.ArrayAdapter; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.core.app.ActivityCompat; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.core.view.GestureDetectorCompat; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
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 com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.source.MediaSource; |
|||
import com.google.android.exoplayer2.source.MediaSourceEventListener; |
|||
import com.google.android.exoplayer2.source.ProgressiveMediaSource; |
|||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.PostsMediaAdapter; |
|||
import awais.instagrabber.asyncs.PostFetcher; |
|||
import awais.instagrabber.asyncs.ProfileFetcher; |
|||
import awais.instagrabber.customviews.CommentMentionClickSpan; |
|||
import awais.instagrabber.customviews.helpers.SwipeGestureListener; |
|||
import awais.instagrabber.databinding.ActivityViewerBinding; |
|||
import awais.instagrabber.interfaces.SwipeEvent; |
|||
import awais.instagrabber.models.BasePostModel; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
import awais.instagrabber.models.enums.ItemGetType; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class PostViewer extends BaseLanguageActivity { |
|||
private ActivityViewerBinding viewerBinding; |
|||
private String url, prevUsername, commentsEndCursor; |
|||
private ProfileModel profileModel; |
|||
private BasePostModel postModel; |
|||
private ViewerPostModel viewerPostModel; |
|||
private SimpleExoPlayer player; |
|||
private ArrayAdapter<String> profileDialogAdapter; |
|||
private View viewsContainer, viewerCaptionParent; |
|||
private GestureDetectorCompat gestureDetector; |
|||
private SwipeEvent swipeEvent; |
|||
private CharSequence postCaption = null, postShortCode; |
|||
private Resources resources; |
|||
private boolean session = false, isFromShare; |
|||
private int slidePos = 0, lastSlidePos = 0; |
|||
private ItemGetType itemGetType; |
|||
@SuppressLint("ClickableViewAccessibility") |
|||
final View.OnTouchListener gestureTouchListener = new View.OnTouchListener() { |
|||
private float startX; |
|||
private float startY; |
|||
|
|||
@Override |
|||
public boolean onTouch(final View v, final MotionEvent event) { |
|||
if (v == viewerCaptionParent) { |
|||
switch (event.getAction()) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
startX = event.getX(); |
|||
startY = event.getY(); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_UP: |
|||
if (!(Utils.isEmpty(postCaption) || |
|||
Math.abs(startX - event.getX()) > 50 || Math.abs(startY - event.getY()) > 50)) { |
|||
Utils.copyText(PostViewer.this, postCaption); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
return gestureDetector.onTouchEvent(event); |
|||
} |
|||
}; |
|||
private final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { |
|||
final String username = viewerPostModel.getUsername(); |
|||
|
|||
if (which == 0) { |
|||
searchUsername(username); |
|||
} else if (profileModel != null && which == 1) { |
|||
startActivity(new Intent(this, ProfileViewer.class) |
|||
.putExtra(Constants.EXTRAS_PROFILE, profileModel)); |
|||
} |
|||
}; |
|||
private final View.OnClickListener onClickListener = new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(final View v) { |
|||
if (v == viewerBinding.topPanel.ivProfilePic) { |
|||
new AlertDialog.Builder(PostViewer.this).setAdapter(profileDialogAdapter, profileDialogListener) |
|||
.setNeutralButton(R.string.cancel, null).setTitle(viewerPostModel.getUsername()).show(); |
|||
|
|||
} else if (v == viewerBinding.ivToggleFullScreen) { |
|||
toggleFullscreen(); |
|||
|
|||
final LinearLayout topPanelRoot = viewerBinding.topPanel.getRoot(); |
|||
final int iconRes; |
|||
|
|||
if (containerLayoutParams.height == 0) { |
|||
containerLayoutParams.height = LinearLayout.LayoutParams.MATCH_PARENT; |
|||
iconRes = R.drawable.ic_fullscreen_exit; |
|||
topPanelRoot.setVisibility(View.GONE); |
|||
viewerBinding.btnDownload.setVisibility(View.VISIBLE); |
|||
} else { |
|||
containerLayoutParams.height = 0; |
|||
iconRes = R.drawable.ic_fullscreen; |
|||
topPanelRoot.setVisibility(View.VISIBLE); |
|||
viewerBinding.btnDownload.setVisibility(View.GONE); |
|||
} |
|||
|
|||
viewerBinding.ivToggleFullScreen.setImageResource(iconRes); |
|||
viewerBinding.container.setLayoutParams(containerLayoutParams); |
|||
|
|||
} else if (v == viewerBinding.bottomPanel.btnMute) { |
|||
if (player != null) { |
|||
final float intVol = player.getVolume() == 0f ? 1f : 0f; |
|||
player.setVolume(intVol); |
|||
viewerBinding.bottomPanel.btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute); |
|||
Utils.sessionVolumeFull = intVol == 1f; |
|||
} |
|||
|
|||
} else { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof ViewerPostModel) { |
|||
viewerPostModel = (ViewerPostModel) tag; |
|||
slidePos = Math.max(0, viewerPostModel.getPosition()); |
|||
refreshPost(); |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
private final View.OnClickListener downloadClickListener = v -> { |
|||
if (ContextCompat.checkSelfPermission(this, Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) |
|||
showDownloadDialog(); |
|||
else |
|||
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020); |
|||
}; |
|||
private final PostsMediaAdapter mediaAdapter = new PostsMediaAdapter(null, onClickListener); |
|||
private RequestManager glideRequestManager; |
|||
private LinearLayout.LayoutParams containerLayoutParams; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
viewerBinding = ActivityViewerBinding.inflate(getLayoutInflater()); |
|||
setContentView(viewerBinding.getRoot()); |
|||
|
|||
glideRequestManager = Glide.with(this); |
|||
|
|||
final Intent intent = getIntent(); |
|||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_POST) |
|||
|| (postModel = (PostModel) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
containerLayoutParams = (LinearLayout.LayoutParams) viewerBinding.container.getLayoutParams(); |
|||
|
|||
if (intent.hasExtra(Constants.EXTRAS_TYPE)) |
|||
itemGetType = (ItemGetType) intent.getSerializableExtra(Constants.EXTRAS_TYPE); |
|||
|
|||
resources = getResources(); |
|||
|
|||
final View viewStoryPost = findViewById(R.id.viewStoryPost); |
|||
if (viewStoryPost != null) viewStoryPost.setVisibility(View.GONE); |
|||
|
|||
viewerBinding.topPanel.title.setMovementMethod(new LinkMovementMethod()); |
|||
viewerBinding.topPanel.title.setMentionClickListener((view, text, isHashtag) -> |
|||
onClickListener.onClick(viewerBinding.topPanel.ivProfilePic)); |
|||
viewerBinding.topPanel.ivProfilePic.setOnClickListener(onClickListener); |
|||
|
|||
viewerBinding.ivToggleFullScreen.setOnClickListener(onClickListener); |
|||
viewerBinding.btnDownload.setOnClickListener(downloadClickListener); |
|||
|
|||
profileDialogAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, |
|||
new String[]{resources.getString(R.string.open_profile), resources.getString(R.string.view_pfp)}); |
|||
|
|||
postModel.setPosition(intent.getIntExtra(Constants.EXTRAS_INDEX, -1)); |
|||
postShortCode = postModel.getShortCode(); |
|||
|
|||
final boolean postIdNull = postModel.getPostId() == null; |
|||
if (!postIdNull) |
|||
setupPostInfoBar(intent.getStringExtra(Constants.EXTRAS_USER), postModel.getItemType()); |
|||
|
|||
isFromShare = postModel.getPosition() == -1 || postIdNull; |
|||
|
|||
viewerCaptionParent = (View) viewerBinding.bottomPanel.viewerCaption.getParent(); |
|||
viewsContainer = (View) viewerBinding.bottomPanel.tvVideoViews.getParent(); |
|||
|
|||
viewerBinding.mediaList.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)); |
|||
viewerBinding.mediaList.setAdapter(mediaAdapter); |
|||
viewerBinding.mediaList.setVisibility(View.GONE); |
|||
|
|||
swipeEvent = isRight -> { |
|||
final List<? extends BasePostModel> itemGetterItems; |
|||
final boolean isMainSwipe; |
|||
|
|||
if (itemGetType != null && Main.itemGetter != null) { |
|||
itemGetterItems = Main.itemGetter.get(itemGetType); |
|||
isMainSwipe = !(itemGetterItems.size() < 1 || itemGetType == ItemGetType.MAIN_ITEMS && isFromShare); |
|||
} else { |
|||
itemGetterItems = null; |
|||
isMainSwipe = false; |
|||
} |
|||
|
|||
final BasePostModel[] basePostModels = mediaAdapter != null ? mediaAdapter.getPostModels() : null; |
|||
final int slides = basePostModels != null ? basePostModels.length : 0; |
|||
|
|||
int position = postModel.getPosition(); |
|||
|
|||
if (isRight) { |
|||
--slidePos; |
|||
if (!isMainSwipe && slidePos < 0) slidePos = 0; |
|||
if (slides > 0 && slidePos >= 0) { |
|||
if (basePostModels[slidePos] instanceof ViewerPostModel) { |
|||
viewerPostModel = (ViewerPostModel) basePostModels[slidePos]; |
|||
} |
|||
refreshPost(); |
|||
return; |
|||
} |
|||
if (isMainSwipe && --position < 0) position = itemGetterItems.size() - 1; |
|||
} else { |
|||
++slidePos; |
|||
if (!isMainSwipe && slidePos >= slides) slidePos = slides - 1; |
|||
if (slides > 0 && slidePos < slides) { |
|||
if (basePostModels[slidePos] instanceof ViewerPostModel) { |
|||
viewerPostModel = (ViewerPostModel) basePostModels[slidePos]; |
|||
} |
|||
refreshPost(); |
|||
return; |
|||
} |
|||
if (isMainSwipe && ++position >= itemGetterItems.size()) position = 0; |
|||
} |
|||
|
|||
if (isMainSwipe) { |
|||
slidePos = 0; |
|||
Log.d("AWAISKING_APP", "swipe left <<< post[" + position + "]: " + postModel + " -- " + slides); |
|||
postModel = itemGetterItems.get(position); |
|||
postModel.setPosition(position); |
|||
viewPost(); |
|||
} |
|||
}; |
|||
gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent)); |
|||
|
|||
viewPost(); |
|||
} |
|||
|
|||
private void viewPost() { |
|||
lastSlidePos = 0; |
|||
mediaAdapter.setData(null); |
|||
viewsContainer.setVisibility(View.GONE); |
|||
viewerCaptionParent.setVisibility(View.GONE); |
|||
viewerBinding.mediaList.setVisibility(View.GONE); |
|||
viewerBinding.btnDownload.setVisibility(View.GONE); |
|||
viewerBinding.bottomPanel.btnMute.setVisibility(View.GONE); |
|||
viewerBinding.bottomPanel.tvPostDate.setVisibility(View.GONE); |
|||
viewerBinding.bottomPanel.btnComments.setVisibility(View.GONE); |
|||
viewerBinding.bottomPanel.btnDownload.setVisibility(View.INVISIBLE); |
|||
viewerBinding.bottomPanel.viewerCaption.setText(null); |
|||
viewerBinding.bottomPanel.viewerCaption.setMentionClickListener(null); |
|||
|
|||
viewerBinding.playerView.setVisibility(View.GONE); |
|||
viewerBinding.playerView.setPlayer(null); |
|||
viewerBinding.imageViewer.setImageResource(0); |
|||
viewerBinding.imageViewer.setImageDrawable(null); |
|||
|
|||
new PostFetcher(postModel.getShortCode(), result -> { |
|||
if (result == null || result.length < 1) { |
|||
Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); |
|||
return; |
|||
} |
|||
|
|||
viewerPostModel = result[0]; |
|||
commentsEndCursor = viewerPostModel.getCommentsEndCursor(); |
|||
|
|||
mediaAdapter.setData(result); |
|||
if (result.length > 1) { |
|||
viewerBinding.mediaList.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
viewerCaptionParent.setOnTouchListener(gestureTouchListener); |
|||
viewerBinding.playerView.setOnTouchListener(gestureTouchListener); |
|||
viewerBinding.imageViewer.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> { |
|||
final float diffX = e2.getX() - e1.getX(); |
|||
if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SwipeGestureListener.SWIPE_THRESHOLD |
|||
&& Math.abs(velocityX) > SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD) { |
|||
swipeEvent.onSwipe(diffX > 0); |
|||
return true; |
|||
} |
|||
return false; |
|||
}); |
|||
|
|||
final long commentsCount = viewerPostModel.getCommentsCount(); |
|||
viewerBinding.bottomPanel.commentsCount.setText(String.valueOf(commentsCount)); |
|||
viewerBinding.bottomPanel.btnComments.setVisibility(View.VISIBLE); |
|||
|
|||
if (commentsCount > 0) { |
|||
viewerBinding.bottomPanel.btnComments.setOnClickListener(v -> |
|||
startActivityForResult(new Intent(this, CommentsViewer.class) |
|||
.putExtra(Constants.EXTRAS_END_CURSOR, commentsEndCursor) |
|||
.putExtra(Constants.EXTRAS_SHORTCODE, postShortCode), 6969)); |
|||
viewerBinding.bottomPanel.btnComments.setClickable(true); |
|||
viewerBinding.bottomPanel.btnComments.setEnabled(true); |
|||
} else { |
|||
viewerBinding.bottomPanel.btnComments.setOnClickListener(null); |
|||
viewerBinding.bottomPanel.btnComments.setClickable(false); |
|||
viewerBinding.bottomPanel.btnComments.setEnabled(false); |
|||
} |
|||
|
|||
if (postModel instanceof PostModel) { |
|||
final PostModel postModel = (PostModel) this.postModel; |
|||
postModel.setPostId(viewerPostModel.getPostId()); |
|||
postModel.setTimestamp(viewerPostModel.getTimestamp()); |
|||
postModel.setPostCaption(viewerPostModel.getPostCaption()); |
|||
} |
|||
|
|||
setupPostInfoBar(viewerPostModel.getUsername(), viewerPostModel.getItemType()); |
|||
|
|||
postCaption = postModel.getPostCaption(); |
|||
viewerCaptionParent.setVisibility(View.VISIBLE); |
|||
|
|||
viewerBinding.bottomPanel.btnDownload.setOnClickListener(downloadClickListener); |
|||
|
|||
refreshPost(); |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void searchUsername(final String text) { |
|||
if (Main.scanHack != null) { |
|||
Main.scanHack.onResult(text); |
|||
finish(); |
|||
} |
|||
} |
|||
|
|||
private void setupVideo() { |
|||
viewerBinding.playerView.setVisibility(View.VISIBLE); |
|||
viewerBinding.bottomPanel.btnDownload.setVisibility(View.VISIBLE); |
|||
viewerBinding.bottomPanel.btnMute.setVisibility(View.VISIBLE); |
|||
viewsContainer.setVisibility(View.VISIBLE); |
|||
viewerBinding.progressView.setVisibility(View.GONE); |
|||
viewerBinding.imageViewer.setVisibility(View.GONE); |
|||
viewerBinding.imageViewer.setImageDrawable(null); |
|||
|
|||
viewerBinding.bottomPanel.tvVideoViews.setText(String.valueOf(viewerPostModel.getVideoViews())); |
|||
|
|||
player = new SimpleExoPlayer.Builder(this).build(); |
|||
viewerBinding.playerView.setPlayer(player); |
|||
float vol = Utils.settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; |
|||
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f; |
|||
|
|||
player.setVolume(vol); |
|||
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); |
|||
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, "instagram")) |
|||
.createMediaSource(Uri.parse(url)); |
|||
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() { |
|||
@Override |
|||
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { |
|||
viewerBinding.progressView.setVisibility(View.GONE); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { |
|||
viewerBinding.progressView.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadCanceled(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { |
|||
viewerBinding.progressView.setVisibility(View.GONE); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadError(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData, final IOException error, final boolean wasCanceled) { |
|||
viewerBinding.progressView.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
player.prepare(mediaSource); |
|||
|
|||
player.setVolume(vol); |
|||
viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute); |
|||
|
|||
viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener); |
|||
} |
|||
|
|||
private void setupImage() { |
|||
viewsContainer.setVisibility(View.GONE); |
|||
viewerBinding.playerView.setVisibility(View.GONE); |
|||
viewerBinding.progressView.setVisibility(View.VISIBLE); |
|||
viewerBinding.bottomPanel.btnMute.setVisibility(View.GONE); |
|||
viewerBinding.bottomPanel.btnDownload.setVisibility(View.VISIBLE); |
|||
|
|||
viewerBinding.imageViewer.setImageDrawable(null); |
|||
viewerBinding.imageViewer.setVisibility(View.VISIBLE); |
|||
viewerBinding.imageViewer.setZoomable(true); |
|||
viewerBinding.imageViewer.setZoomTransitionDuration(420); |
|||
viewerBinding.imageViewer.setMaximumScale(7.2f); |
|||
|
|||
glideRequestManager.load(url).listener(new RequestListener<Drawable>() { |
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
viewerBinding.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
viewerBinding.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
}).into(viewerBinding.imageViewer); |
|||
} |
|||
|
|||
private void showDownloadDialog() { |
|||
final ArrayList<BasePostModel> postModels = new ArrayList<>(); |
|||
|
|||
if (!session && viewerBinding.mediaList.getVisibility() == View.VISIBLE) { |
|||
final DialogInterface.OnClickListener clickListener = (dialog, which) -> { |
|||
postModels.clear(); |
|||
|
|||
if (which == DialogInterface.BUTTON_NEGATIVE) { |
|||
final BasePostModel[] adapterPostModels = mediaAdapter.getPostModels(); |
|||
for (int i = 0, size = mediaAdapter.getItemCount(); i < size; ++i) { |
|||
if (adapterPostModels[i] instanceof ViewerPostModel) |
|||
postModels.add(adapterPostModels[i]); |
|||
} |
|||
} else if (which == DialogInterface.BUTTON_POSITIVE) { |
|||
postModels.add(viewerPostModel); |
|||
} else { |
|||
session = true; |
|||
postModels.add(viewerPostModel); |
|||
} |
|||
|
|||
if (postModels.size() > 0) |
|||
Utils.batchDownload(this, viewerPostModel.getUsername(), DownloadMethod.DOWNLOAD_POST_VIEWER, postModels); |
|||
}; |
|||
|
|||
new AlertDialog.Builder(this).setTitle(R.string.post_viewer_download_dialog_title) |
|||
.setMessage(R.string.post_viewer_download_message) |
|||
.setNeutralButton(R.string.post_viewer_download_session, clickListener).setPositiveButton(R.string.post_viewer_download_current, clickListener) |
|||
.setNegativeButton(R.string.post_viewer_download_album, clickListener).show(); |
|||
} else { |
|||
Utils.batchDownload(this, viewerPostModel.getUsername(), DownloadMethod.DOWNLOAD_POST_VIEWER, Collections.singletonList(viewerPostModel)); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { |
|||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
|||
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED) |
|||
showDownloadDialog(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { |
|||
super.onActivityResult(requestCode, resultCode, data); |
|||
if (resultCode == 6969) { |
|||
setResult(RESULT_OK); |
|||
finish(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
super.onPause(); |
|||
if (Build.VERSION.SDK_INT < 24) releasePlayer(); |
|||
} |
|||
|
|||
@Override |
|||
public void onStop() { |
|||
super.onStop(); |
|||
if (Build.VERSION.SDK_INT >= 24) releasePlayer(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onResume() { |
|||
super.onResume(); |
|||
if (player == null && viewerPostModel != null && viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) |
|||
setupVideo(); |
|||
else if (player != null) { |
|||
player.setPlayWhenReady(true); |
|||
player.getPlaybackState(); |
|||
} |
|||
} |
|||
|
|||
private void refreshPost() { |
|||
postShortCode = postModel.getShortCode(); |
|||
if (viewerBinding.mediaList.getVisibility() == View.VISIBLE) { |
|||
ViewerPostModel item = mediaAdapter.getItemAt(lastSlidePos); |
|||
if (item != null) { |
|||
item.setCurrentSlide(false); |
|||
mediaAdapter.notifyItemChanged(lastSlidePos, item); |
|||
} |
|||
|
|||
item = mediaAdapter.getItemAt(slidePos); |
|||
if (item != null) { |
|||
item.setCurrentSlide(true); |
|||
mediaAdapter.notifyItemChanged(slidePos, item); |
|||
} |
|||
} |
|||
lastSlidePos = slidePos; |
|||
|
|||
postCaption = viewerPostModel.getPostCaption(); |
|||
|
|||
if (Utils.hasMentions(postCaption)) { |
|||
viewerBinding.bottomPanel.viewerCaption.setText(Utils.getMentionText(postCaption), TextView.BufferType.SPANNABLE); |
|||
viewerBinding.bottomPanel.viewerCaption.setMentionClickListener((view, text, isHashtag) -> |
|||
new AlertDialog.Builder(PostViewer.this).setTitle(text) |
|||
.setMessage(isHashtag ? R.string.comment_view_mention_hash_search : R.string.comment_view_mention_user_search) |
|||
.setNegativeButton(R.string.cancel, null).setPositiveButton(R.string.ok, |
|||
(dialog, which) -> searchUsername(text)).show()); |
|||
} else { |
|||
viewerBinding.bottomPanel.viewerCaption.setMentionClickListener(null); |
|||
viewerBinding.bottomPanel.viewerCaption.setText(postCaption); |
|||
} |
|||
|
|||
setupPostInfoBar(viewerPostModel.getUsername(), viewerPostModel.getItemType()); |
|||
|
|||
if (postModel instanceof PostModel) { |
|||
final PostModel postModel = (PostModel) this.postModel; |
|||
postModel.setPostId(viewerPostModel.getPostId()); |
|||
postModel.setTimestamp(viewerPostModel.getTimestamp()); |
|||
postModel.setPostCaption(viewerPostModel.getPostCaption()); |
|||
} |
|||
|
|||
viewerBinding.bottomPanel.tvPostDate.setText(viewerPostModel.getPostDate()); |
|||
viewerBinding.bottomPanel.tvPostDate.setVisibility(View.VISIBLE); |
|||
viewerBinding.bottomPanel.tvPostDate.setSelected(true); |
|||
|
|||
url = viewerPostModel.getDisplayUrl(); |
|||
releasePlayer(); |
|||
|
|||
viewerBinding.btnDownload.setVisibility(containerLayoutParams.height == 0 ? View.GONE : View.VISIBLE); |
|||
if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(); |
|||
else setupImage(); |
|||
} |
|||
|
|||
private void releasePlayer() { |
|||
if (player != null) { |
|||
player.release(); |
|||
player = null; |
|||
} |
|||
} |
|||
|
|||
private void setupPostInfoBar(final String from, final MediaItemType mediaItemType) { |
|||
if (prevUsername == null || !prevUsername.equals(from)) { |
|||
viewerBinding.topPanel.ivProfilePic.setImageBitmap(null); |
|||
viewerBinding.topPanel.ivProfilePic.setImageDrawable(null); |
|||
viewerBinding.topPanel.ivProfilePic.setImageResource(0); |
|||
|
|||
if (from.charAt(0) != '#') |
|||
new ProfileFetcher(from, result -> { |
|||
profileModel = result; |
|||
|
|||
if (result != null) { |
|||
final String hdProfilePic = result.getHdProfilePic(); |
|||
final String sdProfilePic = result.getSdProfilePic(); |
|||
|
|||
final boolean hdPicEmpty = Utils.isEmpty(hdProfilePic); |
|||
glideRequestManager.load(hdPicEmpty ? sdProfilePic : hdProfilePic).listener(new RequestListener<Drawable>() { |
|||
private boolean loaded = true; |
|||
|
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
viewerBinding.topPanel.ivProfilePic.setEnabled(false); |
|||
viewerBinding.topPanel.ivProfilePic.setOnClickListener(null); |
|||
if (loaded) { |
|||
loaded = false; |
|||
if (!Utils.isEmpty(sdProfilePic)) glideRequestManager.load(sdProfilePic).listener(this) |
|||
.into(viewerBinding.topPanel.ivProfilePic); |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
viewerBinding.topPanel.ivProfilePic.setEnabled(true); |
|||
viewerBinding.topPanel.ivProfilePic.setOnClickListener(onClickListener); |
|||
return false; |
|||
} |
|||
}).into(viewerBinding.topPanel.ivProfilePic); |
|||
} |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
prevUsername = from; |
|||
} |
|||
|
|||
final String titlePrefix = resources.getString(mediaItemType == MediaItemType.MEDIA_TYPE_VIDEO ? |
|||
R.string.post_viewer_video_post : R.string.post_viewer_image_post); |
|||
if (Utils.isEmpty(from)) viewerBinding.topPanel.title.setText(titlePrefix); |
|||
else { |
|||
final CharSequence titleText = resources.getString(R.string.post_viewer_post_from, titlePrefix, from) + " "; |
|||
final int titleLen = titleText.length(); |
|||
final SpannableString spannableString = new SpannableString(titleText); |
|||
spannableString.setSpan(new CommentMentionClickSpan(), titleLen - from.length() - 1, titleLen - 1, 0); |
|||
viewerBinding.topPanel.title.setText(spannableString); |
|||
} |
|||
} |
|||
|
|||
private void toggleFullscreen() { |
|||
final View decorView = getWindow().getDecorView(); |
|||
int newUiOptions = decorView.getSystemUiVisibility(); |
|||
newUiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; |
|||
newUiOptions ^= View.SYSTEM_UI_FLAG_FULLSCREEN; |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) |
|||
newUiOptions ^= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; |
|||
decorView.setSystemUiVisibility(newUiOptions); |
|||
} |
|||
} |
@ -0,0 +1,215 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.content.Intent; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.drawable.BitmapDrawable; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.os.AsyncTask; |
|||
import android.os.Bundle; |
|||
import android.os.Environment; |
|||
import android.view.Menu; |
|||
import android.view.MenuItem; |
|||
import android.view.View; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.FragmentManager; |
|||
|
|||
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.io.File; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.asyncs.DownloadAsync; |
|||
import awais.instagrabber.asyncs.ProfilePictureFetcher; |
|||
import awais.instagrabber.databinding.ActivityProfileBinding; |
|||
import awais.instagrabber.dialogs.ProfileSettingsDialog; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.enums.ProfilePictureFetchMode; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Constants.PROFILE_FETCH_MODE; |
|||
|
|||
public final class ProfileViewer extends BaseLanguageActivity { |
|||
private final ProfilePictureFetchMode[] fetchModes = { |
|||
ProfilePictureFetchMode.INSTADP, |
|||
ProfilePictureFetchMode.INSTA_STALKER, |
|||
ProfilePictureFetchMode.INSTAFULLSIZE, |
|||
}; |
|||
private ActivityProfileBinding profileBinding; |
|||
private ProfileModel profileModel; |
|||
private MenuItem menuItemDownload; |
|||
private String profilePicUrl; |
|||
private FragmentManager fragmentManager; |
|||
private FetchListener<String> fetchListener; |
|||
private boolean errorHandled = false; |
|||
private boolean fallbackToProfile = false; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
profileBinding = ActivityProfileBinding.inflate(getLayoutInflater()); |
|||
setContentView(profileBinding.getRoot()); |
|||
|
|||
setSupportActionBar(profileBinding.toolbar.toolbar); |
|||
|
|||
final Intent intent = getIntent(); |
|||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_PROFILE) |
|||
|| (profileModel = (ProfileModel) intent.getSerializableExtra(Constants.EXTRAS_PROFILE)) == null) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
fragmentManager = getSupportFragmentManager(); |
|||
|
|||
final String id = profileModel.getId(); |
|||
final String username = profileModel.getUsername(); |
|||
|
|||
profileBinding.toolbar.toolbar.setTitle(username); |
|||
|
|||
profileBinding.progressView.setVisibility(View.VISIBLE); |
|||
profileBinding.imageViewer.setVisibility(View.VISIBLE); |
|||
|
|||
profileBinding.imageViewer.setZoomable(true); |
|||
profileBinding.imageViewer.setZoomTransitionDuration(420); |
|||
profileBinding.imageViewer.setMaximumScale(7.2f); |
|||
|
|||
final int fetchIndex = Math.min(2, Math.max(0, Utils.settingsHelper.getInteger(PROFILE_FETCH_MODE))); |
|||
final ProfilePictureFetchMode fetchMode = fetchModes[fetchIndex]; |
|||
|
|||
fetchListener = profileUrl -> { |
|||
profilePicUrl = profileUrl; |
|||
|
|||
if (!fallbackToProfile && Utils.isEmpty(profilePicUrl)) { |
|||
fallbackToProfile = true; |
|||
new ProfilePictureFetcher(username, id, fetchListener, fetchMode).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
return; |
|||
} |
|||
|
|||
if (errorHandled && fallbackToProfile || Utils.isEmpty(profilePicUrl)) |
|||
profilePicUrl = profileModel.getHdProfilePic(); |
|||
|
|||
final RequestManager glideRequestManager = Glide.with(this); |
|||
|
|||
glideRequestManager.load(profilePicUrl).addListener(new RequestListener<Drawable>() { |
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
fallbackToProfile = true; |
|||
if (!errorHandled) { |
|||
errorHandled = true; |
|||
new ProfilePictureFetcher(username, id, fetchListener, fetchModes[Math.min(2, Math.max(0, fetchIndex + 1))]) |
|||
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} else { |
|||
glideRequestManager.load(profileModel.getHdProfilePic()).into(profileBinding.imageViewer); |
|||
showImageInfo(); |
|||
} |
|||
profileBinding.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
if (menuItemDownload != null) menuItemDownload.setEnabled(true); |
|||
showImageInfo(); |
|||
profileBinding.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
|
|||
private void showImageInfo() { |
|||
final Drawable drawable = profileBinding.imageViewer.getDrawable(); |
|||
if (drawable != null) { |
|||
final StringBuilder info = new StringBuilder(getString(R.string.profile_viewer_imageinfo, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight())); |
|||
if (drawable instanceof BitmapDrawable) { |
|||
final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); |
|||
if (bitmap != null) { |
|||
final String colorDepthPrefix = getString(R.string.profile_viewer_colordepth_prefix); |
|||
switch (bitmap.getConfig()) { |
|||
case ALPHA_8: |
|||
info.append(colorDepthPrefix).append(" 8-bits(A)"); |
|||
break; |
|||
case RGB_565: |
|||
info.append(colorDepthPrefix).append(" 16-bits-A"); |
|||
break; |
|||
case ARGB_4444: |
|||
info.append(colorDepthPrefix).append(" 16-bits+A"); |
|||
break; |
|||
case ARGB_8888: |
|||
info.append(colorDepthPrefix).append(" 32-bits+A"); |
|||
break; |
|||
case RGBA_F16: |
|||
info.append(colorDepthPrefix).append(" 64-bits+A"); |
|||
break; |
|||
case HARDWARE: |
|||
info.append(colorDepthPrefix).append(" auto"); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
profileBinding.imageInfo.setText(info); |
|||
profileBinding.imageInfo.setVisibility(View.VISIBLE); |
|||
} |
|||
} |
|||
}).into(profileBinding.imageViewer); |
|||
}; |
|||
|
|||
new ProfilePictureFetcher(username, id, fetchListener, fetchMode).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
|
|||
private void downloadProfilePicture() { |
|||
int error = 0; |
|||
|
|||
if (profileModel != null) { |
|||
final File dir = new File(Environment.getExternalStorageDirectory(), "Download"); |
|||
if (dir.exists() || dir.mkdirs()) { |
|||
|
|||
final File saveFile = new File(dir, profileModel.getUsername() + '_' + System.currentTimeMillis() |
|||
+ Utils.getExtensionFromModel(profilePicUrl, profileModel)); |
|||
|
|||
new DownloadAsync(this, |
|||
profilePicUrl, |
|||
saveFile, |
|||
result -> { |
|||
final int toastRes = result != null && result.exists() ? |
|||
R.string.downloader_downloaded_in_folder : R.string.downloader_error_download_file; |
|||
Toast.makeText(this, toastRes, Toast.LENGTH_SHORT).show(); |
|||
}).setItems(null, profileModel.getUsername()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} else error = 1; |
|||
} else error = 2; |
|||
|
|||
if (error == 1) Toast.makeText(this, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); |
|||
else if (error == 2) Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onCreateOptionsMenu(final Menu menu) { |
|||
getMenuInflater().inflate(R.menu.menu, menu); |
|||
|
|||
final MenuItem.OnMenuItemClickListener menuItemClickListener = item -> { |
|||
if (item == menuItemDownload) { |
|||
downloadProfilePicture(); |
|||
} else { |
|||
new ProfileSettingsDialog().show(fragmentManager, "settings"); |
|||
} |
|||
return true; |
|||
}; |
|||
|
|||
menu.findItem(R.id.action_search).setVisible(false); |
|||
menuItemDownload = menu.findItem(R.id.action_download); |
|||
menuItemDownload.setVisible(true); |
|||
menuItemDownload.setEnabled(false); |
|||
menuItemDownload.setOnMenuItemClickListener(menuItemClickListener); |
|||
|
|||
final MenuItem menuItemSettings = menu.findItem(R.id.action_settings); |
|||
menuItemSettings.setVisible(true); |
|||
menuItemSettings.setOnMenuItemClickListener(menuItemClickListener); |
|||
|
|||
return true; |
|||
} |
|||
} |
@ -0,0 +1,354 @@ |
|||
package awais.instagrabber.activities; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Intent; |
|||
import android.content.pm.PackageManager; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.os.Environment; |
|||
import android.os.Handler; |
|||
import android.util.Log; |
|||
import android.util.Pair; |
|||
import android.view.Menu; |
|||
import android.view.MenuItem; |
|||
import android.view.View; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.app.ActivityCompat; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.core.view.GestureDetectorCompat; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
|
|||
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 com.google.android.exoplayer2.Player; |
|||
import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.source.MediaSource; |
|||
import com.google.android.exoplayer2.source.MediaSourceEventListener; |
|||
import com.google.android.exoplayer2.source.ProgressiveMediaSource; |
|||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; |
|||
|
|||
import java.io.File; |
|||
import java.io.IOException; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.StoriesAdapter; |
|||
import awais.instagrabber.asyncs.DownloadAsync; |
|||
import awais.instagrabber.customviews.helpers.SwipeGestureListener; |
|||
import awais.instagrabber.databinding.ActivityStoryViewerBinding; |
|||
import awais.instagrabber.interfaces.SwipeEvent; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.StoryModel; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD; |
|||
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_PATH; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class StoryViewer extends BaseLanguageActivity { |
|||
private final StoriesAdapter storiesAdapter = new StoriesAdapter(null, new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(final View v) { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof StoryModel) { |
|||
currentStory = (StoryModel) tag; |
|||
slidePos = currentStory.getPosition(); |
|||
refreshStory(); |
|||
} |
|||
} |
|||
}); |
|||
private ActivityStoryViewerBinding storyViewerBinding; |
|||
private StoryModel[] storyModels; |
|||
private GestureDetectorCompat gestureDetector; |
|||
private SimpleExoPlayer player; |
|||
private SwipeEvent swipeEvent; |
|||
private MenuItem menuDownload; |
|||
private StoryModel currentStory; |
|||
private String url, username; |
|||
private int slidePos = 0, lastSlidePos = 0; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
storyViewerBinding = ActivityStoryViewerBinding.inflate(getLayoutInflater()); |
|||
setContentView(storyViewerBinding.getRoot()); |
|||
|
|||
setSupportActionBar(storyViewerBinding.toolbar.toolbar); |
|||
|
|||
final Intent intent = getIntent(); |
|||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_STORIES) |
|||
|| (storyModels = (StoryModel[]) intent.getSerializableExtra(Constants.EXTRAS_STORIES)) == null) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
username = intent.getStringExtra(Constants.EXTRAS_USERNAME); |
|||
final String highlight = intent.getStringExtra(Constants.EXTRAS_HIGHLIGHT); |
|||
final boolean hasUsername = !Utils.isEmpty(username); |
|||
final boolean hasHighlight = !Utils.isEmpty(highlight); |
|||
|
|||
if (hasUsername) { |
|||
storyViewerBinding.toolbar.toolbar.setTitle(username); |
|||
if (hasHighlight) storyViewerBinding.toolbar.toolbar.setSubtitle(getString(R.string.title_highlight, highlight)); |
|||
else storyViewerBinding.toolbar.toolbar.setSubtitle(R.string.title_user_story); |
|||
} |
|||
|
|||
storyViewerBinding.storiesList.setVisibility(View.GONE); |
|||
storyViewerBinding.storiesList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); |
|||
storyViewerBinding.storiesList.setAdapter(storiesAdapter); |
|||
|
|||
swipeEvent = new SwipeEvent() { |
|||
private final int storiesLen = storyModels != null ? storyModels.length : 0; |
|||
|
|||
@Override |
|||
public void onSwipe(final boolean isRightSwipe) { |
|||
if (storyModels != null && storiesLen > 0) { |
|||
if (isRightSwipe) { |
|||
if (--slidePos <= 0) slidePos = 0; |
|||
} else if (++slidePos >= storiesLen) slidePos = storiesLen - 1; |
|||
|
|||
currentStory = storyModels[slidePos]; |
|||
slidePos = currentStory.getPosition(); |
|||
refreshStory(); |
|||
} |
|||
} |
|||
}; |
|||
gestureDetector = new GestureDetectorCompat(this, new SwipeGestureListener(swipeEvent)); |
|||
|
|||
viewPost(); |
|||
} |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
private void viewPost() { |
|||
lastSlidePos = 0; |
|||
storyViewerBinding.storiesList.setVisibility(View.GONE); |
|||
storiesAdapter.setData(null); |
|||
|
|||
if (menuDownload != null) menuDownload.setVisible(false); |
|||
|
|||
storyViewerBinding.playerView.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); |
|||
storyViewerBinding.imageViewer.setOnSingleFlingListener((e1, e2, velocityX, velocityY) -> { |
|||
final float diffX = e2.getX() - e1.getX(); |
|||
try { |
|||
if (Math.abs(diffX) > Math.abs(e2.getY() - e1.getY()) && Math.abs(diffX) > SWIPE_THRESHOLD |
|||
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { |
|||
swipeEvent.onSwipe(diffX > 0); |
|||
return true; |
|||
} |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ACTIVITY_STORY_VIEWER, "viewPost", |
|||
new Pair<>("swipeEvent", swipeEvent), |
|||
new Pair<>("diffX", diffX)); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
return false; |
|||
}); |
|||
|
|||
storyViewerBinding.viewStoryPost.setOnClickListener(v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof CharSequence) startActivity(new Intent(this, PostViewer.class) |
|||
.putExtra(Constants.EXTRAS_POST, new PostModel(tag.toString()))); |
|||
}); |
|||
|
|||
storiesAdapter.setData(storyModels); |
|||
if (storyModels.length > 1) storyViewerBinding.storiesList.setVisibility(View.VISIBLE); |
|||
|
|||
currentStory = storyModels[0]; |
|||
refreshStory(); |
|||
} |
|||
|
|||
private void setupVideo() { |
|||
storyViewerBinding.playerView.setVisibility(View.VISIBLE); |
|||
storyViewerBinding.progressView.setVisibility(View.GONE); |
|||
storyViewerBinding.imageViewer.setVisibility(View.GONE); |
|||
storyViewerBinding.imageViewer.setImageDrawable(null); |
|||
|
|||
player = new SimpleExoPlayer.Builder(this).build(); |
|||
storyViewerBinding.playerView.setPlayer(player); |
|||
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); |
|||
|
|||
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(this, "instagram")) |
|||
.createMediaSource(Uri.parse(url)); |
|||
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() { |
|||
@Override |
|||
public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { |
|||
if (menuDownload != null) menuDownload.setVisible(true); |
|||
storyViewerBinding.progressView.setVisibility(View.GONE); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadStarted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { |
|||
if (menuDownload != null) menuDownload.setVisible(true); |
|||
storyViewerBinding.progressView.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadCanceled(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { |
|||
storyViewerBinding.progressView.setVisibility(View.GONE); |
|||
} |
|||
|
|||
@Override |
|||
public void onLoadError(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData, final IOException error, final boolean wasCanceled) { |
|||
if (menuDownload != null) menuDownload.setVisible(false); |
|||
storyViewerBinding.progressView.setVisibility(View.GONE); |
|||
} |
|||
}); |
|||
player.prepare(mediaSource); |
|||
|
|||
storyViewerBinding.playerView.setOnClickListener(v -> { |
|||
if (player != null) { |
|||
if (player.getPlaybackState() == Player.STATE_ENDED) player.seekTo(0); |
|||
player.setPlayWhenReady(player.getPlaybackState() == Player.STATE_ENDED || !player.isPlaying()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private void setupImage() { |
|||
storyViewerBinding.progressView.setVisibility(View.VISIBLE); |
|||
storyViewerBinding.playerView.setVisibility(View.GONE); |
|||
|
|||
storyViewerBinding.imageViewer.setImageDrawable(null); |
|||
storyViewerBinding.imageViewer.setVisibility(View.VISIBLE); |
|||
storyViewerBinding.imageViewer.setZoomable(true); |
|||
storyViewerBinding.imageViewer.setZoomTransitionDuration(420); |
|||
storyViewerBinding.imageViewer.setMaximumScale(7.2f); |
|||
|
|||
Glide.with(this).load(url).listener(new RequestListener<Drawable>() { |
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
storyViewerBinding.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
if (menuDownload != null) menuDownload.setVisible(true); |
|||
storyViewerBinding.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
}).into(storyViewerBinding.imageViewer); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onCreateOptionsMenu(final Menu menu) { |
|||
getMenuInflater().inflate(R.menu.menu, menu); |
|||
|
|||
menu.findItem(R.id.action_settings).setVisible(false); |
|||
menu.findItem(R.id.action_search).setVisible(false); |
|||
|
|||
menuDownload = menu.findItem(R.id.action_download); |
|||
menuDownload.setVisible(true); |
|||
menuDownload.setOnMenuItemClickListener(item -> { |
|||
if (ContextCompat.checkSelfPermission(this, Utils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) |
|||
downloadStory(); |
|||
else |
|||
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020); |
|||
return true; |
|||
}); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { |
|||
super.onRequestPermissionsResult(requestCode, permissions, grantResults); |
|||
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED) downloadStory(); |
|||
} |
|||
|
|||
@Override |
|||
public void onPause() { |
|||
super.onPause(); |
|||
if (Build.VERSION.SDK_INT < 24) releasePlayer(); |
|||
} |
|||
|
|||
@Override |
|||
public void onStop() { |
|||
super.onStop(); |
|||
if (Build.VERSION.SDK_INT >= 24) releasePlayer(); |
|||
} |
|||
|
|||
private void downloadStory() { |
|||
int error = 0; |
|||
if (currentStory != null) { |
|||
File dir = new File(Environment.getExternalStorageDirectory(), "Download"); |
|||
|
|||
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) { |
|||
final String customPath = settingsHelper.getString(FOLDER_PATH); |
|||
if (!Utils.isEmpty(customPath)) dir = new File(customPath); |
|||
} |
|||
|
|||
if (settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER) && !Utils.isEmpty(username)) |
|||
dir = new File(dir, username); |
|||
|
|||
if (dir.exists() || dir.mkdirs()) { |
|||
final String storyUrl = currentStory.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl(); |
|||
final File saveFile = new File(dir, currentStory.getStoryMediaId() + "_" + currentStory.getTimestamp() |
|||
+ Utils.getExtensionFromModel(storyUrl, currentStory)); |
|||
|
|||
new DownloadAsync(this, storyUrl, saveFile, result -> { |
|||
final int toastRes = result != null && result.exists() ? R.string.downloader_complete |
|||
: R.string.downloader_error_download_file; |
|||
Toast.makeText(this, toastRes, Toast.LENGTH_SHORT).show(); |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
|
|||
} else error = 1; |
|||
} else error = 2; |
|||
|
|||
if (error == 1) Toast.makeText(this, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); |
|||
else if (error == 2) Toast.makeText(this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); |
|||
} |
|||
|
|||
private void refreshStory() { |
|||
if (storyViewerBinding.storiesList.getVisibility() == View.VISIBLE) { |
|||
StoryModel item = storiesAdapter.getItemAt(lastSlidePos); |
|||
if (item != null) { |
|||
item.setCurrentSlide(false); |
|||
storiesAdapter.notifyItemChanged(lastSlidePos, item); |
|||
} |
|||
|
|||
item = storiesAdapter.getItemAt(slidePos); |
|||
if (item != null) { |
|||
item.setCurrentSlide(true); |
|||
storiesAdapter.notifyItemChanged(slidePos, item); |
|||
} |
|||
} |
|||
lastSlidePos = slidePos; |
|||
|
|||
final MediaItemType itemType = currentStory.getItemType(); |
|||
|
|||
if (menuDownload != null) menuDownload.setVisible(false); |
|||
url = itemType == MediaItemType.MEDIA_TYPE_VIDEO ? currentStory.getVideoUrl() : currentStory.getStoryUrl(); |
|||
|
|||
final String shortCode = currentStory.getTappableShortCode(); |
|||
storyViewerBinding.viewStoryPost.setVisibility(shortCode != null ? View.VISIBLE : View.GONE); |
|||
storyViewerBinding.viewStoryPost.setTag(shortCode); |
|||
|
|||
releasePlayer(); |
|||
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) setupVideo(); |
|||
else setupImage(); |
|||
} |
|||
|
|||
private void releasePlayer() { |
|||
if (player != null) { |
|||
try { player.stop(true); } catch (Exception ignored) { } |
|||
try { player.release(); } catch (Exception ignored) { } |
|||
player = null; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,136 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.Filter; |
|||
import android.widget.Filterable; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
import com.bumptech.glide.request.RequestOptions; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.CommentViewHolder; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.CommentModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.utils.LocaleUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class CommentsAdapter extends RecyclerView.Adapter<CommentViewHolder> implements Filterable { |
|||
private final boolean isParent; |
|||
private final Filter filter = new Filter() { |
|||
@NonNull |
|||
@Override |
|||
protected FilterResults performFiltering(final CharSequence filter) { |
|||
final FilterResults results = new FilterResults(); |
|||
results.values = commentModels; |
|||
|
|||
final int commentsLen = commentModels == null ? 0 : commentModels.length; |
|||
if (commentModels != null && commentsLen > 0 && !Utils.isEmpty(filter)) { |
|||
final String query = filter.toString().toLowerCase(); |
|||
final ArrayList<CommentModel> filterList = new ArrayList<>(commentsLen); |
|||
|
|||
for (final CommentModel commentModel : commentModels) { |
|||
final String commentText = commentModel.getText().toString().toLowerCase(); |
|||
|
|||
if (commentText.contains(query)) filterList.add(commentModel); |
|||
else { |
|||
final CommentModel[] childCommentModels = commentModel.getChildCommentModels(); |
|||
if (childCommentModels != null) { |
|||
for (final CommentModel childCommentModel : childCommentModels) { |
|||
final String childCommentText = childCommentModel.getText().toString().toLowerCase(); |
|||
if (childCommentText.contains(query)) filterList.add(commentModel); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
filterList.trimToSize(); |
|||
results.values = filterList.toArray(new CommentModel[0]); |
|||
} |
|||
|
|||
return results; |
|||
} |
|||
|
|||
@Override |
|||
protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) { |
|||
if (results.values instanceof CommentModel[]) { |
|||
filteredCommentModels = (CommentModel[]) results.values; |
|||
notifyDataSetChanged(); |
|||
} |
|||
} |
|||
}; |
|||
private final View.OnClickListener onClickListener; |
|||
private final MentionClickListener mentionClickListener; |
|||
private final CommentModel[] commentModels; |
|||
private final String[] quantityStrings = new String[2]; |
|||
private LayoutInflater layoutInflater; |
|||
private CommentModel[] filteredCommentModels; |
|||
|
|||
public CommentsAdapter(final CommentModel[] commentModels, final boolean isParent, final View.OnClickListener onClickListener, |
|||
final MentionClickListener mentionClickListener) { |
|||
this.commentModels = this.filteredCommentModels = commentModels; |
|||
this.isParent = isParent; |
|||
this.onClickListener = onClickListener; |
|||
this.mentionClickListener = mentionClickListener; |
|||
} |
|||
|
|||
@Override |
|||
public Filter getFilter() { |
|||
return filter; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) { |
|||
final Context context = parent.getContext(); |
|||
if (quantityStrings[0] == null) quantityStrings[0] = context.getString(R.string.single_like); |
|||
if (quantityStrings[1] == null) quantityStrings[1] = context.getString(R.string.multiple_likes); |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); |
|||
return new CommentViewHolder(layoutInflater.inflate( |
|||
isParent ? R.layout.item_comment // parent |
|||
: R.layout.item_comment_small, // child |
|||
parent, false), onClickListener, mentionClickListener); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) { |
|||
final CommentModel commentModel = filteredCommentModels[position]; |
|||
if (commentModel != null) { |
|||
holder.setCommentModel(commentModel); |
|||
|
|||
holder.setCommment(commentModel.getText()); |
|||
holder.setDate(commentModel.getDateTime()); |
|||
|
|||
final long likes = commentModel.getLikes(); |
|||
holder.setLikes(String.format(LocaleUtils.getCurrentLocale(), "%d %s", likes, quantityStrings[likes == 1 ? 0 : 1])); |
|||
|
|||
final ProfileModel profileModel = commentModel.getProfileModel(); |
|||
if (profileModel != null) { |
|||
holder.setUsername(profileModel.getUsername()); |
|||
|
|||
Glide.with(layoutInflater.getContext()) |
|||
.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true)) |
|||
.load(profileModel.getSdProfilePic()).into(holder.getProfilePicView()); |
|||
} |
|||
|
|||
if (holder.isParent()) { |
|||
final CommentModel[] childCommentModels = commentModel.getChildCommentModels(); |
|||
if (childCommentModels != null && childCommentModels.length > 0) |
|||
holder.setChildAdapter(new CommentsAdapter(childCommentModels, false, onClickListener, mentionClickListener)); |
|||
else holder.hideChildComments(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return filteredCommentModels == null ? 0 : filteredCommentModels.length; |
|||
} |
|||
} |
@ -0,0 +1,116 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
import com.bumptech.glide.RequestManager; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.DirectMessageViewHolder; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemActionLogModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel; |
|||
import awais.instagrabber.models.direct_messages.InboxThreadModel; |
|||
import awais.instagrabber.models.enums.DirectItemType; |
|||
|
|||
public final class DirectMessagesAdapter extends RecyclerView.Adapter<DirectMessageViewHolder> { |
|||
private final ArrayList<InboxThreadModel> inboxThreadModels; |
|||
private final View.OnClickListener onClickListener; |
|||
private LayoutInflater layoutInflater; |
|||
|
|||
public DirectMessagesAdapter(final ArrayList<InboxThreadModel> inboxThreadModels, final View.OnClickListener onClickListener) { |
|||
this.inboxThreadModels = inboxThreadModels; |
|||
this.onClickListener = onClickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) { |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
return new DirectMessageViewHolder(layoutInflater.inflate(R.layout.layout_include_simple_item, parent, false), |
|||
onClickListener); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) { |
|||
final InboxThreadModel threadModel = inboxThreadModels.get(position); |
|||
final DirectItemModel[] itemModels; |
|||
|
|||
holder.itemView.setTag(threadModel); |
|||
|
|||
final RequestManager glideRequestManager = Glide.with(holder.itemView); |
|||
|
|||
if (threadModel != null && (itemModels = threadModel.getItems()) != null) { |
|||
final ProfileModel[] users = threadModel.getUsers(); |
|||
|
|||
if (users.length > 1) { |
|||
holder.ivProfilePic.setVisibility(View.GONE); |
|||
holder.multipleProfilePicsContainer.setVisibility(View.VISIBLE); |
|||
|
|||
for (int i = 0; i < Math.min(3, users.length); ++i) |
|||
glideRequestManager.load(users[i].getSdProfilePic()).into(holder.multipleProfilePics[i]); |
|||
|
|||
} else { |
|||
holder.ivProfilePic.setVisibility(View.VISIBLE); |
|||
holder.multipleProfilePicsContainer.setVisibility(View.GONE); |
|||
|
|||
glideRequestManager.load(users[0].getSdProfilePic()).into(holder.ivProfilePic); |
|||
} |
|||
|
|||
holder.tvUsername.setText(threadModel.getThreadTitle()); |
|||
|
|||
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1]; |
|||
final DirectItemType itemType = lastItemModel.getItemType(); |
|||
|
|||
holder.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE); |
|||
|
|||
final Context context = layoutInflater.getContext(); |
|||
|
|||
final CharSequence messageText; |
|||
if (itemType == DirectItemType.TEXT) |
|||
messageText = lastItemModel.getText(); |
|||
else if (itemType == DirectItemType.LINK) |
|||
messageText = context.getString(R.string.direct_messages_sent_link); |
|||
else if (itemType == DirectItemType.MEDIA || itemType == DirectItemType.MEDIA_SHARE) |
|||
messageText = context.getString(R.string.direct_messages_sent_media); |
|||
else if (itemType == DirectItemType.ACTION_LOG) { |
|||
final DirectItemActionLogModel logModel = lastItemModel.getActionLogModel(); |
|||
messageText = logModel != null ? logModel.getDescription() : "..."; |
|||
|
|||
} else if (itemType == DirectItemType.REEL_SHARE) { |
|||
final DirectItemReelShareModel reelShare = lastItemModel.getReelShare(); |
|||
if (reelShare == null) |
|||
messageText = context.getString(R.string.direct_messages_sent_media); |
|||
else { |
|||
final String reelType = reelShare.getType(); |
|||
final int textRes; |
|||
if ("reply".equals(reelType)) textRes = R.string.direct_messages_replied_story; |
|||
else if ("mention".equals(reelType)) textRes = R.string.direct_messages_mention_story; |
|||
else if ("reaction".equals(reelType)) textRes = R.string.direct_messages_reacted_story; |
|||
else textRes = R.string.direct_messages_sent_media; |
|||
|
|||
messageText = context.getString(textRes) + " : " + reelShare.getText(); |
|||
} |
|||
|
|||
} else messageText = null; |
|||
|
|||
holder.tvMessage.setText(messageText); |
|||
|
|||
holder.tvDate.setText(lastItemModel.getDateTime()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return inboxThreadModels == null ? 0 : inboxThreadModels.size(); |
|||
} |
|||
} |
@ -0,0 +1,87 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.graphics.drawable.Drawable; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
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 awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.DiscoverViewHolder; |
|||
import awais.instagrabber.models.DiscoverItemModel; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
|
|||
public final class DiscoverAdapter extends RecyclerView.Adapter<DiscoverViewHolder> { |
|||
private final ArrayList<DiscoverItemModel> discoverItemModels; |
|||
private final View.OnClickListener clickListener; |
|||
private final View.OnLongClickListener longClickListener; |
|||
private LayoutInflater layoutInflater; |
|||
public boolean isSelecting = false; |
|||
|
|||
public DiscoverAdapter(final ArrayList<DiscoverItemModel> discoverItemModels, final View.OnClickListener clickListener, |
|||
final View.OnLongClickListener longClickListener) { |
|||
this.discoverItemModels = discoverItemModels; |
|||
this.longClickListener = longClickListener; |
|||
this.clickListener = clickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public DiscoverViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
return new DiscoverViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final DiscoverViewHolder holder, final int position) { |
|||
final DiscoverItemModel itemModel = discoverItemModels.get(position); |
|||
if (itemModel != null) { |
|||
itemModel.setPosition(position); |
|||
holder.itemView.setTag(itemModel); |
|||
|
|||
holder.itemView.setOnClickListener(clickListener); |
|||
holder.itemView.setOnLongClickListener(longClickListener); |
|||
|
|||
final MediaItemType mediaType = itemModel.getItemType(); |
|||
|
|||
holder.typeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || mediaType == MediaItemType.MEDIA_TYPE_SLIDER |
|||
? View.VISIBLE : View.GONE); |
|||
|
|||
holder.typeIcon.setImageResource(mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? R.drawable.slider : R.drawable.video); |
|||
|
|||
holder.selectedView.setVisibility(itemModel.isSelected() ? View.VISIBLE : View.GONE); |
|||
holder.progressView.setVisibility(View.VISIBLE); |
|||
|
|||
Glide.with(layoutInflater.getContext()).load(itemModel.getDisplayUrl()).listener(new RequestListener<Drawable>() { |
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
holder.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
holder.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
}).into(holder.postImage); |
|||
|
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return discoverItemModels == null ? 0 : discoverItemModels.size(); |
|||
} |
|||
} |
@ -0,0 +1,486 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.app.Activity; |
|||
import android.content.Context; |
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.graphics.Typeface; |
|||
import android.net.Uri; |
|||
import android.text.SpannableStringBuilder; |
|||
import android.text.Spanned; |
|||
import android.text.style.StyleSpan; |
|||
import android.util.Log; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
import androidx.viewpager.widget.PagerAdapter; |
|||
import androidx.viewpager.widget.ViewPager; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
import com.bumptech.glide.RequestManager; |
|||
import com.github.chrisbanes.photoview.PhotoView; |
|||
import com.google.android.exoplayer2.Player; |
|||
import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.source.ProgressiveMediaSource; |
|||
import com.google.android.exoplayer2.ui.PlayerView; |
|||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.CommentsViewer; |
|||
import awais.instagrabber.activities.PostViewer; |
|||
import awais.instagrabber.adapters.viewholder.FeedItemViewHolder; |
|||
import awais.instagrabber.customviews.CommentMentionClickSpan; |
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.BasePostModel; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
import awais.instagrabber.models.enums.ItemGetType; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class FeedAdapter extends RecyclerView.Adapter<FeedItemViewHolder> { |
|||
private final static String ellipsize = "… more"; |
|||
private final Activity activity; |
|||
private final LayoutInflater layoutInflater; |
|||
private final ArrayList<FeedModel> feedModels; |
|||
private final MentionClickListener mentionClickListener; |
|||
private final View.OnClickListener clickListener = new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(@NonNull final View v) { |
|||
final Object tag = v.getTag(); |
|||
|
|||
if (tag instanceof FeedModel) { |
|||
final FeedModel feedModel = (FeedModel) tag; |
|||
|
|||
if (v instanceof RamboTextView) { |
|||
if (feedModel.isMentionClicked()) |
|||
feedModel.toggleCaption(); |
|||
feedModel.setMentionClicked(false); |
|||
if (!expandCollapseTextView((RamboTextView) v, feedModel)) |
|||
feedModel.toggleCaption(); |
|||
|
|||
} else { |
|||
final int id = v.getId(); |
|||
switch (id) { |
|||
case R.id.btnComments: |
|||
activity.startActivityForResult(new Intent(activity, CommentsViewer.class) |
|||
.putExtra(Constants.EXTRAS_SHORTCODE, feedModel.getShortCode()), 6969); |
|||
break; |
|||
|
|||
case R.id.viewStoryPost: |
|||
activity.startActivity(new Intent(activity, PostViewer.class) |
|||
.putExtra(Constants.EXTRAS_INDEX, feedModel.getPosition()) |
|||
.putExtra(Constants.EXTRAS_POST, new PostModel(feedModel.getShortCode())) |
|||
.putExtra(Constants.EXTRAS_TYPE, ItemGetType.FEED_ITEMS)); |
|||
break; |
|||
|
|||
case R.id.btnDownload: |
|||
final Context context = v.getContext(); |
|||
ProfileModel profileModel = feedModel.getProfileModel(); |
|||
final String username = profileModel != null ? profileModel.getUsername() : null; |
|||
|
|||
final ViewerPostModel[] sliderItems = feedModel.getSliderItems(); |
|||
|
|||
if (feedModel.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER || sliderItems == null || sliderItems.length == 1) |
|||
Utils.batchDownload(context, username, DownloadMethod.DOWNLOAD_FEED, Collections.singletonList(feedModel)); |
|||
else { |
|||
final ArrayList<BasePostModel> postModels = new ArrayList<>(); |
|||
final DialogInterface.OnClickListener clickListener = (dialog, which) -> { |
|||
postModels.clear(); |
|||
|
|||
final boolean breakWhenFoundSelected = which == DialogInterface.BUTTON_POSITIVE; |
|||
|
|||
for (final ViewerPostModel sliderItem : sliderItems) { |
|||
if (sliderItem != null) { |
|||
if (!breakWhenFoundSelected) postModels.add(sliderItem); |
|||
else if (sliderItem.isSelected()) { |
|||
postModels.add(sliderItem); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// shows 0 items on first item of viewpager cause onPageSelected hasn't been called yet |
|||
if (breakWhenFoundSelected && postModels.size() == 0) |
|||
postModels.add(sliderItems[0]); |
|||
|
|||
if (postModels.size() > 0) |
|||
Utils.batchDownload(context, username, DownloadMethod.DOWNLOAD_FEED, postModels); |
|||
}; |
|||
|
|||
new AlertDialog.Builder(context).setTitle(R.string.post_viewer_download_dialog_title) |
|||
.setPositiveButton(R.string.post_viewer_download_current, clickListener) |
|||
.setNegativeButton(R.string.post_viewer_download_album, clickListener).show(); |
|||
} |
|||
break; |
|||
|
|||
case R.id.ivProfilePic: |
|||
if (mentionClickListener != null) { |
|||
profileModel = feedModel.getProfileModel(); |
|||
if (profileModel != null) |
|||
mentionClickListener.onClick(null, profileModel.getUsername(), false); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
private final View.OnLongClickListener longClickListener = v -> { |
|||
final Object tag; |
|||
if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) |
|||
Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption()); |
|||
return true; |
|||
}; |
|||
public SimpleExoPlayer pagerPlayer; |
|||
private final PlayerChangeListener playerChangeListener = (childPos, player) -> { |
|||
// todo |
|||
pagerPlayer = player; |
|||
}; |
|||
|
|||
public FeedAdapter(final Activity activity, final ArrayList<FeedModel> FeedModels, final MentionClickListener mentionClickListener) { |
|||
this.activity = activity; |
|||
this.feedModels = FeedModels; |
|||
this.mentionClickListener = mentionClickListener; |
|||
this.layoutInflater = LayoutInflater.from(activity); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final View view; |
|||
if (viewType == MediaItemType.MEDIA_TYPE_VIDEO.ordinal()) |
|||
view = layoutInflater.inflate(R.layout.item_feed_video, parent, false); |
|||
else if (viewType == MediaItemType.MEDIA_TYPE_SLIDER.ordinal()) |
|||
view = layoutInflater.inflate(R.layout.item_feed_slider, parent, false); |
|||
else |
|||
view = layoutInflater.inflate(R.layout.item_feed, parent, false); |
|||
return new FeedItemViewHolder(view); |
|||
} |
|||
|
|||
@SuppressLint("SetTextI18n") |
|||
@Override |
|||
public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) { |
|||
final FeedModel feedModel = feedModels.get(position); |
|||
if (feedModel != null) { |
|||
final RequestManager glideRequestManager = Glide.with(viewHolder.itemView); |
|||
|
|||
feedModel.setPosition(position); |
|||
|
|||
viewHolder.viewPost.setTag(feedModel); |
|||
viewHolder.profilePic.setTag(feedModel); |
|||
viewHolder.btnDownload.setTag(feedModel); |
|||
viewHolder.viewerCaption.setTag(feedModel); |
|||
|
|||
final ProfileModel profileModel = feedModel.getProfileModel(); |
|||
if (profileModel != null) { |
|||
glideRequestManager.load(profileModel.getSdProfilePic()).into(viewHolder.profilePic); |
|||
viewHolder.username.setText(profileModel.getUsername()); |
|||
} |
|||
|
|||
viewHolder.viewPost.setOnClickListener(clickListener); |
|||
viewHolder.profilePic.setOnClickListener(clickListener); |
|||
viewHolder.btnDownload.setOnClickListener(clickListener); |
|||
|
|||
viewHolder.tvPostDate.setText(feedModel.getPostDate()); |
|||
|
|||
final long commentsCount = feedModel.getCommentsCount(); |
|||
viewHolder.commentsCount.setText(String.valueOf(commentsCount)); |
|||
|
|||
if (commentsCount <= 0) { |
|||
viewHolder.btnComments.setTag(null); |
|||
viewHolder.btnComments.setOnClickListener(null); |
|||
viewHolder.btnComments.setEnabled(false); |
|||
} else { |
|||
viewHolder.btnComments.setTag(feedModel); |
|||
viewHolder.btnComments.setOnClickListener(clickListener); |
|||
viewHolder.btnComments.setEnabled(true); |
|||
} |
|||
|
|||
final String thumbnailUrl = feedModel.getThumbnailUrl(); |
|||
final String displayUrl = feedModel.getDisplayUrl(); |
|||
CharSequence postCaption = feedModel.getPostCaption(); |
|||
|
|||
final boolean captionEmpty = Utils.isEmpty(postCaption); |
|||
|
|||
viewHolder.viewerCaption.setOnClickListener(clickListener); |
|||
viewHolder.viewerCaption.setOnLongClickListener(longClickListener); |
|||
viewHolder.viewerCaption.setVisibility(captionEmpty ? View.GONE : View.VISIBLE); |
|||
|
|||
if (!captionEmpty && Utils.hasMentions(postCaption)) { |
|||
postCaption = Utils.getMentionText(postCaption); |
|||
feedModel.setPostCaption(postCaption); |
|||
viewHolder.viewerCaption.setText(postCaption, TextView.BufferType.SPANNABLE); |
|||
viewHolder.viewerCaption.setMentionClickListener(mentionClickListener); |
|||
} else { |
|||
viewHolder.viewerCaption.setText(postCaption); |
|||
} |
|||
|
|||
expandCollapseTextView(viewHolder.viewerCaption, feedModel); |
|||
|
|||
final MediaItemType itemType = feedModel.getItemType(); |
|||
final View viewToChangeHeight; |
|||
|
|||
if (itemType == MediaItemType.MEDIA_TYPE_VIDEO) { |
|||
viewToChangeHeight = viewHolder.playerView; |
|||
|
|||
viewHolder.videoViewsParent.setVisibility(View.VISIBLE); |
|||
viewHolder.videoViews.setText(String.valueOf(feedModel.getViewCount())); |
|||
} else { |
|||
viewHolder.videoViewsParent.setVisibility(View.GONE); |
|||
viewHolder.btnMute.setVisibility(View.GONE); |
|||
|
|||
if (itemType == MediaItemType.MEDIA_TYPE_SLIDER) { |
|||
viewToChangeHeight = viewHolder.mediaList; |
|||
|
|||
final ViewerPostModel[] sliderItems = feedModel.getSliderItems(); |
|||
final int sliderItemLen = sliderItems != null ? sliderItems.length : 0; |
|||
|
|||
if (sliderItemLen > 0) { |
|||
viewHolder.mediaCounter.setText("1/" + sliderItemLen); |
|||
viewHolder.mediaList.setOffscreenPageLimit(Math.min(5, sliderItemLen)); |
|||
|
|||
final ViewPager.SimpleOnPageChangeListener simpleOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() { |
|||
private int prevPos = 0; |
|||
|
|||
@Override |
|||
public void onPageSelected(final int position) { |
|||
ViewerPostModel sliderItem = sliderItems[prevPos]; |
|||
if (sliderItem != null) sliderItem.setSelected(false); |
|||
sliderItem = sliderItems[position]; |
|||
if (sliderItem != null) sliderItem.setSelected(true); |
|||
|
|||
View childAt = viewHolder.mediaList.getChildAt(prevPos); |
|||
if (childAt instanceof PlayerView) { |
|||
pagerPlayer = (SimpleExoPlayer) ((PlayerView) childAt).getPlayer(); |
|||
if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false); |
|||
} |
|||
childAt = viewHolder.mediaList.getChildAt(position); |
|||
if (childAt instanceof PlayerView) { |
|||
pagerPlayer = (SimpleExoPlayer) ((PlayerView) childAt).getPlayer(); |
|||
if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(true); |
|||
} |
|||
prevPos = position; |
|||
viewHolder.mediaCounter.setText((position + 1) + "/" + sliderItemLen); |
|||
} |
|||
}; |
|||
|
|||
//noinspection deprecation |
|||
viewHolder.mediaList.setOnPageChangeListener(simpleOnPageChangeListener); // cause add listeners might add to recycled holders |
|||
|
|||
final View.OnClickListener muteClickListener = v -> { |
|||
Player player = null; |
|||
if (v instanceof PlayerView) player = ((PlayerView) v).getPlayer(); |
|||
else if (v instanceof ImageView || v == viewHolder.btnMute) { |
|||
final int currentItem = viewHolder.mediaList.getCurrentItem(); |
|||
if (currentItem < viewHolder.mediaList.getChildCount()) { |
|||
final View childAt = viewHolder.mediaList.getChildAt(currentItem); |
|||
if (childAt instanceof PlayerView) player = ((PlayerView) childAt).getPlayer(); |
|||
} |
|||
|
|||
} else { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof Player) player = (Player) tag; |
|||
} |
|||
|
|||
if (player instanceof SimpleExoPlayer) { |
|||
final SimpleExoPlayer exoPlayer = (SimpleExoPlayer) player; |
|||
final float intVol = exoPlayer.getVolume() == 0f ? 1f : 0f; |
|||
exoPlayer.setVolume(intVol); |
|||
viewHolder.btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute); |
|||
Utils.sessionVolumeFull = intVol == 1f; |
|||
} |
|||
}; |
|||
|
|||
viewHolder.btnMute.setOnClickListener(muteClickListener); |
|||
viewHolder.mediaList.setAdapter(new ChildMediaItemsAdapter(sliderItems, viewHolder.btnMute, muteClickListener, playerChangeListener)); |
|||
} |
|||
} else { |
|||
viewToChangeHeight = viewHolder.imageView; |
|||
String url = displayUrl; |
|||
if (Utils.isEmpty(url)) url = thumbnailUrl; |
|||
glideRequestManager.load(url).into(viewHolder.imageView); |
|||
} |
|||
} |
|||
|
|||
if (viewToChangeHeight != null) { |
|||
final ViewGroup.LayoutParams layoutParams = viewToChangeHeight.getLayoutParams(); |
|||
layoutParams.height = Utils.displayMetrics.widthPixels + 1; |
|||
viewToChangeHeight.setLayoutParams(layoutParams); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return feedModels == null ? 0 : feedModels.size(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(final int position) { |
|||
if (feedModels != null) return feedModels.get(position).getItemType().ordinal(); |
|||
return MediaItemType.MEDIA_TYPE_IMAGE.ordinal(); |
|||
} |
|||
|
|||
/** |
|||
* expands or collapses {@link RamboTextView} [stg idek why i wrote this documentation] |
|||
* |
|||
* @param textView the {@link RamboTextView} view, to expand and collapse |
|||
* @param feedModel the {@link FeedModel} model to check wether model is collapsed to expanded |
|||
* |
|||
* @return true if expanded/collapsed, false if empty or text size is <= 255 chars |
|||
*/ |
|||
public static boolean expandCollapseTextView(@NonNull final RamboTextView textView, @NonNull final FeedModel feedModel) { |
|||
final CharSequence caption = feedModel.getPostCaption(); |
|||
if (Utils.isEmpty(caption)) return false; |
|||
|
|||
final TextView.BufferType bufferType = caption instanceof Spanned ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL; |
|||
|
|||
if (!feedModel.isCaptionExpanded()) { |
|||
int i = Utils.indexOfChar(caption, '\r', 0); |
|||
if (i == -1) i = Utils.indexOfChar(caption, '\n', 0); |
|||
if (i == -1) i = 255; |
|||
|
|||
final int captionLen = caption.length(); |
|||
final int minTrim = Math.min(255, i); |
|||
if (captionLen <= minTrim) return false; |
|||
|
|||
final CharSequence mentionText = caption.subSequence(0, Math.min(captionLen, minTrim)); |
|||
final SpannableStringBuilder stringBuilder = new SpannableStringBuilder(mentionText).append(ellipsize); |
|||
final int spanLen = stringBuilder.length(); |
|||
|
|||
// fixed @mention...more merging into one span |
|||
final CommentMentionClickSpan[] spans = stringBuilder.getSpans(0, mentionText.length(), CommentMentionClickSpan.class); |
|||
if (spans != null) { |
|||
for (final CommentMentionClickSpan span : spans) { |
|||
final int spanStart = stringBuilder.getSpanStart(span); |
|||
stringBuilder.removeSpan(span); |
|||
stringBuilder.setSpan(span, spanStart, mentionText.length(), 0); |
|||
} |
|||
} |
|||
|
|||
stringBuilder.setSpan(new StyleSpan(Typeface.BOLD), spanLen - ellipsize.length(), spanLen, 0); |
|||
|
|||
textView.setText(stringBuilder, bufferType); |
|||
textView.setCaptionIsExpandable(true); |
|||
textView.setCaptionIsExpanded(true); |
|||
} else { |
|||
textView.setText(caption, bufferType); |
|||
textView.setCaptionIsExpanded(false); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
private interface PlayerChangeListener { |
|||
void playerChanged(final int childPos, final SimpleExoPlayer player); |
|||
} |
|||
|
|||
private static final class ChildMediaItemsAdapter extends PagerAdapter { |
|||
private final PlayerChangeListener playerChangeListener; |
|||
private final View.OnClickListener muteClickListener; |
|||
private final ViewerPostModel[] sliderItems; |
|||
private final View btnMute; |
|||
private SimpleExoPlayer player; |
|||
|
|||
private ChildMediaItemsAdapter(final ViewerPostModel[] sliderItems, final View btnMute, final View.OnClickListener muteClickListener, |
|||
final PlayerChangeListener playerChangeListener) { |
|||
this.muteClickListener = muteClickListener; |
|||
this.sliderItems = sliderItems; |
|||
this.btnMute = btnMute; |
|||
if (BuildConfig.DEBUG) this.playerChangeListener = playerChangeListener; |
|||
else this.playerChangeListener = null; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Object instantiateItem(@NonNull final ViewGroup container, final int position) { |
|||
if (BuildConfig.DEBUG) container.setBackgroundColor(0xFF_0a_c0_09); // todo remove |
|||
|
|||
final Context context = container.getContext(); |
|||
final ViewerPostModel sliderItem = sliderItems[position]; |
|||
|
|||
if (sliderItem.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { |
|||
if (btnMute != null) btnMute.setVisibility(View.VISIBLE); |
|||
final PlayerView playerView = new PlayerView(context); |
|||
|
|||
player = new SimpleExoPlayer.Builder(context).build(); |
|||
playerView.setPlayer(player); |
|||
|
|||
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; |
|||
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f; |
|||
player.setVolume(vol); |
|||
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); |
|||
|
|||
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram")) |
|||
.createMediaSource(Uri.parse(sliderItem.getDisplayUrl())); |
|||
|
|||
player.setRepeatMode(Player.REPEAT_MODE_ALL); |
|||
player.prepare(mediaSource); |
|||
player.setVolume(vol); |
|||
|
|||
playerView.setTag(player); |
|||
playerView.setOnClickListener(muteClickListener); |
|||
|
|||
if (playerChangeListener != null) { |
|||
//todo |
|||
// playerChangeListener.playerChanged(position, player); |
|||
Log.d("AWAISKING_APP", "playerChangeListener: " + playerChangeListener); |
|||
} |
|||
|
|||
container.addView(playerView); |
|||
return playerView; |
|||
} else { |
|||
if (btnMute != null) btnMute.setVisibility(View.GONE); |
|||
|
|||
final PhotoView photoView = new PhotoView(context); |
|||
photoView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); |
|||
Glide.with(context).load(sliderItem.getDisplayUrl()).into(photoView); |
|||
container.addView(photoView); |
|||
return photoView; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void destroyItem(@NonNull final ViewGroup container, final int position, @NonNull final Object object) { |
|||
final Player player = object instanceof PlayerView ? ((PlayerView) object).getPlayer() : this.player; |
|||
|
|||
if (player == this.player && this.player != null) { |
|||
this.player.stop(true); |
|||
this.player.release(); |
|||
} else if (player != null) { |
|||
player.stop(true); |
|||
player.release(); |
|||
} |
|||
|
|||
container.removeView((View) object); |
|||
} |
|||
|
|||
@Override |
|||
public int getCount() { |
|||
return sliderItems != null ? sliderItems.length : 0; |
|||
} |
|||
|
|||
@Override |
|||
public boolean isViewFromObject(@NonNull final View view, @NonNull final Object object) { |
|||
return view == object; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,57 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.HighlightViewHolder; |
|||
import awais.instagrabber.models.FeedStoryModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
|
|||
public final class FeedStoriesAdapter extends RecyclerView.Adapter<HighlightViewHolder> { |
|||
private final View.OnClickListener clickListener; |
|||
private LayoutInflater layoutInflater; |
|||
private FeedStoryModel[] feedStoryModels; |
|||
|
|||
public FeedStoriesAdapter(final FeedStoryModel[] feedStoryModels, final View.OnClickListener clickListener) { |
|||
this.feedStoryModels = feedStoryModels; |
|||
this.clickListener = clickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) { |
|||
final FeedStoryModel feedStoryModel = feedStoryModels[position]; |
|||
if (feedStoryModel != null) { |
|||
holder.itemView.setTag(feedStoryModel); |
|||
holder.itemView.setOnClickListener(clickListener); |
|||
|
|||
final ProfileModel profileModel = feedStoryModel.getProfileModel(); |
|||
|
|||
holder.title.setText(profileModel.getUsername()); |
|||
Glide.with(layoutInflater.getContext()).load(profileModel.getSdProfilePic()).into(holder.icon); |
|||
} |
|||
} |
|||
|
|||
public void setData(final FeedStoryModel[] feedStoryModels) { |
|||
this.feedStoryModels = feedStoryModels; |
|||
notifyDataSetChanged(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return feedStoryModels == null ? 0 : feedStoryModels.length; |
|||
} |
|||
} |
@ -0,0 +1,144 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.Filter; |
|||
import android.widget.Filterable; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.FollowsViewHolder; |
|||
import awais.instagrabber.interfaces.OnGroupClickListener; |
|||
import awais.instagrabber.models.FollowModel; |
|||
import awais.instagrabber.utils.Utils; |
|||
import thoughtbot.expandableadapter.ExpandableGroup; |
|||
import thoughtbot.expandableadapter.ExpandableList; |
|||
import thoughtbot.expandableadapter.ExpandableListPosition; |
|||
import thoughtbot.expandableadapter.GroupViewHolder; |
|||
|
|||
// thanks to ThoughtBot's ExpandableRecyclerViewAdapter |
|||
// https://github.com/thoughtbot/expandable-recycler-view |
|||
public final class FollowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements OnGroupClickListener, Filterable { |
|||
private final Filter filter = new Filter() { |
|||
@Nullable |
|||
@Override |
|||
protected FilterResults performFiltering(final CharSequence filter) { |
|||
if (expandableList.groups != null) { |
|||
final boolean isFilterEmpty = Utils.isEmpty(filter); |
|||
final String query = isFilterEmpty ? null : filter.toString().toLowerCase(); |
|||
|
|||
for (int x = 0; x < expandableList.groups.size(); ++x) { |
|||
final ExpandableGroup expandableGroup = expandableList.groups.get(x); |
|||
final List<FollowModel> items = expandableGroup.getItems(false); |
|||
final int itemCount = expandableGroup.getItemCount(false); |
|||
|
|||
for (int i = 0; i < itemCount; ++i) { |
|||
final FollowModel followModel = items.get(i); |
|||
|
|||
if (isFilterEmpty) followModel.setShown(true); |
|||
else followModel.setShown(Utils.hasKey(query, followModel.getUsername(), followModel.getFullName())); |
|||
} |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected void publishResults(final CharSequence constraint, final FilterResults results) { |
|||
notifyDataSetChanged(); |
|||
} |
|||
}; |
|||
private final View.OnClickListener onClickListener; |
|||
private final LayoutInflater layoutInflater; |
|||
private final ExpandableList expandableList; |
|||
private final boolean hasManyGroups; |
|||
|
|||
public FollowAdapter(final Context context, final View.OnClickListener onClickListener, @NonNull final ArrayList<ExpandableGroup> groups) { |
|||
this.layoutInflater = LayoutInflater.from(context); |
|||
this.expandableList = new ExpandableList(groups); |
|||
this.onClickListener = onClickListener; |
|||
this.hasManyGroups = groups.size() > 1; |
|||
} |
|||
|
|||
@Override |
|||
public Filter getFilter() { |
|||
return filter; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final boolean isGroup = hasManyGroups && viewType == ExpandableListPosition.GROUP; |
|||
|
|||
final View view = layoutInflater.inflate(isGroup ? R.layout.header_follow : R.layout.item_follow, parent, false); |
|||
|
|||
return isGroup ? new GroupViewHolder(view, this) : new FollowsViewHolder(view); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { |
|||
final ExpandableListPosition listPos = expandableList.getUnflattenedPosition(position); |
|||
final ExpandableGroup group = expandableList.getExpandableGroup(listPos); |
|||
|
|||
if (hasManyGroups && listPos.type == ExpandableListPosition.GROUP) { |
|||
final GroupViewHolder gvh = (GroupViewHolder) holder; |
|||
gvh.setTitle(group.getTitle()); |
|||
gvh.toggle(isGroupExpanded(group)); |
|||
|
|||
} else { |
|||
final FollowModel model = group.getItems(true).get(hasManyGroups ? listPos.childPos : position); |
|||
|
|||
final FollowsViewHolder followHolder = (FollowsViewHolder) holder; |
|||
if (model != null) { |
|||
followHolder.itemView.setTag(model); |
|||
followHolder.itemView.setOnClickListener(onClickListener); |
|||
|
|||
followHolder.tvUsername.setText(model.getUsername()); |
|||
followHolder.tvFullName.setText(model.getFullName()); |
|||
|
|||
Glide.with(layoutInflater.getContext()).load(model.getProfilePicUrl()).into(followHolder.profileImage); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return expandableList.getVisibleItemCount() - (hasManyGroups ? 0 : 1); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(final int position) { |
|||
return !hasManyGroups ? 0 : expandableList.getUnflattenedPosition(position).type; |
|||
} |
|||
|
|||
@Override |
|||
public void toggleGroup(final int flatPos) { |
|||
final ExpandableListPosition listPosition = expandableList.getUnflattenedPosition(flatPos); |
|||
|
|||
final int groupPos = listPosition.groupPos; |
|||
final int positionStart = expandableList.getFlattenedGroupIndex(listPosition) + 1; |
|||
final int positionEnd = expandableList.groups.get(groupPos).getItemCount(true); |
|||
|
|||
final boolean isExpanded = expandableList.expandedGroupIndexes[groupPos]; |
|||
expandableList.expandedGroupIndexes[groupPos] = !isExpanded; |
|||
notifyItemChanged(positionStart - 1); |
|||
if (positionEnd > 0) { |
|||
if (isExpanded) notifyItemRangeRemoved(positionStart, positionEnd); |
|||
else notifyItemRangeInserted(positionStart, positionEnd); |
|||
} |
|||
} |
|||
|
|||
public boolean isGroupExpanded(final ExpandableGroup group) { |
|||
return expandableList.expandedGroupIndexes[expandableList.groups.indexOf(group)]; |
|||
} |
|||
} |
@ -0,0 +1,53 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.HighlightViewHolder; |
|||
import awais.instagrabber.models.HighlightModel; |
|||
|
|||
public final class HighlightsAdapter extends RecyclerView.Adapter<HighlightViewHolder> { |
|||
private final View.OnClickListener clickListener; |
|||
private LayoutInflater layoutInflater; |
|||
private HighlightModel[] highlightModels; |
|||
|
|||
public HighlightsAdapter(final HighlightModel[] highlightModels, final View.OnClickListener clickListener) { |
|||
this.highlightModels = highlightModels; |
|||
this.clickListener = clickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public HighlightViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
return new HighlightViewHolder(layoutInflater.inflate(R.layout.item_highlight, parent, false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final HighlightViewHolder holder, final int position) { |
|||
final HighlightModel highlightModel = highlightModels[position]; |
|||
if (highlightModel != null) { |
|||
holder.itemView.setTag(highlightModel); |
|||
holder.itemView.setOnClickListener(clickListener); |
|||
holder.title.setText(highlightModel.getTitle()); |
|||
Glide.with(holder.itemView).load(highlightModel.getThumbnailUrl()).into(holder.icon); |
|||
} |
|||
} |
|||
|
|||
public void setData(final HighlightModel[] highlightModels) { |
|||
this.highlightModels = highlightModels; |
|||
notifyDataSetChanged(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return highlightModels == null ? 0 : highlightModels.length; |
|||
} |
|||
} |
@ -0,0 +1,354 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.text.Spanned; |
|||
import android.util.Log; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.text.HtmlCompat; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
import com.bumptech.glide.RequestManager; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.directmessages.TextMessageViewHolder; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemMediaModel; |
|||
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemRavenMediaModel; |
|||
import awais.instagrabber.models.enums.DirectItemType; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.models.enums.RavenExpiringMediaType; |
|||
import awais.instagrabber.models.enums.RavenMediaViewType; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkContext; |
|||
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkModel; |
|||
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel; |
|||
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemVoiceMediaModel; |
|||
import static awais.instagrabber.models.direct_messages.DirectItemModel.RavenExpiringMediaActionSummaryModel; |
|||
|
|||
public final class MessageItemsAdapter extends RecyclerView.Adapter<TextMessageViewHolder> { |
|||
private static final int MESSAGE_INCOMING = 69, MESSAGE_OUTGOING = 420; |
|||
private final ProfileModel myProfileHolder = new ProfileModel(false, false, null, null, null, null, null, null, null, 0, 0, 0); |
|||
private final ArrayList<DirectItemModel> directItemModels; |
|||
private final ArrayList<ProfileModel> users; |
|||
private final View.OnClickListener onClickListener; |
|||
private final MentionClickListener mentionClickListener; |
|||
private final View.OnClickListener openProfileClickListener = v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof ProfileModel) { |
|||
// todo do profile stuff |
|||
final ProfileModel profileModel = (ProfileModel) tag; |
|||
Log.d("AWAISKING_APP", "--> " + profileModel); |
|||
} |
|||
}; |
|||
private final int itemMargin; |
|||
private DirectItemVoiceMediaModel prevVoiceModel; |
|||
private ImageView prevPlayIcon; |
|||
private final View.OnClickListener voicePlayClickListener = v -> { |
|||
final Object tag = v.getTag(); |
|||
if (v instanceof ViewGroup && tag instanceof DirectItemVoiceMediaModel) { |
|||
final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0); |
|||
final DirectItemVoiceMediaModel voiceMediaModel = (DirectItemVoiceMediaModel) tag; |
|||
final boolean voicePlaying = voiceMediaModel.isPlaying(); |
|||
voiceMediaModel.setPlaying(!voicePlaying); |
|||
|
|||
if (voiceMediaModel == prevVoiceModel) { |
|||
// todo pause / resume |
|||
} else { |
|||
// todo release prev audio, start new voice |
|||
if (prevVoiceModel != null) prevVoiceModel.setPlaying(false); |
|||
if (prevPlayIcon != null) prevPlayIcon.setImageResource(android.R.drawable.ic_media_play); |
|||
} |
|||
|
|||
if (voicePlaying) { |
|||
playIcon.setImageResource(android.R.drawable.ic_media_play); |
|||
} else { |
|||
playIcon.setImageResource(android.R.drawable.ic_media_pause); |
|||
} |
|||
|
|||
prevVoiceModel = voiceMediaModel; |
|||
prevPlayIcon = playIcon; |
|||
} |
|||
}; |
|||
private Context context; |
|||
private LayoutInflater layoutInflater; |
|||
private String strDmYou; |
|||
|
|||
public MessageItemsAdapter(final ArrayList<DirectItemModel> directItemModels, final ArrayList<ProfileModel> users, |
|||
final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) { |
|||
this.users = users; |
|||
this.directItemModels = directItemModels; |
|||
this.onClickListener = onClickListener; |
|||
this.mentionClickListener = mentionClickListener; |
|||
this.itemMargin = Utils.displayMetrics.widthPixels / 5; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public TextMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) { |
|||
if (context == null) context = parent.getContext(); |
|||
if (strDmYou == null) strDmYou = context.getString(R.string.direct_messages_you); |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); |
|||
return new TextMessageViewHolder(layoutInflater.inflate(R.layout.item_message_item, parent, false), |
|||
onClickListener, mentionClickListener); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final TextMessageViewHolder holder, final int position) { |
|||
final DirectItemModel directItemModel = directItemModels.get(position); |
|||
holder.itemView.setTag(directItemModel); |
|||
|
|||
if (directItemModel != null) { |
|||
final DirectItemType itemType = directItemModel.getItemType(); |
|||
|
|||
final ProfileModel user = getUser(directItemModel.getUserId()); |
|||
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING; |
|||
|
|||
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams(); |
|||
layoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0, |
|||
type == MESSAGE_INCOMING ? itemMargin : 0, 0); |
|||
|
|||
holder.tvMessage.setVisibility(View.GONE); |
|||
holder.voiceMessageContainer.setVisibility(View.GONE); |
|||
holder.ivAnimatedMessage.setVisibility(View.GONE); |
|||
holder.linkMessageContainer.setVisibility(View.GONE); |
|||
|
|||
holder.mediaMessageContainer.setVisibility(View.GONE); |
|||
holder.mediaTypeIcon.setVisibility(View.GONE); |
|||
holder.mediaExpiredIcon.setVisibility(View.GONE); |
|||
|
|||
holder.profileMessageContainer.setVisibility(View.GONE); |
|||
holder.isVerified.setVisibility(View.GONE); |
|||
|
|||
holder.btnOpenProfile.setVisibility(View.GONE); |
|||
holder.btnOpenProfile.setOnClickListener(null); |
|||
holder.btnOpenProfile.setTag(null); |
|||
|
|||
CharSequence text = "?"; |
|||
if (user != null && user != myProfileHolder) text = user.getUsername(); |
|||
else if (user == myProfileHolder) text = strDmYou; |
|||
text = text + " - " + directItemModel.getDateTime(); |
|||
|
|||
holder.tvUsername.setText(text); |
|||
|
|||
holder.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE); |
|||
|
|||
final RequestManager glideRequestManager = Glide.with(holder.itemView); |
|||
|
|||
if (type == MESSAGE_INCOMING && user != null) |
|||
glideRequestManager.load(user.getSdProfilePic()).into(holder.ivProfilePic); |
|||
|
|||
DirectItemMediaModel mediaModel = directItemModel.getMediaModel(); |
|||
switch (itemType) { |
|||
case PLACEHOLDER: |
|||
case TEXT: |
|||
text = directItemModel.getText(); |
|||
text = Utils.getSpannableUrl(text.toString()); // for urls |
|||
if (Utils.hasMentions(text)) text = Utils.getMentionText(text); // for mentions |
|||
|
|||
if (text instanceof Spanned) holder.tvMessage.setText(text, TextView.BufferType.SPANNABLE); |
|||
else if (text == "") holder.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown)); |
|||
else holder.tvMessage.setText(text); |
|||
|
|||
holder.tvMessage.setVisibility(View.VISIBLE); |
|||
break; |
|||
|
|||
case LINK: { |
|||
final DirectItemLinkModel link = directItemModel.getLinkModel(); |
|||
final DirectItemLinkContext linkContext = link.getLinkContext(); |
|||
|
|||
final String linkImageUrl = linkContext.getLinkImageUrl(); |
|||
if (!Utils.isEmpty(linkImageUrl)) { |
|||
glideRequestManager.load(linkImageUrl).into(holder.ivLinkPreview); |
|||
holder.tvLinkTitle.setText(linkContext.getLinkTitle()); |
|||
holder.tvLinkSummary.setText(linkContext.getLinkSummary()); |
|||
holder.ivLinkPreview.setVisibility(View.VISIBLE); |
|||
holder.linkMessageContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
holder.tvMessage.setText(Utils.getSpannableUrl(link.getText())); |
|||
holder.tvMessage.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case MEDIA_SHARE: { |
|||
final ProfileModel modelUser = mediaModel.getUser(); |
|||
if (modelUser != null) { |
|||
holder.tvMessage.setText(context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername())); |
|||
holder.tvMessage.setVisibility(View.VISIBLE); |
|||
} |
|||
} |
|||
case MEDIA: { |
|||
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview); |
|||
|
|||
final MediaItemType modelMediaType = mediaModel.getMediaType(); |
|||
holder.mediaTypeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || |
|||
modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE); |
|||
|
|||
holder.mediaMessageContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case RAVEN_MEDIA: { |
|||
final DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel(); |
|||
final RavenExpiringMediaActionSummaryModel mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary(); |
|||
|
|||
mediaModel = ravenMediaModel.getMedia(); |
|||
|
|||
final boolean isExpired = mediaModel == null || |
|||
Utils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1; |
|||
|
|||
holder.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE); |
|||
|
|||
int textRes = R.string.dms_inbox_raven_media_unknown; |
|||
if (isExpired) textRes = R.string.dms_inbox_raven_media_expired; |
|||
|
|||
if (!isExpired && mediaActionSummary != null) { |
|||
final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType(); |
|||
|
|||
if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED) |
|||
textRes = R.string.dms_inbox_raven_media_delivered; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT) |
|||
textRes = R.string.dms_inbox_raven_media_sent; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED) |
|||
textRes = R.string.dms_inbox_raven_media_opened; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED) |
|||
textRes = R.string.dms_inbox_raven_media_replayed; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING) |
|||
textRes = R.string.dms_inbox_raven_media_sending; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED) |
|||
textRes = R.string.dms_inbox_raven_media_blocked; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED) |
|||
textRes = R.string.dms_inbox_raven_media_suggested; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT) |
|||
textRes = R.string.dms_inbox_raven_media_screenshot; |
|||
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER) |
|||
textRes = R.string.dms_inbox_raven_media_cant_deliver; |
|||
|
|||
final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType(); |
|||
if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) { |
|||
final MediaItemType mediaType = mediaModel.getMediaType(); |
|||
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || |
|||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE); |
|||
|
|||
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview); |
|||
holder.mediaMessageContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
} |
|||
|
|||
holder.tvMessage.setText(context.getText(textRes)); |
|||
holder.tvMessage.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case REEL_SHARE: { |
|||
final DirectItemReelShareModel reelShare = directItemModel.getReelShare(); |
|||
if (!Utils.isEmpty(text = reelShare.getText())) { |
|||
holder.tvMessage.setText(text); |
|||
holder.tvMessage.setVisibility(View.VISIBLE); |
|||
} |
|||
|
|||
final DirectItemMediaModel reelShareMedia = reelShare.getMedia(); |
|||
final MediaItemType mediaType = reelShareMedia.getMediaType(); |
|||
|
|||
Log.d("austin_debug", "media: " + reelShareMedia); |
|||
|
|||
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO || |
|||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE); |
|||
|
|||
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(holder.ivMediaPreview); |
|||
holder.mediaMessageContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case VOICE_MEDIA: { |
|||
final DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel(); |
|||
|
|||
if (voiceMediaModel != null) { |
|||
final int[] waveformData = voiceMediaModel.getWaveformData(); |
|||
if (waveformData != null) holder.waveformSeekBar.setSample(waveformData); |
|||
|
|||
final long durationMs = voiceMediaModel.getDurationMs(); |
|||
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs)); |
|||
holder.waveformSeekBar.setProgress(voiceMediaModel.getProgress()); |
|||
holder.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> { |
|||
// todo progress audio player |
|||
voiceMediaModel.setProgress(progress); |
|||
if (fromUser) |
|||
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs * progress / 100)); |
|||
}); |
|||
holder.btnPlayVoice.setTag(voiceMediaModel); |
|||
holder.btnPlayVoice.setOnClickListener(voicePlayClickListener); |
|||
} else { |
|||
holder.waveformSeekBar.setProgress(0); |
|||
} |
|||
|
|||
holder.voiceMessageContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case ANIMATED_MEDIA: { |
|||
glideRequestManager.asGif().load(directItemModel.getAnimatedMediaModel().getGifUrl()) |
|||
.into(holder.ivAnimatedMessage); |
|||
holder.ivAnimatedMessage.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case PROFILE: { |
|||
final ProfileModel profileModel = directItemModel.getProfileModel(); |
|||
Glide.with(holder.ivMessageProfilePic).load(profileModel.getSdProfilePic()) |
|||
.into(holder.ivMessageProfilePic); |
|||
holder.btnOpenProfile.setTag(profileModel); |
|||
holder.btnOpenProfile.setOnClickListener(openProfileClickListener); |
|||
|
|||
holder.tvProfileName.setText(profileModel.getName()); |
|||
holder.tvProfileUsername.setText(profileModel.getUsername()); |
|||
holder.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); |
|||
|
|||
holder.btnOpenProfile.setVisibility(View.VISIBLE); |
|||
holder.profileMessageContainer.setVisibility(View.VISIBLE); |
|||
} |
|||
break; |
|||
|
|||
case VIDEO_CALL_EVENT: { |
|||
// todo add call event info |
|||
holder.tvMessage.setVisibility(View.VISIBLE); |
|||
holder.itemView.setBackgroundColor(0xFF_1F90E6); // blue bitch |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemViewType(final int position) { |
|||
return directItemModels.get(position).getItemType().ordinal(); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return directItemModels == null ? 0 : directItemModels.size(); |
|||
} |
|||
|
|||
@Nullable |
|||
private ProfileModel getUser(final long userId) { |
|||
if (users != null) { |
|||
for (final ProfileModel user : users) |
|||
if (Long.toString(userId).equals(user.getId())) return user; |
|||
return myProfileHolder; |
|||
} |
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,92 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.graphics.drawable.Drawable; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
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 awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.PostViewHolder; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
|
|||
public final class PostsAdapter extends RecyclerView.Adapter<PostViewHolder> { |
|||
private final ArrayList<PostModel> postModels; |
|||
private final View.OnClickListener clickListener; |
|||
private final View.OnLongClickListener longClickListener; |
|||
private LayoutInflater layoutInflater; |
|||
public boolean isSelecting = false; |
|||
|
|||
public PostsAdapter(final ArrayList<PostModel> postModels, final View.OnClickListener clickListener, |
|||
final View.OnLongClickListener longClickListener) { |
|||
this.postModels = postModels; |
|||
this.clickListener = clickListener; |
|||
this.longClickListener = longClickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public PostViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
return new PostViewHolder(layoutInflater.inflate(R.layout.item_post, parent, false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final PostViewHolder holder, final int position) { |
|||
final PostModel postModel = postModels.get(position); |
|||
if (postModel != null) { |
|||
postModel.setPosition(position); |
|||
|
|||
holder.itemView.setTag(postModel); |
|||
|
|||
holder.itemView.setOnClickListener(clickListener); |
|||
holder.itemView.setOnLongClickListener(longClickListener); |
|||
|
|||
final MediaItemType itemType = postModel.getItemType(); |
|||
final boolean isSlider = itemType == MediaItemType.MEDIA_TYPE_SLIDER; |
|||
|
|||
holder.isDownloaded.setVisibility(postModel.isDownloaded() ? View.VISIBLE : View.GONE); |
|||
|
|||
holder.typeIcon.setVisibility(itemType == MediaItemType.MEDIA_TYPE_VIDEO || isSlider ? View.VISIBLE : View.GONE); |
|||
holder.typeIcon.setImageResource(isSlider ? R.drawable.slider : R.drawable.video); |
|||
|
|||
holder.selectedView.setVisibility(postModel.isSelected() ? View.VISIBLE : View.GONE); |
|||
holder.progressView.setVisibility(View.VISIBLE); |
|||
|
|||
final RequestManager glideRequestManager = Glide.with(holder.postImage); |
|||
|
|||
glideRequestManager.load(postModel.getThumbnailUrl()).listener(new RequestListener<Drawable>() { |
|||
@Override |
|||
public boolean onResourceReady(final Drawable resource, final Object model, final Target<Drawable> target, final DataSource dataSource, final boolean isFirstResource) { |
|||
holder.progressView.setVisibility(View.GONE); |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onLoadFailed(@Nullable final GlideException e, final Object model, final Target<Drawable> target, final boolean isFirstResource) { |
|||
holder.progressView.setVisibility(View.GONE); |
|||
glideRequestManager.load(postModel.getDisplayUrl()).into(holder.postImage); |
|||
return false; |
|||
} |
|||
}).into(holder.postImage); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return postModels == null ? 0 : postModels.size(); |
|||
} |
|||
} |
@ -0,0 +1,68 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.viewholder.PostMediaViewHolder; |
|||
import awais.instagrabber.models.BasePostModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
|
|||
public final class PostsMediaAdapter extends RecyclerView.Adapter<PostMediaViewHolder> { |
|||
private final View.OnClickListener clickListener; |
|||
private LayoutInflater layoutInflater; |
|||
private ViewerPostModel[] postModels; |
|||
|
|||
public PostsMediaAdapter(final ViewerPostModel[] postModels, final View.OnClickListener clickListener) { |
|||
this.postModels = postModels; |
|||
this.clickListener = clickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public PostMediaViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext()); |
|||
return new PostMediaViewHolder(layoutInflater.inflate(R.layout.item_child_post, parent, false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final PostMediaViewHolder holder, final int position) { |
|||
final ViewerPostModel postModel = postModels[position]; |
|||
if (postModel != null) { |
|||
postModel.setPosition(position); |
|||
|
|||
holder.itemView.setTag(postModel); |
|||
holder.itemView.setOnClickListener(clickListener); |
|||
|
|||
holder.selectedView.setVisibility(postModel.isCurrentSlide() ? View.VISIBLE : View.GONE); |
|||
|
|||
holder.isDownloaded.setVisibility(postModel.isDownloaded() ? View.VISIBLE : View.GONE); |
|||
|
|||
Glide.with(layoutInflater.getContext()).load(postModel.getSliderDisplayUrl()).into(holder.icon); |
|||
} |
|||
} |
|||
|
|||
public void setData(final ViewerPostModel[] postModels) { |
|||
this.postModels = postModels; |
|||
notifyDataSetChanged(); |
|||
} |
|||
|
|||
public ViewerPostModel getItemAt(final int position) { |
|||
return postModels == null ? null : postModels[position]; |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return postModels == null ? 0 : postModels.length; |
|||
} |
|||
|
|||
public BasePostModel[] getPostModels() { |
|||
return postModels; |
|||
} |
|||
} |
@ -0,0 +1,75 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.utils.DataBox; |
|||
|
|||
public final class SimpleAdapter<T> extends RecyclerView.Adapter<SimpleAdapter.SimpleViewHolder> { |
|||
private List<T> items; |
|||
private final LayoutInflater layoutInflater; |
|||
private final View.OnClickListener onClickListener; |
|||
private final View.OnLongClickListener longClickListener; |
|||
|
|||
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener) { |
|||
this(context, items, onClickListener, null); |
|||
} |
|||
|
|||
public SimpleAdapter(final Context context, final List<T> items, final View.OnClickListener onClickListener, |
|||
final View.OnLongClickListener longClickListener) { |
|||
this.layoutInflater = LayoutInflater.from(context); |
|||
this.items = items; |
|||
this.onClickListener = onClickListener; |
|||
this.longClickListener = longClickListener; |
|||
} |
|||
|
|||
public void setItems(final List<T> items) { |
|||
this.items = items; |
|||
notifyDataSetChanged(); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public SimpleViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
return new SimpleViewHolder(layoutInflater. |
|||
inflate(R.layout.item_dir_list, parent, false), onClickListener, longClickListener); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final SimpleViewHolder holder, final int position) { |
|||
final T item = items.get(position); |
|||
holder.itemView.setTag(item); |
|||
holder.text.setText(item.toString()); |
|||
if (item instanceof DataBox.CookieModel && ((DataBox.CookieModel) item).isSelected() || |
|||
item instanceof String && ((String) item).toLowerCase().endsWith(".zaai")) |
|||
holder.itemView.setBackgroundColor(0xF0_125687); |
|||
else |
|||
holder.itemView.setBackground(null); |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return items != null ? items.size() : 0; |
|||
} |
|||
|
|||
static final class SimpleViewHolder extends RecyclerView.ViewHolder { |
|||
private final TextView text; |
|||
|
|||
private SimpleViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, |
|||
final View.OnLongClickListener longClickListener) { |
|||
super(itemView); |
|||
text = itemView.findViewById(android.R.id.text1); |
|||
itemView.setOnClickListener(onClickListener); |
|||
if (longClickListener != null) itemView.setOnLongClickListener(longClickListener); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,84 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.content.res.Resources; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
import com.bumptech.glide.request.RequestOptions; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.models.StoryModel; |
|||
|
|||
public final class StoriesAdapter extends RecyclerView.Adapter<StoriesAdapter.StoryViewHolder> { |
|||
private final View.OnClickListener clickListener; |
|||
private LayoutInflater layoutInflater; |
|||
private StoryModel[] storyModels; |
|||
private Resources resources; |
|||
private int width, height; |
|||
|
|||
public StoriesAdapter(final StoryModel[] storyModels, final View.OnClickListener clickListener) { |
|||
this.storyModels = storyModels; |
|||
this.clickListener = clickListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public StoryViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { |
|||
final Context context = parent.getContext(); |
|||
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); |
|||
if (resources == null) resources = context.getResources(); |
|||
|
|||
height = Math.round(resources.getDimension(R.dimen.story_item_height)); |
|||
width = Math.round(resources.getDimension(R.dimen.story_item_width)); |
|||
|
|||
return new StoryViewHolder(layoutInflater.inflate(R.layout.item_story, parent, false)); |
|||
} |
|||
|
|||
@Override |
|||
public void onBindViewHolder(@NonNull final StoryViewHolder holder, final int position) { |
|||
final StoryModel storyModel = storyModels[position]; |
|||
if (storyModel != null) { |
|||
storyModel.setPosition(position); |
|||
|
|||
holder.itemView.setTag(storyModel); |
|||
holder.itemView.setOnClickListener(clickListener); |
|||
|
|||
holder.selectedView.setVisibility(storyModel.isCurrentSlide() ? View.VISIBLE : View.GONE); |
|||
|
|||
Glide.with(holder.itemView).load(storyModel.getStoryUrl()) |
|||
.apply(new RequestOptions().override(width, height)) |
|||
.into(holder.icon); |
|||
} |
|||
} |
|||
|
|||
public void setData(final StoryModel[] storyModels) { |
|||
this.storyModels = storyModels; |
|||
notifyDataSetChanged(); |
|||
} |
|||
|
|||
public StoryModel getItemAt(final int position) { |
|||
return storyModels == null ? null : storyModels[position]; |
|||
} |
|||
|
|||
@Override |
|||
public int getItemCount() { |
|||
return storyModels == null ? 0 : storyModels.length; |
|||
} |
|||
|
|||
public final static class StoryViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView icon, selectedView; |
|||
|
|||
public StoryViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
selectedView = itemView.findViewById(R.id.selectedView); |
|||
icon = itemView.findViewById(R.id.icon); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,60 @@ |
|||
package awais.instagrabber.adapters; |
|||
|
|||
import android.content.Context; |
|||
import android.database.Cursor; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.cursoradapter.widget.CursorAdapter; |
|||
|
|||
import com.bumptech.glide.Glide; |
|||
import com.bumptech.glide.RequestManager; |
|||
import com.bumptech.glide.request.RequestOptions; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class SuggestionsAdapter extends CursorAdapter { |
|||
private final LayoutInflater layoutInflater; |
|||
private final View.OnClickListener onClickListener; |
|||
private final RequestManager glideRequestManager; |
|||
|
|||
public SuggestionsAdapter(final Context context, final View.OnClickListener onClickListener) { |
|||
super(context, null, FLAG_REGISTER_CONTENT_OBSERVER); |
|||
this.glideRequestManager = Glide.with(context); |
|||
this.layoutInflater = LayoutInflater.from(context); |
|||
this.onClickListener = onClickListener; |
|||
} |
|||
|
|||
@Override |
|||
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) { |
|||
return layoutInflater.inflate(R.layout.item_suggestion, parent, false); |
|||
} |
|||
|
|||
@Override |
|||
public void bindView(@NonNull final View view, final Context context, @NonNull final Cursor cursor) { |
|||
// i, username, fullname, type, picUrl, verified |
|||
// 0, 1 , 2 , 3 , 4 , 5 |
|||
|
|||
final String fullname = cursor.getString(2); |
|||
String username = cursor.getString(1); |
|||
final String picUrl = cursor.getString(4); |
|||
final boolean verified = cursor.getString(5).charAt(0) == 't'; |
|||
|
|||
if ("TYPE_HASHTAG".equals(cursor.getString(3))) username = '#' + username; |
|||
|
|||
view.setOnClickListener(onClickListener); |
|||
view.setTag(username); |
|||
|
|||
view.findViewById(R.id.isVerified).setVisibility(verified ? View.VISIBLE : View.GONE); |
|||
|
|||
((TextView) view.findViewById(R.id.tvUsername)).setText(username); |
|||
((TextView) view.findViewById(R.id.tvFullName)).setText(fullname); |
|||
|
|||
glideRequestManager.applyDefaultRequestOptions(new RequestOptions().skipMemoryCache(true)) |
|||
.load(picUrl).into((ImageView) view.findViewById(R.id.ivProfilePic)); |
|||
} |
|||
} |
@ -0,0 +1,85 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.text.Spannable; |
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.adapters.CommentsAdapter; |
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.CommentModel; |
|||
|
|||
public final class CommentViewHolder extends RecyclerView.ViewHolder { |
|||
private final MentionClickListener mentionClickListener; |
|||
private final RecyclerView rvChildComments; |
|||
private final ImageView ivProfilePic; |
|||
private final TextView tvUsername, tvDate, tvComment, tvLikes; |
|||
private final View container; |
|||
|
|||
public CommentViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, final MentionClickListener mentionClickListener) { |
|||
super(itemView); |
|||
|
|||
container = itemView.findViewById(R.id.container); |
|||
if (onClickListener != null) container.setOnClickListener(onClickListener); |
|||
|
|||
this.mentionClickListener = mentionClickListener; |
|||
|
|||
ivProfilePic = itemView.findViewById(R.id.ivProfilePic); |
|||
tvUsername = itemView.findViewById(R.id.tvUsername); |
|||
tvDate = itemView.findViewById(R.id.tvDate); |
|||
tvLikes = itemView.findViewById(R.id.tvLikes); |
|||
tvComment = itemView.findViewById(R.id.tvComment); |
|||
|
|||
tvUsername.setSelected(true); |
|||
tvDate.setSelected(true); |
|||
|
|||
rvChildComments = itemView.findViewById(R.id.rvChildComments); |
|||
} |
|||
|
|||
public final ImageView getProfilePicView() { |
|||
return ivProfilePic; |
|||
} |
|||
|
|||
public final boolean isParent() { |
|||
return rvChildComments != null; |
|||
} |
|||
|
|||
public final void setCommentModel(final CommentModel commentModel) { |
|||
if (container != null) container.setTag(commentModel); |
|||
} |
|||
|
|||
public final void setUsername(final String username) { |
|||
if (tvUsername != null) tvUsername.setText(username); |
|||
} |
|||
|
|||
public final void setDate(final String date) { |
|||
if (tvDate != null) tvDate.setText(date); |
|||
} |
|||
|
|||
public final void setLikes(final String likes) { |
|||
if (tvLikes != null) tvLikes.setText(likes); |
|||
} |
|||
|
|||
public final void setCommment(final CharSequence commment) { |
|||
if (tvComment != null) { |
|||
tvComment.setText(commment, commment instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL); |
|||
((RamboTextView) tvComment).setMentionClickListener(mentionClickListener); |
|||
} |
|||
} |
|||
|
|||
public final void setChildAdapter(final CommentsAdapter adapter) { |
|||
if (isParent()) { |
|||
rvChildComments.setAdapter(adapter); |
|||
rvChildComments.setVisibility(View.VISIBLE); |
|||
} |
|||
} |
|||
|
|||
public final void hideChildComments() { |
|||
if (isParent()) rvChildComments.setVisibility(View.GONE); |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.LinearLayout; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class DirectMessageViewHolder extends RecyclerView.ViewHolder { |
|||
public final LinearLayout multipleProfilePicsContainer; |
|||
public final ImageView[] multipleProfilePics; |
|||
public final ImageView ivProfilePic, notTextType; |
|||
public final TextView tvUsername, tvDate, tvMessage; |
|||
|
|||
public DirectMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener) { |
|||
super(itemView); |
|||
|
|||
if (clickListener != null) itemView.setOnClickListener(clickListener); |
|||
|
|||
itemView.findViewById(R.id.tvLikes).setVisibility(View.GONE); |
|||
|
|||
tvDate = itemView.findViewById(R.id.tvDate); |
|||
tvMessage = itemView.findViewById(R.id.tvComment); |
|||
tvUsername = itemView.findViewById(R.id.tvUsername); |
|||
notTextType = itemView.findViewById(R.id.notTextType); |
|||
ivProfilePic = itemView.findViewById(R.id.ivProfilePic); |
|||
|
|||
multipleProfilePicsContainer = itemView.findViewById(R.id.container); |
|||
final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1); |
|||
multipleProfilePics = new ImageView[]{ |
|||
(ImageView) multipleProfilePicsContainer.getChildAt(0), |
|||
(ImageView) containerChild.getChildAt(0), |
|||
(ImageView) containerChild.getChildAt(1) |
|||
}; |
|||
|
|||
tvDate.setSelected(true); |
|||
tvUsername.setSelected(true); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class DiscoverViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView postImage, typeIcon; |
|||
public final View selectedView, progressView; |
|||
|
|||
public DiscoverViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
typeIcon = itemView.findViewById(R.id.typeIcon); |
|||
postImage = itemView.findViewById(R.id.postImage); |
|||
selectedView = itemView.findViewById(R.id.selectedView); |
|||
progressView = itemView.findViewById(R.id.progressView); |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
import androidx.viewpager.widget.ViewPager; |
|||
|
|||
import com.github.chrisbanes.photoview.PhotoView; |
|||
import com.google.android.exoplayer2.ui.PlayerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
|
|||
public final class FeedItemViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView profilePic, btnMute, btnDownload; |
|||
public final TextView username, commentsCount, videoViews, mediaCounter, tvPostDate; |
|||
public final RamboTextView viewerCaption; |
|||
public final View btnComments, videoViewsParent, viewPost; |
|||
public final ViewPager mediaList; |
|||
public final PhotoView imageView; |
|||
public final PlayerView playerView; |
|||
|
|||
public FeedItemViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
|
|||
// common |
|||
viewerCaption = itemView.findViewById(R.id.viewerCaption); |
|||
btnDownload = itemView.findViewById(R.id.btnDownload); |
|||
btnComments = itemView.findViewById(R.id.btnComments); |
|||
profilePic = itemView.findViewById(R.id.ivProfilePic); |
|||
tvPostDate = itemView.findViewById(R.id.tvPostDate); |
|||
viewPost = itemView.findViewById(R.id.viewStoryPost); |
|||
username = itemView.findViewById(R.id.title); |
|||
|
|||
// video view |
|||
btnMute = itemView.findViewById(R.id.btnMute); |
|||
videoViews = itemView.findViewById(R.id.tvVideoViews); |
|||
commentsCount = btnComments.findViewById(R.id.commentsCount); |
|||
videoViewsParent = videoViews != null ? (View) videoViews.getParent() : null; |
|||
|
|||
// slider view |
|||
mediaCounter = itemView.findViewById(R.id.mediaCounter); |
|||
|
|||
// different types |
|||
mediaList = itemView.findViewById(R.id.media_list); |
|||
imageView = itemView.findViewById(R.id.imageViewer); |
|||
playerView = itemView.findViewById(R.id.playerView); |
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class FollowsViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView profileImage; |
|||
public final TextView tvFullName, tvUsername; |
|||
|
|||
public FollowsViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
profileImage = itemView.findViewById(R.id.ivProfilePic); |
|||
tvFullName = itemView.findViewById(R.id.tvFullName); |
|||
tvUsername = itemView.findViewById(R.id.tvUsername); |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class HighlightViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView icon; |
|||
public final TextView title; |
|||
|
|||
public HighlightViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
icon = itemView.findViewById(R.id.icon); |
|||
title = itemView.findViewById(R.id.title); |
|||
} |
|||
} |
@ -0,0 +1,20 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class PostMediaViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView icon, isDownloaded, selectedView; |
|||
|
|||
public PostMediaViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
selectedView = itemView.findViewById(R.id.selectedView); |
|||
isDownloaded = itemView.findViewById(R.id.isDownloaded); |
|||
icon = itemView.findViewById(R.id.icon); |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
package awais.instagrabber.adapters.viewholder; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class PostViewHolder extends RecyclerView.ViewHolder { |
|||
public final ImageView postImage, typeIcon; |
|||
public final View selectedView, progressView, isDownloaded; |
|||
|
|||
public PostViewHolder(@NonNull final View itemView) { |
|||
super(itemView); |
|||
typeIcon = itemView.findViewById(R.id.typeIcon); |
|||
postImage = itemView.findViewById(R.id.postImage); |
|||
isDownloaded = itemView.findViewById(R.id.isDownloaded); |
|||
selectedView = itemView.findViewById(R.id.selectedView); |
|||
progressView = itemView.findViewById(R.id.progressView); |
|||
} |
|||
} |
@ -0,0 +1,91 @@ |
|||
package awais.instagrabber.adapters.viewholder.directmessages; |
|||
|
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.cardview.widget.CardView; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
import awais.instagrabber.customviews.masoudss_waveform.WaveformSeekBar; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
|
|||
public final class TextMessageViewHolder extends RecyclerView.ViewHolder { |
|||
public final CardView rootCardView; |
|||
public final TextView tvUsername; |
|||
public final ImageView ivProfilePic; |
|||
// text message |
|||
public final RamboTextView tvMessage; |
|||
// expired message icon |
|||
public final View mediaExpiredIcon; |
|||
// media message |
|||
public final View mediaMessageContainer; |
|||
public final ImageView ivMediaPreview, mediaTypeIcon; |
|||
// profile messag |
|||
public final View profileMessageContainer, isVerified, btnOpenProfile; |
|||
public final TextView tvProfileUsername, tvProfileName; |
|||
public final ImageView ivMessageProfilePic; |
|||
// animated message |
|||
public final ImageView ivAnimatedMessage; |
|||
// link message |
|||
public final View linkMessageContainer; |
|||
public final ImageView ivLinkPreview; |
|||
public final TextView tvLinkTitle, tvLinkSummary; |
|||
// voice message |
|||
public final View voiceMessageContainer, btnPlayVoice; |
|||
public final WaveformSeekBar waveformSeekBar; |
|||
public final TextView tvVoiceDuration; |
|||
|
|||
public TextMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener, |
|||
final MentionClickListener mentionClickListener) { |
|||
super(itemView); |
|||
|
|||
if (clickListener != null) itemView.setOnClickListener(clickListener); |
|||
|
|||
tvUsername = itemView.findViewById(R.id.tvUsername); |
|||
ivProfilePic = itemView.findViewById(R.id.ivProfilePic); |
|||
|
|||
// text message |
|||
tvMessage = itemView.findViewById(R.id.tvMessage); |
|||
tvMessage.setCaptionIsExpandable(true); |
|||
tvMessage.setCaptionIsExpanded(true); |
|||
if (mentionClickListener != null) tvMessage.setMentionClickListener(mentionClickListener); |
|||
|
|||
// root view |
|||
rootCardView = (CardView) tvMessage.getParent().getParent(); |
|||
|
|||
// expired message icon |
|||
mediaExpiredIcon = itemView.findViewById(R.id.mediaExpiredIcon); |
|||
|
|||
// media message |
|||
ivMediaPreview = itemView.findViewById(R.id.ivMediaPreview); |
|||
mediaMessageContainer = (View) ivMediaPreview.getParent(); |
|||
mediaTypeIcon = mediaMessageContainer.findViewById(R.id.typeIcon); |
|||
|
|||
// profile message |
|||
btnOpenProfile = itemView.findViewById(R.id.btnInfo); |
|||
ivMessageProfilePic = itemView.findViewById(R.id.profileInfo); |
|||
profileMessageContainer = (View) ivMessageProfilePic.getParent(); |
|||
isVerified = profileMessageContainer.findViewById(R.id.isVerified); |
|||
tvProfileName = profileMessageContainer.findViewById(R.id.tvFullName); |
|||
tvProfileUsername = profileMessageContainer.findViewById(R.id.profileInfoText); |
|||
|
|||
// animated message |
|||
ivAnimatedMessage = itemView.findViewById(R.id.ivAnimatedMessage); |
|||
|
|||
// link message |
|||
ivLinkPreview = itemView.findViewById(R.id.ivLinkPreview); |
|||
linkMessageContainer = (View) ivLinkPreview.getParent(); |
|||
tvLinkTitle = linkMessageContainer.findViewById(R.id.tvLinkTitle); |
|||
tvLinkSummary = linkMessageContainer.findViewById(R.id.tvLinkSummary); |
|||
|
|||
// voice message |
|||
waveformSeekBar = itemView.findViewById(R.id.waveformSeekBar); |
|||
voiceMessageContainer = (View) waveformSeekBar.getParent(); |
|||
btnPlayVoice = voiceMessageContainer.findViewById(R.id.btnPlayVoice); |
|||
tvVoiceDuration = voiceMessageContainer.findViewById(R.id.tvVoiceDuration); |
|||
} |
|||
} |
@ -0,0 +1,265 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
import android.util.Pair; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.CommentModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]> { |
|||
private final String shortCode; |
|||
private final FetchListener<CommentModel[]> fetchListener; |
|||
|
|||
/* |
|||
* i fucking spent the whole day on this and fixing all the fucking problems in this class. |
|||
* DO NO FUCK WITH THIS CODE! |
|||
* -AWAiS (The Badak) @the.badak |
|||
*/ |
|||
public CommentsFetcher(final String shortCode, final FetchListener<CommentModel[]> fetchListener) { |
|||
this.shortCode = shortCode; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
protected CommentModel[] doInBackground(final Void... voids) { |
|||
/* |
|||
"https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" + "{\"shortcode\":\"" + shortcode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; |
|||
|
|||
97b41c52301f77ce508f55e66d17620e -> for comments |
|||
51fdd02b67508306ad4484ff574a0b62 -> for child comments |
|||
|
|||
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""} |
|||
*/ |
|||
final ArrayList<CommentModel> commentModels = getParentComments(); |
|||
|
|||
for (final CommentModel commentModel : commentModels) { |
|||
final CommentModel[] childCommentModels = commentModel.getChildCommentModels(); |
|||
if (childCommentModels != null) { |
|||
final int childCommentsLen = childCommentModels.length; |
|||
|
|||
final CommentModel lastChild = childCommentModels[childCommentsLen - 1]; |
|||
if (lastChild != null && lastChild.hasNextPage() && !Utils.isEmpty(lastChild.getEndCursor())) { |
|||
final CommentModel[] remoteChildComments = getChildComments(commentModel.getId()); |
|||
commentModel.setChildCommentModels(remoteChildComments); |
|||
lastChild.setPageCursor(false, null); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return commentModels.toArray(new CommentModel[0]); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final CommentModel[] result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
|
|||
@NonNull |
|||
private synchronized CommentModel[] getChildComments(final String commentId) { |
|||
final ArrayList<CommentModel> commentModels = new ArrayList<>(); |
|||
|
|||
String endCursor = ""; |
|||
while (endCursor != null) { |
|||
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" + |
|||
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break; |
|||
else { |
|||
final JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data") |
|||
.getJSONObject("comment").getJSONObject("edge_threaded_comments"); |
|||
|
|||
final JSONObject pageInfo = data.getJSONObject("page_info"); |
|||
endCursor = pageInfo.getString("end_cursor"); |
|||
if (Utils.isEmpty(endCursor)) endCursor = null; |
|||
|
|||
final JSONArray childComments = data.optJSONArray("edges"); |
|||
if (childComments != null) { |
|||
final int length = childComments.length(); |
|||
for (int i = 0; i < length; ++i) { |
|||
final JSONObject childComment = childComments.getJSONObject(i).optJSONObject("node"); |
|||
|
|||
if (childComment != null) { |
|||
final JSONObject owner = childComment.getJSONObject("owner"); |
|||
final ProfileModel profileModel = new ProfileModel(false, |
|||
false, |
|||
owner.getString(Constants.EXTRAS_ID), |
|||
owner.getString(Constants.EXTRAS_USERNAME), |
|||
null, null, null, |
|||
owner.getString("profile_pic_url"), |
|||
null, 0, 0, 0); |
|||
|
|||
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by"); |
|||
|
|||
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID), |
|||
childComment.getString("text"), |
|||
childComment.getLong("created_at"), |
|||
likedBy != null ? likedBy.optLong("count", 0) : 0, |
|||
profileModel)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getChildComments", |
|||
new Pair<>("commentModels.size", commentModels.size())); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return commentModels.toArray(new CommentModel[0]); |
|||
} |
|||
|
|||
@NonNull |
|||
private synchronized ArrayList<CommentModel> getParentComments() { |
|||
final ArrayList<CommentModel> commentModelsList = new ArrayList<>(); |
|||
|
|||
String endCursor = ""; |
|||
while (endCursor != null) { |
|||
final String url = "https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" + |
|||
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break; |
|||
else { |
|||
final JSONObject parentComments = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data") |
|||
.getJSONObject("shortcode_media").getJSONObject("edge_media_to_parent_comment"); |
|||
|
|||
final JSONObject pageInfo = parentComments.getJSONObject("page_info"); |
|||
endCursor = pageInfo.optString("end_cursor"); |
|||
if (Utils.isEmpty(endCursor)) endCursor = null; |
|||
|
|||
// final boolean containsToken = endCursor.contains("bifilter_token"); |
|||
// if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) { |
|||
// final JSONObject endCursorObject = new JSONObject(endCursor); |
|||
// endCursor = endCursorObject.optString("cached_comments_cursor"); |
|||
// |
|||
// if (!Utils.isEmpty(endCursor)) |
|||
// endCursor = "{\\\"cached_comments_cursor\\\": \\\"" + endCursor + "\\\", "; |
|||
// else |
|||
// endCursor = "{"; |
|||
// |
|||
// endCursor = endCursor + "\\\"bifilter_token\\\": \\\"" + endCursorObject.getString("bifilter_token") + "\\\"}"; |
|||
// } |
|||
// else if (containsToken) endCursor = null; |
|||
|
|||
final JSONArray comments = parentComments.getJSONArray("edges"); |
|||
final int commentsLen = comments.length(); |
|||
final CommentModel[] commentModels = new CommentModel[commentsLen]; |
|||
|
|||
for (int i = 0; i < commentsLen; ++i) { |
|||
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node"); |
|||
|
|||
final JSONObject owner = comment.getJSONObject("owner"); |
|||
final ProfileModel profileModel = new ProfileModel(false, |
|||
owner.optBoolean("is_verified"), |
|||
owner.getString(Constants.EXTRAS_ID), |
|||
owner.getString(Constants.EXTRAS_USERNAME), |
|||
null, null, null, |
|||
owner.getString("profile_pic_url"), |
|||
null, 0, 0, 0); |
|||
|
|||
final JSONObject likedBy = comment.optJSONObject("edge_liked_by"); |
|||
final String commentId = comment.getString(Constants.EXTRAS_ID); |
|||
commentModels[i] = new CommentModel(commentId, |
|||
comment.getString("text"), |
|||
comment.getLong("created_at"), |
|||
likedBy != null ? likedBy.optLong("count", 0) : 0, |
|||
profileModel); |
|||
|
|||
JSONObject tempJsonObject; |
|||
|
|||
final JSONArray childCommentsArray; |
|||
final int childCommentsLen; |
|||
if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null && |
|||
(childCommentsArray = tempJsonObject.optJSONArray("edges")) != null |
|||
&& (childCommentsLen = childCommentsArray.length()) > 0) { |
|||
|
|||
final String childEndCursor; |
|||
final boolean hasNextPage; |
|||
if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) { |
|||
childEndCursor = tempJsonObject.optString("end_cursor"); |
|||
hasNextPage = tempJsonObject.optBoolean("has_next_page", !Utils.isEmpty(childEndCursor)); |
|||
} else { |
|||
childEndCursor = null; |
|||
hasNextPage = false; |
|||
} |
|||
|
|||
final CommentModel[] childCommentModels = new CommentModel[childCommentsLen]; |
|||
for (int j = 0; j < childCommentsLen; ++j) { |
|||
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node"); |
|||
|
|||
tempJsonObject = childComment.getJSONObject("owner"); |
|||
final ProfileModel childProfileModel = new ProfileModel(false, false, |
|||
tempJsonObject.getString(Constants.EXTRAS_ID), |
|||
tempJsonObject.getString(Constants.EXTRAS_USERNAME), |
|||
null, null, null, |
|||
tempJsonObject.getString("profile_pic_url"), |
|||
null, 0, 0, 0); |
|||
|
|||
tempJsonObject = childComment.optJSONObject("edge_liked_by"); |
|||
childCommentModels[j] = new CommentModel(childComment.getString(Constants.EXTRAS_ID), |
|||
childComment.getString("text"), |
|||
childComment.getLong("created_at"), |
|||
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0, |
|||
childProfileModel); |
|||
} |
|||
|
|||
childCommentModels[childCommentsLen - 1].setPageCursor(hasNextPage, childEndCursor); |
|||
|
|||
commentModels[i].setChildCommentModels(childCommentModels); |
|||
} |
|||
} |
|||
|
|||
Collections.addAll(commentModelsList, commentModels); |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments", |
|||
new Pair<>("commentModelsList.size", commentModelsList.size())); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return commentModelsList; |
|||
} |
|||
} |
@ -0,0 +1,194 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.os.Environment; |
|||
import android.util.Log; |
|||
import android.util.Pair; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.io.File; |
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.DiscoverItemModel; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Constants.FOLDER_PATH; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class DiscoverFetcher extends AsyncTask<Void, Void, DiscoverItemModel[]> { |
|||
private final String maxId; |
|||
private final FetchListener<DiscoverItemModel[]> fetchListener; |
|||
private int lastId = 0; |
|||
private boolean isFirst, moreAvailable; |
|||
private String nextMaxId; |
|||
|
|||
public DiscoverFetcher(final String maxId, final FetchListener<DiscoverItemModel[]> fetchListener, final boolean isFirst) { |
|||
this.maxId = maxId == null ? "" : "&max_id=" + maxId; |
|||
this.fetchListener = fetchListener; |
|||
this.isFirst = isFirst; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected final DiscoverItemModel[] doInBackground(final Void... voids) { |
|||
// to check if file exists |
|||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download"); |
|||
File customDir = null; |
|||
if (settingsHelper.getBoolean(FOLDER_SAVE_TO)) { |
|||
final String customPath = settingsHelper.getString(FOLDER_PATH); |
|||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath); |
|||
} |
|||
|
|||
DiscoverItemModel[] result = null; |
|||
|
|||
final ArrayList<DiscoverItemModel> discoverItemModels = fetchItems(downloadDir, customDir, null, maxId); |
|||
if (discoverItemModels != null) { |
|||
result = discoverItemModels.toArray(new DiscoverItemModel[0]); |
|||
if (result.length > 0) { |
|||
final DiscoverItemModel lastModel = result[result.length - 1]; |
|||
if (lastModel != null) lastModel.setMore(moreAvailable, nextMaxId); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private ArrayList<DiscoverItemModel> fetchItems(final File downloadDir, final File customDir, |
|||
ArrayList<DiscoverItemModel> discoverItemModels, final String maxId) { |
|||
try { |
|||
final String url = "https://www.instagram.com/explore/grid/?is_prefetch=false&omit_cover_media=true&module=explore_popular" + |
|||
"&use_sectional_payload=false&cluster_id=explore_all%3A0&include_fixed_destinations=true" + maxId; |
|||
|
|||
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); |
|||
|
|||
urlConnection.setUseCaches(false); |
|||
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Instagram 72.0.0.21.98 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 132081645)"); |
|||
|
|||
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONObject discoverResponse = new JSONObject(Utils.readFromConnection(urlConnection)); |
|||
|
|||
moreAvailable = discoverResponse.getBoolean("more_available"); |
|||
nextMaxId = discoverResponse.getString("next_max_id"); |
|||
|
|||
final JSONArray sectionalItems = discoverResponse.getJSONArray("sectional_items"); |
|||
if (discoverItemModels == null) discoverItemModels = new ArrayList<>(sectionalItems.length() * 2); |
|||
|
|||
for (int i = 0; i < sectionalItems.length(); ++i) { |
|||
final JSONObject sectionItem = sectionalItems.getJSONObject(i); |
|||
|
|||
final String feedType = sectionItem.getString("feed_type"); |
|||
final String layoutType = sectionItem.getString("layout_type"); |
|||
|
|||
if (sectionItem.has("layout_content") && feedType.equals("media")) { |
|||
final JSONObject layoutContent = sectionItem.getJSONObject("layout_content"); |
|||
|
|||
if ("media_grid".equals(layoutType)) { |
|||
final JSONArray medias = layoutContent.getJSONArray("medias"); |
|||
for (int j = 0; j < medias.length(); ++j) |
|||
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir, |
|||
medias.getJSONObject(j).getJSONObject("media"))); |
|||
|
|||
} else { |
|||
final boolean isOneSide = "one_by_two_left".equals(layoutType); |
|||
if (isOneSide || "two_by_two_right".equals(layoutType)) { |
|||
|
|||
final JSONObject layoutItem = layoutContent.getJSONObject(isOneSide ? "one_by_two_item" : "two_by_two_item"); |
|||
if (layoutItem.has("media")) |
|||
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir, |
|||
layoutItem.getJSONObject("media"))); |
|||
|
|||
if (layoutContent.has("fill_items")) { |
|||
final JSONArray fillItems = layoutContent.getJSONArray("fill_items"); |
|||
for (int j = 0; j < fillItems.length(); ++j) |
|||
discoverItemModels.add(makeDiscoverModel(downloadDir, customDir, |
|||
fillItems.getJSONObject(j).getJSONObject("media"))); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
discoverItemModels.trimToSize(); |
|||
urlConnection.disconnect(); |
|||
|
|||
// hack to fetch 50+ items |
|||
if (this.isFirst) { |
|||
final int size = discoverItemModels.size(); |
|||
if (size > 50) this.isFirst = false; |
|||
discoverItemModels = fetchItems(downloadDir, customDir, discoverItemModels, |
|||
"&max_id=" + (lastId++)); |
|||
} |
|||
} else { |
|||
urlConnection.disconnect(); |
|||
} |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_DISCOVER_FETCHER, "fetchItems", |
|||
new Pair<>("maxId", maxId), |
|||
new Pair<>("lastId", lastId), |
|||
new Pair<>("isFirst", isFirst), |
|||
new Pair<>("nextMaxId", nextMaxId)); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return discoverItemModels; |
|||
} |
|||
|
|||
@NonNull |
|||
private DiscoverItemModel makeDiscoverModel(final File downloadDir, final File customDir, |
|||
@NonNull final JSONObject media) throws Exception { |
|||
final JSONObject user = media.getJSONObject(Constants.EXTRAS_USER); |
|||
final String username = user.getString(Constants.EXTRAS_USERNAME); |
|||
// final ProfileModel userProfileModel = new ProfileModel(user.getBoolean("is_private"), |
|||
// user.getBoolean("is_verified"), |
|||
// String.valueOf(user.get("pk")), |
|||
// username, |
|||
// user.getString("full_name"), |
|||
// null, |
|||
// user.getString("profile_pic_url"), null, |
|||
// 0, 0, 0); |
|||
|
|||
// final String comment; |
|||
// if (!media.has("caption")) comment = null; |
|||
// else { |
|||
// final Object caption = media.get("caption"); |
|||
// comment = caption instanceof JSONObject ? ((JSONObject) caption).getString("text") : null; |
|||
// } |
|||
|
|||
final MediaItemType mediaType = Utils.getMediaItemType(media.getInt("media_type")); |
|||
|
|||
final DiscoverItemModel model = new DiscoverItemModel(mediaType, |
|||
media.getString(Constants.EXTRAS_ID), |
|||
media.getString("code"), |
|||
Utils.getThumbnailUrl(media, mediaType)); |
|||
|
|||
Utils.checkExistence(downloadDir, customDir, username, |
|||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER, -1, model); |
|||
|
|||
return model; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final DiscoverItemModel[] discoverItemModels) { |
|||
if (fetchListener != null) fetchListener.onResult(discoverItemModels); |
|||
} |
|||
} |
@ -0,0 +1,248 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.app.NotificationChannel; |
|||
import android.app.NotificationManager; |
|||
import android.app.PendingIntent; |
|||
import android.content.ContentResolver; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.res.Resources; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.BitmapFactory; |
|||
import android.media.MediaMetadataRetriever; |
|||
import android.media.MediaScannerConnection; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Environment; |
|||
import android.util.Log; |
|||
import android.util.Pair; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.annotation.StringRes; |
|||
import androidx.core.app.NotificationCompat; |
|||
import androidx.core.app.NotificationManagerCompat; |
|||
import androidx.core.content.FileProvider; |
|||
|
|||
import java.io.BufferedInputStream; |
|||
import java.io.File; |
|||
import java.io.FileOutputStream; |
|||
import java.net.URL; |
|||
import java.net.URLConnection; |
|||
import java.util.concurrent.atomic.AtomicReference; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.ProfileViewer; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.CHANNEL_ID; |
|||
import static awais.instagrabber.utils.Utils.CHANNEL_NAME; |
|||
import static awais.instagrabber.utils.Utils.NOTIF_GROUP_NAME; |
|||
import static awais.instagrabber.utils.Utils.isChannelCreated; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awais.instagrabber.utils.Utils.notificationManager; |
|||
import static awaisomereport.LogCollector.LogFile; |
|||
|
|||
public final class DownloadAsync extends AsyncTask<Void, Float, File> { |
|||
private static int lastNotifId = 1; |
|||
private final int currentNotifId; |
|||
private final AtomicReference<Context> context; |
|||
private final File outFile; |
|||
private final String url; |
|||
private final FetchListener<File> fetchListener; |
|||
private final Resources resources; |
|||
private final NotificationCompat.Builder downloadNotif; |
|||
private String shortCode, username; |
|||
|
|||
public DownloadAsync(final Context context, final String url, final File outFile, final FetchListener<File> fetchListener) { |
|||
this.context = new AtomicReference<>(context); |
|||
this.resources = context.getResources(); |
|||
this.url = url; |
|||
this.outFile = outFile; |
|||
this.fetchListener = fetchListener; |
|||
this.shortCode = this.username = resources.getString(R.string.downloader_started); |
|||
this.currentNotifId = ++lastNotifId; |
|||
if (++lastNotifId + 1 == Integer.MAX_VALUE) lastNotifId = 1; |
|||
|
|||
if (notificationManager == null) |
|||
notificationManager = NotificationManagerCompat.from(context.getApplicationContext()); |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isChannelCreated) { |
|||
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, |
|||
CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)); |
|||
isChannelCreated = true; |
|||
} |
|||
|
|||
@StringRes final int titleRes = context instanceof ProfileViewer ? R.string.downloader_downloading_pfp : R.string.downloader_downloading_post; |
|||
|
|||
downloadNotif = new NotificationCompat.Builder(context, CHANNEL_ID).setCategory(NotificationCompat.CATEGORY_STATUS) |
|||
.setSmallIcon(R.mipmap.ic_launcher).setContentText(shortCode == null ? username : shortCode).setOngoing(true) |
|||
.setProgress(100, 0, false).setAutoCancel(false).setOnlyAlertOnce(true) |
|||
.setContentTitle(resources.getString(titleRes)); |
|||
|
|||
notificationManager.notify(currentNotifId, downloadNotif.build()); |
|||
} |
|||
|
|||
public DownloadAsync setItems(final String shortCode, final String username) { |
|||
this.shortCode = shortCode; |
|||
this.username = username; |
|||
if (downloadNotif != null) downloadNotif.setContentText(this.shortCode == null ? this.username : this.shortCode); |
|||
return this; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected File doInBackground(final Void... voids) { |
|||
try { |
|||
final URLConnection urlConnection = new URL(url).openConnection(); |
|||
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : |
|||
urlConnection.getContentLength(); |
|||
float totalRead = 0; |
|||
|
|||
try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream()); |
|||
final FileOutputStream fos = new FileOutputStream(outFile)) { |
|||
final byte[] buffer = new byte[0x2000]; |
|||
|
|||
int count; |
|||
boolean deletedIPTC = false; |
|||
while ((count = bis.read(buffer, 0, 0x2000)) != -1) { |
|||
totalRead = totalRead + count; |
|||
|
|||
if (!deletedIPTC) { |
|||
int iptcStart = -1; |
|||
int fbmdStart = -1; |
|||
int fbmdBytesLen = -1; |
|||
|
|||
for (int i = 0; i < buffer.length; ++i) { |
|||
if (buffer[i] == (byte) 0xFF && buffer[i + 1] == (byte) 0xED) |
|||
iptcStart = i; |
|||
else if (buffer[i] == (byte) 'F' && buffer[i + 1] == (byte) 'B' |
|||
&& buffer[i + 2] == (byte) 'M' && buffer[i + 3] == (byte) 'D') { |
|||
fbmdStart = i; |
|||
fbmdBytesLen = buffer[i - 10] << 24 | (buffer[i - 9] & 0xFF) << 16 | |
|||
(buffer[i - 8] & 0xFF) << 8 | (buffer[i - 7] & 0xFF) | |
|||
(buffer[i - 6] & 0xFF); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (iptcStart != -1 && fbmdStart != -1 && fbmdBytesLen != -1) { |
|||
final int fbmdDataLen = (iptcStart + (fbmdStart - iptcStart) + (fbmdBytesLen - iptcStart)) - 4; |
|||
|
|||
fos.write(buffer, 0, iptcStart); |
|||
fos.write(buffer, fbmdDataLen + iptcStart, count - fbmdDataLen - iptcStart); |
|||
|
|||
publishProgress(totalRead * 100f / fileSize); |
|||
|
|||
deletedIPTC = true; |
|||
continue; |
|||
} |
|||
} |
|||
|
|||
fos.write(buffer, 0, count); |
|||
publishProgress(totalRead * 100f / fileSize); |
|||
} |
|||
fos.flush(); |
|||
} |
|||
|
|||
return outFile; |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "doInBackground", |
|||
new Pair<>("context", context.get()), |
|||
new Pair<>("resources", resources), |
|||
new Pair<>("lastNotifId", lastNotifId), |
|||
new Pair<>("downloadNotif", downloadNotif), |
|||
new Pair<>("currentNotifId", currentNotifId), |
|||
new Pair<>("notificationManager", notificationManager)); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onProgressUpdate(@NonNull final Float... values) { |
|||
if (downloadNotif != null) { |
|||
downloadNotif.setProgress(100, values[0].intValue(), false); |
|||
notificationManager.notify(currentNotifId, downloadNotif.build()); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final File result) { |
|||
if (result != null) { |
|||
final Context context = this.context.get(); |
|||
|
|||
context.sendBroadcast(Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT ? |
|||
new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(Environment.getExternalStorageDirectory())) : |
|||
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(result.getAbsoluteFile())) |
|||
); |
|||
MediaScannerConnection.scanFile(context, new String[]{result.getAbsolutePath()}, null, null); |
|||
|
|||
if (notificationManager != null) { |
|||
final Uri uri = FileProvider.getUriForFile(context, "awais.instagrabber.provider", result); |
|||
|
|||
final ContentResolver contentResolver = context.getContentResolver(); |
|||
Bitmap bitmap = null; |
|||
if (Utils.isImage(uri, contentResolver)) { |
|||
try { |
|||
bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(uri)); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_1"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
} |
|||
|
|||
if (bitmap == null) { |
|||
final MediaMetadataRetriever retriever = new MediaMetadataRetriever(); |
|||
try { |
|||
try { |
|||
retriever.setDataSource(context, uri); |
|||
} catch (final Exception e) { |
|||
retriever.setDataSource(result.getAbsolutePath()); |
|||
} |
|||
bitmap = retriever.getFrameAtTime(); |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) |
|||
try { |
|||
retriever.close(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_2"); |
|||
} |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_DOWNLOADER, "onPostExecute::bitmap_3"); |
|||
} |
|||
} |
|||
|
|||
final String downloadComplete = resources.getString(R.string.downloader_complete); |
|||
|
|||
downloadNotif.setContentText(null).setContentTitle(downloadComplete).setProgress(0, 0, false) |
|||
.setWhen(System.currentTimeMillis()).setOngoing(false).setOnlyAlertOnce(false).setAutoCancel(true) |
|||
.setGroup(NOTIF_GROUP_NAME).setGroupSummary(true).setContentIntent( |
|||
PendingIntent.getActivity(context, 2020, new Intent(Intent.ACTION_VIEW, uri) |
|||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FROM_BACKGROUND | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
|||
.putExtra(Intent.EXTRA_STREAM, uri), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT)); |
|||
|
|||
if (bitmap != null) |
|||
downloadNotif.setStyle(new NotificationCompat.BigPictureStyle().setBigContentTitle(downloadComplete).bigPicture(bitmap)) |
|||
.setLargeIcon(bitmap).setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL); |
|||
|
|||
notificationManager.cancel(currentNotifId); |
|||
notificationManager.notify(currentNotifId + 1, downloadNotif.build()); |
|||
} |
|||
} |
|||
|
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,194 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class FeedFetcher extends AsyncTask<Void, Void, FeedModel[]> { |
|||
private static final int maxItemsToLoad = 25; // max is 50, but that's too many posts, setting more than 30 is gay |
|||
private final String endCursor; |
|||
private final FetchListener<FeedModel[]> fetchListener; |
|||
|
|||
public FeedFetcher(final FetchListener<FeedModel[]> fetchListener) { |
|||
this.endCursor = ""; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
public FeedFetcher(final String endCursor, final FetchListener<FeedModel[]> fetchListener) { |
|||
this.endCursor = endCursor == null ? "" : endCursor; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected final FeedModel[] doInBackground(final Void... voids) { |
|||
FeedModel[] result = null; |
|||
try { |
|||
// |
|||
// stories: 04334405dbdef91f2c4e207b84c204d7 && https://i.instagram.com/api/v1/feed/reels_tray/ |
|||
// https://www.instagram.com/graphql/query/?query_hash=04334405dbdef91f2c4e207b84c204d7&variables={"only_stories":true,"stories_prefetch":false,"stories_video_dash_manifest":false} |
|||
// /////////////////////////////////////////////// |
|||
// feed: |
|||
// https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables= |
|||
// {"cached_feed_item_ids":[],"fetch_media_item_count":12,"fetch_media_item_cursor":"<end_cursor>","fetch_comment_count":4,"fetch_like":3,"has_stories":false,"has_threaded_comments":true} |
|||
// only used: fetch_media_item_cursor, fetch_media_item_count: 100 (max 50), has_threaded_comments = true |
|||
// ////////////////////////////////////////////// |
|||
// more unknowns: https://github.com/qsniyg/rssit/blob/master/rssit/generators/instagram.py |
|||
// |
|||
|
|||
final String url = "https://www.instagram.com/graphql/query/?query_hash=6b838488258d7a4820e48d209ef79eb1&variables=" + |
|||
"{\"fetch_media_item_count\":" + maxItemsToLoad + ",\"has_threaded_comments\":true,\"fetch_media_item_cursor\":\"" + endCursor + "\"}"; |
|||
final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); |
|||
|
|||
if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONObject timelineFeed = new JSONObject(Utils.readFromConnection(urlConnection)).getJSONObject("data") |
|||
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_web_feed_timeline"); |
|||
|
|||
final String endCursor; |
|||
final boolean hasNextPage; |
|||
|
|||
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); |
|||
if (pageInfo.has("has_next_page")) { |
|||
hasNextPage = pageInfo.getBoolean("has_next_page"); |
|||
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; |
|||
} else { |
|||
hasNextPage = false; |
|||
endCursor = null; |
|||
} |
|||
|
|||
final JSONArray feedItems = timelineFeed.getJSONArray("edges"); |
|||
|
|||
final int feedLen = feedItems.length(); |
|||
final ArrayList<FeedModel> feedModelsList = new ArrayList<>(feedLen); |
|||
for (int i = 0; i < feedLen; ++i) { |
|||
final JSONObject feedItem = feedItems.getJSONObject(i).getJSONObject("node"); |
|||
final String mediaType = feedItem.optString("__typename"); |
|||
if (mediaType.isEmpty() || "GraphSuggestedUserFeedUnit".equals(mediaType)) continue; |
|||
|
|||
final boolean isVideo = feedItem.optBoolean("is_video"); |
|||
final long videoViews = feedItem.optLong("video_view_count", 0); |
|||
|
|||
final String displayUrl = feedItem.getString("display_url"); |
|||
final String resourceUrl; |
|||
|
|||
if (isVideo) resourceUrl = feedItem.getString("video_url"); |
|||
else resourceUrl = feedItem.has("display_resources") ? Utils.getHighQualityImage(feedItem) : displayUrl; |
|||
|
|||
ProfileModel profileModel = null; |
|||
if (feedItem.has("owner")) { |
|||
final JSONObject owner = feedItem.getJSONObject("owner"); |
|||
profileModel = new ProfileModel(owner.optBoolean("is_private"), |
|||
owner.optBoolean("is_verified"), |
|||
owner.getString(Constants.EXTRAS_ID), |
|||
owner.getString(Constants.EXTRAS_USERNAME), |
|||
owner.optString("full_name"), |
|||
null, null, |
|||
owner.getString("profile_pic_url"), |
|||
null, 0, 0, 0); |
|||
} |
|||
|
|||
JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); |
|||
final long commentsCount = tempJsonObject != null ? tempJsonObject.optLong("count") : 0; |
|||
|
|||
tempJsonObject = feedItem.optJSONObject("edge_media_to_caption"); |
|||
final JSONArray captions = tempJsonObject != null ? tempJsonObject.getJSONArray("edges") : null; |
|||
|
|||
String captionText = null; |
|||
if (captions != null && captions.length() > 0) { |
|||
if ((tempJsonObject = captions.optJSONObject(0)) != null && |
|||
(tempJsonObject = tempJsonObject.optJSONObject("node")) != null) |
|||
captionText = tempJsonObject.getString("text"); |
|||
} |
|||
|
|||
final FeedModel feedModel = new FeedModel(profileModel, |
|||
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, |
|||
videoViews, |
|||
feedItem.getString(Constants.EXTRAS_ID), |
|||
resourceUrl, |
|||
displayUrl, |
|||
feedItem.getString(Constants.EXTRAS_SHORTCODE), |
|||
captionText, |
|||
commentsCount, |
|||
feedItem.optLong("taken_at_timestamp", -1)); |
|||
|
|||
final boolean isSlider = "GraphSidecar".equals(mediaType) && feedItem.has("edge_sidecar_to_children"); |
|||
|
|||
if (isSlider) { |
|||
final JSONObject sidecar = feedItem.optJSONObject("edge_sidecar_to_children"); |
|||
if (sidecar != null) { |
|||
final JSONArray children = sidecar.optJSONArray("edges"); |
|||
|
|||
if (children != null) { |
|||
final ViewerPostModel[] sliderItems = new ViewerPostModel[children.length()]; |
|||
|
|||
for (int j = 0; j < sliderItems.length; ++j) { |
|||
final JSONObject node = children.optJSONObject(j).getJSONObject("node"); |
|||
final boolean isChildVideo = node.optBoolean("is_video"); |
|||
|
|||
sliderItems[j] = new ViewerPostModel( |
|||
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, |
|||
node.getString(Constants.EXTRAS_ID), |
|||
isChildVideo ? node.getString("video_url") : Utils.getHighQualityImage(node), |
|||
null, null, null, |
|||
node.optLong("video_view_count", -1), -1); |
|||
|
|||
sliderItems[j].setSliderDisplayUrl(node.getString("display_url")); |
|||
} |
|||
|
|||
feedModel.setSliderItems(sliderItems); |
|||
} |
|||
} |
|||
} |
|||
|
|||
feedModelsList.add(feedModel); |
|||
} |
|||
|
|||
feedModelsList.trimToSize(); |
|||
|
|||
final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]); |
|||
if (feedModels[feedModels.length - 1] != null) |
|||
feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor); |
|||
|
|||
result = feedModels; |
|||
} |
|||
|
|||
urlConnection.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_FEED_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final FeedModel[] postModels) { |
|||
if (fetchListener != null) fetchListener.onResult(postModels); |
|||
} |
|||
} |
@ -0,0 +1,103 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.FeedStoryModel; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector.LogFile; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class FeedStoriesFetcher extends AsyncTask<Void, Void, FeedStoryModel[]> { |
|||
private final FetchListener<FeedStoryModel[]> fetchListener; |
|||
|
|||
public FeedStoriesFetcher(final FetchListener<FeedStoryModel[]> fetchListener) { |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Override |
|||
protected FeedStoryModel[] doInBackground(final Void... voids) { |
|||
FeedStoryModel[] result = null; |
|||
String url = "https://www.instagram.com/graphql/query/?query_hash=b7b84d884400bc5aa7cfe12ae843a091&variables=" + |
|||
"{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}"; |
|||
|
|||
try { |
|||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setInstanceFollowRedirects(false); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONArray feedStoriesReel = new JSONObject(Utils.readFromConnection(conn)) |
|||
.getJSONObject("data") |
|||
.getJSONObject(Constants.EXTRAS_USER) |
|||
.getJSONObject("feed_reels_tray") |
|||
.getJSONObject("edge_reels_tray_to_reel") |
|||
.getJSONArray("edges"); |
|||
|
|||
conn.disconnect(); |
|||
|
|||
final int storiesLen = feedStoriesReel.length(); |
|||
final FeedStoryModel[] feedStoryModels = new FeedStoryModel[storiesLen]; |
|||
final String[] feedStoryIDs = new String[storiesLen]; |
|||
|
|||
for (int i = 0; i < storiesLen; ++i) { |
|||
final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node"); |
|||
|
|||
final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner"); |
|||
final ProfileModel profileModel = new ProfileModel(false, false, |
|||
user.getString("id"), |
|||
user.getString("username"), |
|||
null, null, null, |
|||
user.getString("profile_pic_url"), |
|||
null, 0, 0, 0); |
|||
|
|||
final String id = node.getString("id"); |
|||
feedStoryIDs[i] = id; |
|||
feedStoryModels[i] = new FeedStoryModel(id, profileModel); |
|||
} |
|||
|
|||
url = "https://www.instagram.com/graphql/query/?query_hash=0a85e6ea60a4c99edc58ab2f3d17cfdf&variables=" + |
|||
"{\"reel_ids\":" + Utils.highlightIdsMerger(feedStoryIDs) + ",\"precomposed_overlay\":false}"; |
|||
conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setInstanceFollowRedirects(false); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
Utils.putHighlightModels(conn, feedStoryModels); |
|||
} |
|||
|
|||
result = feedStoryModels; |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_FEED_STORY_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final FeedStoryModel[] result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,101 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.FollowModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class FollowFetcher extends AsyncTask<Void, Void, FollowModel[]> { |
|||
private final String endCursor, id; |
|||
private final boolean isFollowers; |
|||
private final FetchListener<FollowModel[]> fetchListener; |
|||
|
|||
public FollowFetcher(final String id, final boolean isFollowers, final FetchListener<FollowModel[]> fetchListener) { |
|||
this.id = id; |
|||
this.endCursor = ""; |
|||
this.isFollowers = isFollowers; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
public FollowFetcher(final String id, final boolean isFollowers, final String endCursor, final FetchListener<FollowModel[]> fetchListener) { |
|||
this.id = id; |
|||
this.endCursor = endCursor == null ? "" : endCursor; |
|||
this.isFollowers = isFollowers; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected FollowModel[] doInBackground(final Void... voids) { |
|||
FollowModel[] result = null; |
|||
final String url = "https://www.instagram.com/graphql/query/?query_id=" + (isFollowers ? "17851374694183129" : "17874545323001329") |
|||
+ "&id=" + id + "&first=50&after=" + endCursor; |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setInstanceFollowRedirects(false); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data") |
|||
.getJSONObject(Constants.EXTRAS_USER).getJSONObject(isFollowers ? "edge_followed_by" : "edge_follow"); |
|||
|
|||
final String endCursor; |
|||
final boolean hasNextPage; |
|||
|
|||
final JSONObject pageInfo = data.getJSONObject("page_info"); |
|||
if (pageInfo.has("has_next_page")) { |
|||
hasNextPage = pageInfo.getBoolean("has_next_page"); |
|||
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; |
|||
} else { |
|||
hasNextPage = false; |
|||
endCursor = null; |
|||
} |
|||
|
|||
final JSONArray edges = data.getJSONArray("edges"); |
|||
final FollowModel[] models = new FollowModel[edges.length()]; |
|||
for (int i = 0; i < models.length; ++i) { |
|||
final JSONObject followNode = edges.getJSONObject(i).getJSONObject("node"); |
|||
models[i] = new FollowModel(followNode.getString(Constants.EXTRAS_ID), followNode.getString(Constants.EXTRAS_USERNAME), |
|||
followNode.getString("full_name"), followNode.getString("profile_pic_url")); |
|||
} |
|||
|
|||
if (models[models.length - 1] != null) |
|||
models[models.length - 1].setPageCursor(hasNextPage, endCursor); |
|||
|
|||
result = models; |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_FOLLOW_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final FollowModel[] result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,87 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.HighlightModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class HighlightsFetcher extends AsyncTask<Void, Void, HighlightModel[]> { |
|||
private final String id; |
|||
private final FetchListener<HighlightModel[]> fetchListener; |
|||
|
|||
public HighlightsFetcher(final String id, final FetchListener<HighlightModel[]> fetchListener) { |
|||
this.id = id; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Override |
|||
protected HighlightModel[] doInBackground(final Void... voids) { |
|||
HighlightModel[] result = null; |
|||
String url = "https://www.instagram.com/graphql/query/?query_hash=7c16654f22c819fb63d1183034a5162f&variables=" + |
|||
"{\"user_id\":\"" + id + "\",\"include_chaining\":false,\"include_reel\":true,\"include_suggested_users\":false," + |
|||
"\"include_logged_out_extras\":false,\"include_highlight_reels\":true}"; |
|||
|
|||
try { |
|||
HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setInstanceFollowRedirects(false); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONArray highlightsReel = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data") |
|||
.getJSONObject(Constants.EXTRAS_USER).getJSONObject("edge_highlight_reels").getJSONArray("edges"); |
|||
|
|||
final int length = highlightsReel.length(); |
|||
final HighlightModel[] highlightModels = new HighlightModel[length]; |
|||
final String[] highlightIds = new String[length]; |
|||
for (int i = 0; i < length; ++i) { |
|||
final JSONObject highlightNode = highlightsReel.getJSONObject(i).getJSONObject("node"); |
|||
final String id = highlightNode.getString(Constants.EXTRAS_ID); |
|||
highlightIds[i] = id; |
|||
highlightModels[i] = new HighlightModel( |
|||
highlightNode.getString("title"), |
|||
highlightNode.getJSONObject("cover_media").getString("thumbnail_src") |
|||
); |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
|
|||
// a22a50ce4582220909e302d6eb84d259 |
|||
// 45246d3fe16ccc6577e0bd297a5db1ab |
|||
url = "https://www.instagram.com/graphql/query/?query_hash=a22a50ce4582220909e302d6eb84d259&variables=" + |
|||
"{\"highlight_reel_ids\":" + Utils.highlightIdsMerger(highlightIds) + ",\"reel_ids\":[],\"location_ids\":[],\"precomposed_overlay\":false}"; |
|||
conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setInstanceFollowRedirects(false); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
Utils.putHighlightModels(conn, highlightModels); |
|||
} |
|||
|
|||
result = highlightModels; |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final HighlightModel[] result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,146 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.os.Environment; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.io.File; |
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Constants.FOLDER_PATH; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]> { |
|||
private final String shortCode; |
|||
private final FetchListener<ViewerPostModel[]> fetchListener; |
|||
|
|||
public PostFetcher(final String shortCode, final FetchListener<ViewerPostModel[]> fetchListener) { |
|||
this.shortCode = shortCode; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Override |
|||
protected ViewerPostModel[] doInBackground(final Void... voids) { |
|||
ViewerPostModel[] result = null; |
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection(); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
// to check if file exists |
|||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download"); |
|||
File customDir = null; |
|||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { |
|||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); |
|||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath); |
|||
} |
|||
|
|||
final JSONObject media = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("graphql") |
|||
.getJSONObject("shortcode_media"); |
|||
|
|||
final String username = media.has("owner") ? media.getJSONObject("owner").getString(Constants.EXTRAS_USERNAME) : null; |
|||
|
|||
final long timestamp = media.getLong("taken_at_timestamp"); |
|||
|
|||
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video"); |
|||
final boolean isSlider = media.has("edge_sidecar_to_children"); |
|||
|
|||
final MediaItemType mediaItemType; |
|||
if (isSlider) mediaItemType = MediaItemType.MEDIA_TYPE_SLIDER; |
|||
else if (isVideo) mediaItemType = MediaItemType.MEDIA_TYPE_VIDEO; |
|||
else mediaItemType = MediaItemType.MEDIA_TYPE_IMAGE; |
|||
|
|||
final String postCaption; |
|||
final JSONObject mediaToCaption = media.optJSONObject("edge_media_to_caption"); |
|||
if (mediaToCaption == null) postCaption = null; |
|||
else { |
|||
final JSONArray captions = mediaToCaption.optJSONArray("edges"); |
|||
postCaption = captions != null && captions.length() > 0 ? |
|||
captions.getJSONObject(0).getJSONObject("node").optString("text") : null; |
|||
} |
|||
|
|||
JSONObject commentObject = media.optJSONObject("edge_media_to_parent_comment"); |
|||
final long commentsCount = commentObject != null ? commentObject.optLong("count") : 0; |
|||
|
|||
String endCursor = null; |
|||
if (commentObject != null && (commentObject = commentObject.optJSONObject("page_info")) != null) |
|||
endCursor = commentObject.optString("end_cursor"); |
|||
|
|||
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) { |
|||
final ViewerPostModel postModel = new ViewerPostModel(mediaItemType, |
|||
media.getString(Constants.EXTRAS_ID), |
|||
isVideo ? media.getString("video_url") : Utils.getHighQualityImage(media), |
|||
shortCode, |
|||
Utils.isEmpty(postCaption) ? null : postCaption, |
|||
username, |
|||
isVideo && media.has("video_view_count") ? media.getLong("video_view_count") : -1, |
|||
timestamp); |
|||
|
|||
postModel.setCommentsCount(commentsCount); |
|||
postModel.setCommentsEndCursor(endCursor); |
|||
|
|||
Utils.checkExistence(downloadDir, customDir, username, false, -1, postModel); |
|||
|
|||
result = new ViewerPostModel[]{postModel}; |
|||
|
|||
} else { |
|||
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges"); |
|||
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()]; |
|||
|
|||
for (int i = 0; i < postModels.length; ++i) { |
|||
final JSONObject node = children.getJSONObject(i).getJSONObject("node"); |
|||
final boolean isChildVideo = node.getBoolean("is_video"); |
|||
|
|||
postModels[i] = new ViewerPostModel(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, |
|||
node.getString(Constants.EXTRAS_ID), |
|||
isChildVideo ? node.getString("video_url") : Utils.getHighQualityImage(node), |
|||
node.getString(Constants.EXTRAS_SHORTCODE), |
|||
postCaption, |
|||
username, |
|||
isChildVideo && node.has("video_view_count") ? node.getLong("video_view_count") : -1, |
|||
timestamp); |
|||
postModels[i].setSliderDisplayUrl(node.getString("display_url")); |
|||
|
|||
Utils.checkExistence(downloadDir, customDir, username, true, i, postModels[i]); |
|||
} |
|||
|
|||
postModels[0].setCommentsCount(commentsCount); |
|||
postModels[0].setCommentsEndCursor(endCursor); |
|||
|
|||
result = postModels; |
|||
} |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final ViewerPostModel[] postModels) { |
|||
if (fetchListener != null) fetchListener.onResult(postModels); |
|||
} |
|||
} |
@ -0,0 +1,134 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.os.Environment; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.io.File; |
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Constants.FOLDER_PATH; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class PostsFetcher extends AsyncTask<Void, Void, PostModel[]> { |
|||
private final String endCursor; |
|||
private final String id; |
|||
private final FetchListener<PostModel[]> fetchListener; |
|||
private String username; |
|||
|
|||
public PostsFetcher(final String id, final FetchListener<PostModel[]> fetchListener) { |
|||
this.id = id; |
|||
this.endCursor = ""; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
public PostsFetcher(final String id, final String endCursor, final FetchListener<PostModel[]> fetchListener) { |
|||
this.id = id; |
|||
this.endCursor = endCursor == null ? "" : endCursor; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
public PostsFetcher setUsername(final String username) { |
|||
this.username = username; |
|||
return this; |
|||
} |
|||
|
|||
@Override |
|||
protected PostModel[] doInBackground(final Void... voids) { |
|||
final boolean isHashTag = id.charAt(0) == '#'; |
|||
|
|||
final String url; |
|||
if (isHashTag) |
|||
url = "https://www.instagram.com/graphql/query/?query_hash=ded47faa9a1aaded10161a2ff32abb6b&variables=" + |
|||
"{\"tag_name\":\"" + id.substring(1) + "\",\"first\":150,\"after\":\"" + endCursor + "\"}"; |
|||
else |
|||
url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor; |
|||
|
|||
PostModel[] result = null; |
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
// to check if file exists |
|||
final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download"); |
|||
File customDir = null; |
|||
if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { |
|||
final String customPath = Utils.settingsHelper.getString(FOLDER_PATH); |
|||
if (!Utils.isEmpty(customPath)) customDir = new File(customPath); |
|||
} |
|||
|
|||
final JSONObject mediaPosts = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data") |
|||
.getJSONObject(isHashTag ? "hashtag" : Constants.EXTRAS_USER) |
|||
.getJSONObject(isHashTag ? "edge_hashtag_to_media" : "edge_owner_to_timeline_media"); |
|||
|
|||
final String endCursor; |
|||
final boolean hasNextPage; |
|||
|
|||
final JSONObject pageInfo = mediaPosts.getJSONObject("page_info"); |
|||
if (pageInfo.has("has_next_page")) { |
|||
hasNextPage = pageInfo.getBoolean("has_next_page"); |
|||
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; |
|||
} else { |
|||
hasNextPage = false; |
|||
endCursor = null; |
|||
} |
|||
|
|||
final JSONArray edges = mediaPosts.getJSONArray("edges"); |
|||
final PostModel[] models = new PostModel[edges.length()]; |
|||
for (int i = 0; i < models.length; ++i) { |
|||
final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); |
|||
final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges"); |
|||
|
|||
final boolean isSlider = mediaNode.has("__typename") && mediaNode.getString("__typename").equals("GraphSidecar"); |
|||
final boolean isVideo = mediaNode.getBoolean("is_video"); |
|||
|
|||
final MediaItemType itemType; |
|||
if (isSlider) itemType = MediaItemType.MEDIA_TYPE_SLIDER; |
|||
else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; |
|||
else itemType = MediaItemType.MEDIA_TYPE_IMAGE; |
|||
|
|||
models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), |
|||
mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"), |
|||
mediaNode.getString(Constants.EXTRAS_SHORTCODE), |
|||
captions.length() > 0 ? captions.getJSONObject(0).getJSONObject("node").getString("text") : null, |
|||
mediaNode.getLong("taken_at_timestamp")); |
|||
|
|||
Utils.checkExistence(downloadDir, customDir, username, isSlider, -1, models[i]); |
|||
} |
|||
|
|||
if (models[models.length - 1] != null) |
|||
models[models.length - 1].setPageCursor(hasNextPage, endCursor); |
|||
|
|||
result = models; |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final PostModel[] postModels) { |
|||
if (fetchListener != null) fetchListener.onResult(postModels); |
|||
} |
|||
} |
@ -0,0 +1,83 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.ProfileModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class ProfileFetcher extends AsyncTask<Void, Void, ProfileModel> { |
|||
private final FetchListener<ProfileModel> fetchListener; |
|||
private final String userName; |
|||
|
|||
public ProfileFetcher(String userName, FetchListener<ProfileModel> fetchListener) { |
|||
this.userName = userName; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected ProfileModel doInBackground(final Void... voids) { |
|||
ProfileModel result = null; |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/" + userName + "/?__a=1").openConnection(); |
|||
conn.setUseCaches(true); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONObject user = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_USER); |
|||
|
|||
boolean isPrivate = user.getBoolean("is_private"); |
|||
final JSONObject timelineMedia = user.getJSONObject("edge_owner_to_timeline_media"); |
|||
if (timelineMedia.has("edges")) { |
|||
final JSONArray edges = timelineMedia.getJSONArray("edges"); |
|||
if (edges.length() > 0) isPrivate = false; |
|||
} |
|||
|
|||
String url = user.optString("external_url"); |
|||
if (Utils.isEmpty(url)) url = null; |
|||
|
|||
result = new ProfileModel(isPrivate, |
|||
user.getBoolean("is_verified"), |
|||
user.getString(Constants.EXTRAS_ID), |
|||
userName, |
|||
user.getString("full_name"), |
|||
user.getString("biography"), |
|||
url, |
|||
user.getString("profile_pic_url"), |
|||
user.getString("profile_pic_url_hd"), |
|||
timelineMedia.getLong("count"), |
|||
user.getJSONObject("edge_followed_by").getLong("count"), |
|||
user.getJSONObject("edge_follow").getLong("count")); |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final ProfileModel result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,120 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
import android.util.Pair; |
|||
|
|||
import org.json.JSONObject; |
|||
import org.jsoup.Jsoup; |
|||
import org.jsoup.nodes.Document; |
|||
import org.jsoup.nodes.Element; |
|||
import org.jsoup.select.Elements; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
import awais.instagrabber.models.enums.ProfilePictureFetchMode; |
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> { |
|||
private final FetchListener<String> fetchListener; |
|||
private final String userName, userId; |
|||
private final ProfilePictureFetchMode fetchMode; |
|||
|
|||
public ProfilePictureFetcher(final String userName, final String userId, final FetchListener<String> fetchListener, |
|||
final ProfilePictureFetchMode fetchMode) { |
|||
this.fetchListener = fetchListener; |
|||
this.fetchMode = fetchMode; |
|||
this.userName = userName; |
|||
this.userId = userId; |
|||
} |
|||
|
|||
@Override |
|||
protected String doInBackground(final Void... voids) { |
|||
String out = null; |
|||
try { |
|||
final String url; |
|||
|
|||
if (fetchMode == ProfilePictureFetchMode.INSTADP) |
|||
url = "https://instadp.com/fullsize/" + userName; |
|||
else if (fetchMode == ProfilePictureFetchMode.INSTA_STALKER) |
|||
url = "https://insta-stalker.co/instadp_fullsize/?id=" + userName; |
|||
else // select from s1, s2, s3 but s1 works fine |
|||
url = "https://instafullsize.com/ifsapi/ig/photo/s1/" + userName + "?igid=" + userId; |
|||
|
|||
// prolly http://167.99.85.4/instagram/userid?profile-url=the.badak |
|||
|
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setUseCaches(false); |
|||
|
|||
if (fetchMode == ProfilePictureFetchMode.INSTAFULLSIZE) { |
|||
conn.setRequestMethod("GET"); |
|||
conn.setRequestProperty("Authorization", "fjgt842ff582a"); |
|||
} |
|||
|
|||
final String result = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? Utils.readFromConnection(conn) : null; |
|||
conn.disconnect(); |
|||
|
|||
if (!Utils.isEmpty(result)) { |
|||
final Document doc = Jsoup.parse(result); |
|||
boolean fallback = false; |
|||
|
|||
if (fetchMode == ProfilePictureFetchMode.INSTADP) { |
|||
final int imgIndex = result.indexOf("preloadImg('"), lastIndex; |
|||
|
|||
Element element = doc.selectFirst(".instadp"); |
|||
if (element != null && (element = element.selectFirst(".picture")) != null) |
|||
out = element.attr("src"); |
|||
else if ((element = doc.selectFirst(".download-btn")) != null) |
|||
out = element.attr("href"); |
|||
else if (imgIndex != -1 && (lastIndex = result.indexOf("')", imgIndex)) != -1) |
|||
out = result.substring(imgIndex + 12, lastIndex); |
|||
else fallback = true; |
|||
|
|||
} else if (fetchMode == ProfilePictureFetchMode.INSTAFULLSIZE) { |
|||
try { |
|||
final JSONObject object = new JSONObject(result); |
|||
out = object.getString("result"); |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
fallback = true; |
|||
} |
|||
|
|||
} else { |
|||
final Elements elements = doc.select("img[data-src]"); |
|||
if (elements.size() > 0) out = elements.get(0).attr("data-src"); |
|||
else fallback = true; |
|||
} |
|||
|
|||
if (fallback) { |
|||
final Elements imgs = doc.getElementsByTag("img"); |
|||
for (final Element img : imgs) { |
|||
final String imgStr = img.toString(); |
|||
if (imgStr.contains("cdninstagram.com")) return img.attr("src"); |
|||
} |
|||
} |
|||
} |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_PROFILE_PICTURE_FETCHER, "doInBackground", |
|||
new Pair<>("fetchMode", fetchMode)); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return out; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final String result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,102 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.enums.MediaItemType; |
|||
import awais.instagrabber.models.StoryModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.LogCollector; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
|
|||
public final class StoryStatusFetcher extends AsyncTask<Void, Void, StoryModel[]> { |
|||
private final String id; |
|||
private final FetchListener<StoryModel[]> fetchListener; |
|||
|
|||
public StoryStatusFetcher(final String id, final FetchListener<StoryModel[]> fetchListener) { |
|||
this.id = id; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Override |
|||
protected StoryModel[] doInBackground(final Void... voids) { |
|||
StoryModel[] result = null; |
|||
final String url = "https://www.instagram.com/graphql/query/?query_hash=52a36e788a02a3c612742ed5146f1676&variables=" + |
|||
"{\"precomposed_overlay\":false,\"reel_ids\":[\"" + id + "\"]}"; |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setInstanceFollowRedirects(false); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("data"); |
|||
|
|||
JSONArray media; |
|||
if ((media = data.optJSONArray("reels_media")) != null && media.length() > 0 && |
|||
(data = media.optJSONObject(0)) != null && |
|||
(media = data.optJSONArray("items")) != null) { |
|||
|
|||
final int mediaLen = media.length(); |
|||
|
|||
final StoryModel[] models = new StoryModel[mediaLen]; |
|||
for (int i = 0; i < mediaLen; ++i) { |
|||
data = media.getJSONObject(i); |
|||
final boolean isVideo = data.getBoolean("is_video"); |
|||
|
|||
final JSONArray tappableObjects = data.optJSONArray("tappable_objects"); |
|||
final int tappableLength = tappableObjects != null ? tappableObjects.length() : 0; |
|||
|
|||
models[i] = new StoryModel(data.getString(Constants.EXTRAS_ID), |
|||
data.getString("display_url"), |
|||
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, |
|||
data.optLong("taken_at_timestamp", 0)); |
|||
|
|||
final JSONArray videoResources = data.optJSONArray("video_resources"); |
|||
if (isVideo && videoResources != null) |
|||
models[i].setVideoUrl(Utils.getHighQualityPost(videoResources, true)); |
|||
|
|||
for (int j = 0; j < tappableLength; ++j) { |
|||
JSONObject tappableObject = tappableObjects.getJSONObject(j); |
|||
if (tappableObject.optString("__typename").equals("GraphTappableFeedMedia")) { |
|||
tappableObject = tappableObject.getJSONObject("media"); |
|||
models[i].setTappableShortCode(tappableObject.getString(Constants.EXTRAS_SHORTCODE)); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
result = models; |
|||
} |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogCollector.LogFile.ASYNC_STORY_STATUS_FETCHER, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final StoryModel[] result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,98 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.io.InterruptedIOException; |
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.SuggestionModel; |
|||
import awais.instagrabber.models.enums.SuggestionType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.UrlEncoder; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class SuggestionsFetcher extends AsyncTask<String, String, SuggestionModel[]> { |
|||
private final FetchListener<SuggestionModel[]> fetchListener; |
|||
|
|||
public SuggestionsFetcher(final FetchListener<SuggestionModel[]> fetchListener) { |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected SuggestionModel[] doInBackground(final String... params) { |
|||
SuggestionModel[] result = null; |
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/web/search/topsearch/?context=blended&count=50&query=" |
|||
+ UrlEncoder.encodeUrl(params[0])).openConnection(); |
|||
conn.setUseCaches(false); |
|||
conn.connect(); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final String defaultHashTagPic = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png"; |
|||
final JSONObject jsonObject = new JSONObject(Utils.readFromConnection(conn)); |
|||
conn.disconnect(); |
|||
|
|||
final JSONArray usersArray = jsonObject.getJSONArray("users"); |
|||
final JSONArray hashtagsArray = jsonObject.getJSONArray("hashtags"); |
|||
|
|||
final int usersLen = usersArray.length(); |
|||
final int hashtagsLen = hashtagsArray.length(); |
|||
|
|||
final ArrayList<SuggestionModel> suggestionModels = new ArrayList<>(usersLen + hashtagsLen); |
|||
for (int i = 0; i < hashtagsLen; i++) { |
|||
final JSONObject hashtagsArrayJSONObject = hashtagsArray.getJSONObject(i); |
|||
|
|||
final JSONObject hashtag = hashtagsArrayJSONObject.getJSONObject("hashtag"); |
|||
|
|||
suggestionModels.add(new SuggestionModel(false, |
|||
hashtag.getString(Constants.EXTRAS_NAME), |
|||
null, |
|||
hashtag.optString("profile_pic_url", defaultHashTagPic), |
|||
SuggestionType.TYPE_HASHTAG, |
|||
hashtagsArrayJSONObject.optInt("position", suggestionModels.size() - 1))); |
|||
} |
|||
|
|||
for (int i = 0; i < usersLen; i++) { |
|||
final JSONObject usersArrayJSONObject = usersArray.getJSONObject(i); |
|||
|
|||
final JSONObject user = usersArrayJSONObject.getJSONObject(Constants.EXTRAS_USER); |
|||
|
|||
suggestionModels.add(new SuggestionModel(user.getBoolean("is_verified"), |
|||
user.getString(Constants.EXTRAS_USERNAME), |
|||
user.getString("full_name"), |
|||
user.getString("profile_pic_url"), |
|||
SuggestionType.TYPE_USER, |
|||
usersArrayJSONObject.optInt("position", suggestionModels.size() - 1))); |
|||
} |
|||
|
|||
suggestionModels.trimToSize(); |
|||
|
|||
Collections.sort(suggestionModels); |
|||
|
|||
result = suggestionModels.toArray(new SuggestionModel[0]); |
|||
} |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG && !(e instanceof InterruptedIOException)) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final SuggestionModel[] result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,54 @@ |
|||
package awais.instagrabber.asyncs; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class UsernameFetcher extends AsyncTask<Void, Void, String> { |
|||
private final FetchListener<String> fetchListener; |
|||
private final String uid; |
|||
|
|||
public UsernameFetcher(final String uid, final FetchListener<String> fetchListener) { |
|||
this.uid = uid; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected String doInBackground(final Void... voids) { |
|||
String result = null; |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL("https://i.instagram.com/api/v1/users/" + uid + "/info/").openConnection(); |
|||
conn.setRequestProperty("User-Agent", Constants.USER_AGENT); |
|||
conn.setUseCaches(true); |
|||
|
|||
final JSONObject user; |
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK && |
|||
(user = new JSONObject(Utils.readFromConnection(conn)).optJSONObject(Constants.EXTRAS_USER)) != null) |
|||
result = user.getString(Constants.EXTRAS_USERNAME); |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final String result) { |
|||
if (fetchListener != null) fetchListener.onResult(result); |
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
package awais.instagrabber.asyncs.direct_messages; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONArray; |
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.direct_messages.InboxModel; |
|||
import awais.instagrabber.models.direct_messages.InboxThreadModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.LocaleUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awaisomereport.LogCollector.LogFile; |
|||
|
|||
public final class InboxFetcher extends AsyncTask<Void, Void, InboxModel> { |
|||
private final String endCursor; |
|||
private final FetchListener<InboxModel> fetchListener; |
|||
|
|||
public InboxFetcher(final String endCursor, final FetchListener<InboxModel> fetchListener) { |
|||
this.endCursor = Utils.isEmpty(endCursor) ? "" : "?cursor=" + endCursor; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected InboxModel doInBackground(final Void... voids) { |
|||
InboxModel result = null; |
|||
|
|||
final String url = "https://i.instagram.com/api/v1/direct_v2/inbox/" + endCursor; |
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setRequestProperty("User-Agent", Constants.USER_AGENT); |
|||
conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8"); |
|||
conn.setUseCaches(false); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
JSONObject data = new JSONObject(Utils.readFromConnection(conn)); |
|||
// try (FileWriter fileWriter = new FileWriter(new File("/sdcard/test.json"))) { |
|||
// fileWriter.write(data.toString(2)); |
|||
// } |
|||
|
|||
final long seqId = data.optLong("seq_id"); |
|||
final int pendingRequestsCount = data.optInt("pending_requests_total"); |
|||
final boolean hasPendingTopRequests = data.optBoolean("has_pending_top_requests"); |
|||
|
|||
data = data.getJSONObject("inbox"); |
|||
|
|||
final boolean blendedInboxEnabled = data.optBoolean("blended_inbox_enabled"); |
|||
final boolean hasOlder = data.optBoolean("has_older"); |
|||
final int unseenCount = data.optInt("unseen_count"); |
|||
final long unseenCountTimestamp = data.optLong("unseen_count_ts"); |
|||
final String oldestCursor = data.optString("oldest_cursor"); |
|||
|
|||
InboxThreadModel[] inboxThreadModels = null; |
|||
|
|||
final JSONArray threadsArray = data.optJSONArray("threads"); |
|||
if (threadsArray != null) { |
|||
final int threadsLen = threadsArray.length(); |
|||
inboxThreadModels = new InboxThreadModel[threadsLen]; |
|||
|
|||
for (int i = 0; i < threadsLen; ++i) |
|||
inboxThreadModels[i] = Utils.createInboxThreadModel(threadsArray.getJSONObject(i), false); |
|||
} |
|||
|
|||
result = new InboxModel(hasOlder, hasPendingTopRequests, |
|||
blendedInboxEnabled, unseenCount, pendingRequestsCount, |
|||
seqId, unseenCountTimestamp, oldestCursor, inboxThreadModels); |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
result = null; |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_DMS, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final InboxModel inboxModel) { |
|||
if (fetchListener != null) fetchListener.onResult(inboxModel); |
|||
} |
|||
} |
@ -0,0 +1,76 @@ |
|||
package awais.instagrabber.asyncs.direct_messages; |
|||
|
|||
import android.os.AsyncTask; |
|||
import android.util.Log; |
|||
|
|||
import androidx.annotation.Nullable; |
|||
|
|||
import org.json.JSONObject; |
|||
|
|||
import java.net.HttpURLConnection; |
|||
import java.net.URL; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.direct_messages.InboxThreadModel; |
|||
import awais.instagrabber.models.enums.UserInboxDirection; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.logCollector; |
|||
import static awaisomereport.LogCollector.LogFile; |
|||
|
|||
public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadModel> { |
|||
private final String id; |
|||
private final String endCursor; |
|||
private final FetchListener<InboxThreadModel> fetchListener; |
|||
private final String direction; |
|||
|
|||
public UserInboxFetcher(final String id, final UserInboxDirection direction, final String endCursor, |
|||
final FetchListener<InboxThreadModel> fetchListener) { |
|||
this.id = id; |
|||
this.direction = "&direction=" + (direction == UserInboxDirection.NEWER ? "newer" : "older"); |
|||
this.endCursor = !Utils.isEmpty(endCursor) ? "&cursor=" + endCursor : ""; |
|||
this.fetchListener = fetchListener; |
|||
} |
|||
|
|||
@Nullable |
|||
@Override |
|||
protected InboxThreadModel doInBackground(final Void... voids) { |
|||
InboxThreadModel result = null; |
|||
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?visual_message_return_type=unseen" |
|||
+ direction + endCursor; |
|||
// todo probably |
|||
// & seq_id = seqId |
|||
|
|||
try { |
|||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |
|||
conn.setRequestProperty("User-Agent", Constants.USER_AGENT); |
|||
conn.setUseCaches(false); |
|||
|
|||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |
|||
final JSONObject data = new JSONObject(Utils.readFromConnection(conn)).getJSONObject("thread"); |
|||
result = Utils.createInboxThreadModel(data, true); |
|||
} |
|||
|
|||
conn.disconnect(); |
|||
} catch (final Exception e) { |
|||
result = null; |
|||
if (logCollector != null) |
|||
logCollector.appendException(e, LogFile.ASYNC_DMS_THREAD, "doInBackground"); |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
@Override |
|||
protected void onPreExecute() { |
|||
if (fetchListener != null) fetchListener.doBefore(); |
|||
} |
|||
|
|||
@Override |
|||
protected void onPostExecute(final InboxThreadModel inboxThreadModel) { |
|||
if (fetchListener != null) fetchListener.onResult(inboxThreadModel); |
|||
} |
|||
} |
@ -0,0 +1,105 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.BitmapShader; |
|||
import android.graphics.Canvas; |
|||
import android.graphics.Color; |
|||
import android.graphics.Outline; |
|||
import android.graphics.Paint; |
|||
import android.graphics.Shader; |
|||
import android.graphics.drawable.BitmapDrawable; |
|||
import android.os.Build; |
|||
import android.util.AttributeSet; |
|||
import android.view.View; |
|||
import android.view.ViewOutlineProvider; |
|||
|
|||
import androidx.appcompat.widget.AppCompatImageView; |
|||
|
|||
public final class CircularImageView extends AppCompatImageView { |
|||
private final int borderSize = 8; |
|||
private int color = Color.TRANSPARENT; |
|||
private final Paint paint = new Paint(); |
|||
private final Paint paintBorder = new Paint(); |
|||
private BitmapShader shader; |
|||
private Bitmap bitmap; |
|||
|
|||
public CircularImageView(final Context context) { |
|||
super(context); |
|||
setup(); |
|||
} |
|||
|
|||
public CircularImageView(final Context context, final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
setup(); |
|||
} |
|||
|
|||
public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
setup(); |
|||
} |
|||
|
|||
private void setup() { |
|||
paint.setAntiAlias(true); |
|||
paintBorder.setColor(color); |
|||
paintBorder.setAntiAlias(true); |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
setOutlineProvider(new ViewOutlineProvider() { |
|||
private int viewHeight; |
|||
private int viewWidth; |
|||
|
|||
@Override |
|||
public void getOutline(final View view, final Outline outline) { |
|||
if (viewHeight == 0) viewHeight = getHeight(); |
|||
if (viewWidth == 0) viewWidth = getWidth(); |
|||
outline.setRoundRect(borderSize, borderSize, viewWidth - borderSize, viewHeight - borderSize, viewHeight >> 1); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onDraw(final Canvas canvas) { |
|||
final BitmapDrawable bitmapDrawable = (BitmapDrawable) getDrawable(); |
|||
if (bitmapDrawable != null) { |
|||
final Bitmap prevBitmap = bitmap; |
|||
bitmap = bitmapDrawable.getBitmap(); |
|||
final boolean changed = prevBitmap != bitmap; |
|||
if (bitmap != null) { |
|||
final int width = getWidth(); |
|||
final int height = getHeight(); |
|||
|
|||
if (shader == null || changed) { |
|||
shader = new BitmapShader(Bitmap.createScaledBitmap(bitmap, width, height, true), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); |
|||
paint.setShader(shader); |
|||
} |
|||
|
|||
if (changed) color = 0; |
|||
paintBorder.setColor(color); |
|||
|
|||
final int circleCenter = (width - borderSize) / 2; |
|||
final int position = circleCenter + (borderSize / 2); |
|||
canvas.drawCircle(position, position, position - 4.0f, paintBorder); |
|||
canvas.drawCircle(position, position, circleCenter - 4.0f, paint); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onAttachedToWindow() { |
|||
super.onAttachedToWindow(); |
|||
setLayerType(LAYER_TYPE_HARDWARE, null); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDetachedFromWindow() { |
|||
setLayerType(LAYER_TYPE_NONE, null); |
|||
super.onDetachedFromWindow(); |
|||
} |
|||
|
|||
public void setStoriesBorder() { |
|||
this.color = Color.GREEN; |
|||
invalidate(); |
|||
} |
|||
} |
@ -0,0 +1,17 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.text.TextPaint; |
|||
import android.text.style.ClickableSpan; |
|||
import android.view.View; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
public final class CommentMentionClickSpan extends ClickableSpan { |
|||
@Override |
|||
public void onClick(@NonNull final View widget) { } |
|||
|
|||
@Override |
|||
public void updateDrawState(@NonNull final TextPaint ds) { |
|||
ds.setColor(ds.linkColor); |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.content.Context; |
|||
import android.util.AttributeSet; |
|||
|
|||
import androidx.appcompat.widget.AppCompatImageView; |
|||
|
|||
public final class FixedImageView extends AppCompatImageView { |
|||
public FixedImageView(final Context context) { |
|||
super(context); |
|||
} |
|||
|
|||
public FixedImageView(final Context context, final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
} |
|||
|
|||
public FixedImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
} |
|||
|
|||
@Override |
|||
protected void onMeasure(final int wMeasure, final int hMeasure) { |
|||
super.onMeasure(wMeasure, wMeasure); |
|||
} |
|||
} |
@ -0,0 +1,986 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Context; |
|||
import android.content.res.TypedArray; |
|||
import android.graphics.Canvas; |
|||
import android.graphics.Matrix; |
|||
import android.graphics.PixelFormat; |
|||
import android.graphics.Rect; |
|||
import android.graphics.drawable.Drawable; |
|||
import android.os.Build; |
|||
import android.os.Parcel; |
|||
import android.os.Parcelable; |
|||
import android.util.AttributeSet; |
|||
import android.util.Log; |
|||
import android.view.Gravity; |
|||
import android.view.InputDevice; |
|||
import android.view.KeyEvent; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
|
|||
import androidx.annotation.IntDef; |
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.view.GravityCompat; |
|||
import androidx.core.view.ViewCompat; |
|||
import androidx.customview.view.AbsSavedState; |
|||
import androidx.customview.widget.ViewDragHelper; |
|||
|
|||
import java.lang.annotation.Retention; |
|||
import java.lang.annotation.RetentionPolicy; |
|||
import java.lang.reflect.Field; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.BuildConfig; |
|||
|
|||
// exactly same as the LayoutDrawer with some edits |
|||
@SuppressLint("RtlHardcoded") |
|||
public class MouseDrawer extends ViewGroup { |
|||
@IntDef({ViewDragHelper.STATE_IDLE, ViewDragHelper.STATE_DRAGGING, ViewDragHelper.STATE_SETTLING}) |
|||
@Retention(RetentionPolicy.SOURCE) |
|||
private @interface State {} |
|||
|
|||
@IntDef(value = {Gravity.NO_GRAVITY, Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}, flag = true) |
|||
@Retention(RetentionPolicy.SOURCE) |
|||
public @interface EdgeGravity {} |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////// |
|||
private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; |
|||
//////////////////////////////////////////////////////////////////////////////////// |
|||
private final ArrayList<View> mNonDrawerViews = new ArrayList<>(); |
|||
private final ViewDragHelper mLeftDragger, mRightDragger; |
|||
private boolean mInLayout, mFirstLayout = true; |
|||
private float mDrawerElevation, mInitialMotionX, mInitialMotionY; |
|||
private int mDrawerState; |
|||
private List<DrawerListener> mListeners; |
|||
private Matrix mChildInvertedMatrix; |
|||
private Rect mChildHitRect; |
|||
|
|||
public interface DrawerListener { |
|||
void onDrawerSlide(final View drawerView, @EdgeGravity final int gravity, final float slideOffset); |
|||
default void onDrawerOpened(final View drawerView, @EdgeGravity final int gravity) {} |
|||
default void onDrawerClosed(final View drawerView, @EdgeGravity final int gravity) {} |
|||
default void onDrawerStateChanged() {} |
|||
} |
|||
|
|||
public MouseDrawer(@NonNull final Context context) { |
|||
this(context, null); |
|||
} |
|||
|
|||
public MouseDrawer(@NonNull final Context context, @Nullable final AttributeSet attrs) { |
|||
this(context, attrs, 0); |
|||
} |
|||
|
|||
public MouseDrawer(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { |
|||
super(context, attrs, defStyle); |
|||
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); |
|||
|
|||
final float density = getResources().getDisplayMetrics().density; |
|||
this.mDrawerElevation = 10 * density; |
|||
|
|||
final float touchSlopSensitivity = 0.5f; // was 1.0f |
|||
final float minFlingVelocity = 400 /* dips per second */ * density; |
|||
|
|||
final ViewDragCallback mLeftCallback = new ViewDragCallback(Gravity.LEFT); |
|||
this.mLeftDragger = ViewDragHelper.create(this, touchSlopSensitivity, mLeftCallback); |
|||
this.mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); |
|||
this.mLeftDragger.setMinVelocity(minFlingVelocity); |
|||
|
|||
final ViewDragCallback mRightCallback = new ViewDragCallback(Gravity.RIGHT); |
|||
this.mRightDragger = ViewDragHelper.create(this, touchSlopSensitivity, mRightCallback); |
|||
this.mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); |
|||
this.mRightDragger.setMinVelocity(minFlingVelocity); |
|||
|
|||
try { |
|||
final Field edgeSizeField = ViewDragHelper.class.getDeclaredField("mEdgeSize"); |
|||
if (!edgeSizeField.isAccessible()) edgeSizeField.setAccessible(true); |
|||
final int widthPixels = getResources().getDisplayMetrics().widthPixels; // whole screen |
|||
edgeSizeField.set(this.mLeftDragger, widthPixels / 2); |
|||
edgeSizeField.set(this.mRightDragger, widthPixels / 2); |
|||
} catch (final Exception e) { |
|||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
|
|||
mLeftCallback.setDragger(mLeftDragger); |
|||
mRightCallback.setDragger(mRightDragger); |
|||
|
|||
setFocusableInTouchMode(true); |
|||
//setMotionEventSplittingEnabled(false); |
|||
} |
|||
|
|||
public void setDrawerElevation(final float elevation) { |
|||
mDrawerElevation = elevation; |
|||
for (int i = 0; i < getChildCount(); i++) { |
|||
final View child = getChildAt(i); |
|||
if (isDrawerView(child)) ViewCompat.setElevation(child, mDrawerElevation); |
|||
} |
|||
} |
|||
|
|||
public float getDrawerElevation() { |
|||
return Build.VERSION.SDK_INT >= 21 ? mDrawerElevation : 0f; |
|||
} |
|||
|
|||
public void addDrawerListener(@NonNull final DrawerListener listener) { |
|||
if (mListeners == null) mListeners = new ArrayList<>(); |
|||
mListeners.add(listener); |
|||
} |
|||
|
|||
private boolean isInBoundsOfChild(final float x, final float y, final View child) { |
|||
if (mChildHitRect == null) mChildHitRect = new Rect(); |
|||
child.getHitRect(mChildHitRect); |
|||
return mChildHitRect.contains((int) x, (int) y); |
|||
} |
|||
|
|||
private boolean dispatchTransformedGenericPointerEvent(final MotionEvent event, @NonNull final View child) { |
|||
final boolean handled; |
|||
final Matrix childMatrix = child.getMatrix(); |
|||
if (!childMatrix.isIdentity()) { |
|||
final MotionEvent transformedEvent = getTransformedMotionEvent(event, child); |
|||
handled = child.dispatchGenericMotionEvent(transformedEvent); |
|||
transformedEvent.recycle(); |
|||
} else { |
|||
final float offsetX = getScrollX() - child.getLeft(); |
|||
final float offsetY = getScrollY() - child.getTop(); |
|||
event.offsetLocation(offsetX, offsetY); |
|||
handled = child.dispatchGenericMotionEvent(event); |
|||
event.offsetLocation(-offsetX, -offsetY); |
|||
} |
|||
return handled; |
|||
} |
|||
|
|||
@NonNull |
|||
private MotionEvent getTransformedMotionEvent(final MotionEvent event, @NonNull final View child) { |
|||
final float offsetX = getScrollX() - child.getLeft(); |
|||
final float offsetY = getScrollY() - child.getTop(); |
|||
final MotionEvent transformedEvent = MotionEvent.obtain(event); |
|||
transformedEvent.offsetLocation(offsetX, offsetY); |
|||
final Matrix childMatrix = child.getMatrix(); |
|||
if (!childMatrix.isIdentity()) { |
|||
if (mChildInvertedMatrix == null) mChildInvertedMatrix = new Matrix(); |
|||
childMatrix.invert(mChildInvertedMatrix); |
|||
transformedEvent.transform(mChildInvertedMatrix); |
|||
} |
|||
return transformedEvent; |
|||
} |
|||
|
|||
void updateDrawerState(@State final int activeState, final View activeDrawer) { |
|||
final int leftState = mLeftDragger.getViewDragState(); |
|||
final int rightState = mRightDragger.getViewDragState(); |
|||
|
|||
final int state; |
|||
if (leftState == ViewDragHelper.STATE_DRAGGING || rightState == ViewDragHelper.STATE_DRAGGING) |
|||
state = ViewDragHelper.STATE_DRAGGING; |
|||
else if (leftState == ViewDragHelper.STATE_SETTLING || rightState == ViewDragHelper.STATE_SETTLING) |
|||
state = ViewDragHelper.STATE_SETTLING; |
|||
else state = ViewDragHelper.STATE_IDLE; |
|||
|
|||
if (activeDrawer != null && activeState == ViewDragHelper.STATE_IDLE) { |
|||
final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); |
|||
if (lp.onScreen == 0) dispatchOnDrawerClosed(activeDrawer); |
|||
else if (lp.onScreen == 1) dispatchOnDrawerOpened(activeDrawer); |
|||
} |
|||
|
|||
if (state != mDrawerState) { |
|||
mDrawerState = state; |
|||
|
|||
if (mListeners != null) { |
|||
final int listenerCount = mListeners.size(); |
|||
for (int i = listenerCount - 1; i >= 0; i--) mListeners.get(i).onDrawerStateChanged(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void dispatchOnDrawerClosed(@NonNull final View drawerView) { |
|||
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); |
|||
if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) { |
|||
lp.openState = 0; |
|||
|
|||
if (mListeners != null) { |
|||
final int listenerCount = mListeners.size(); |
|||
for (int i = listenerCount - 1; i >= 0; i--) mListeners.get(i).onDrawerClosed(drawerView, lp.gravity); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void dispatchOnDrawerOpened(@NonNull final View drawerView) { |
|||
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); |
|||
if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) { |
|||
lp.openState = LayoutParams.FLAG_IS_OPENED; |
|||
if (mListeners != null) { |
|||
final int listenerCount = mListeners.size(); |
|||
for (int i = listenerCount - 1; i >= 0; i--) mListeners.get(i).onDrawerOpened(drawerView, lp.gravity); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void setDrawerViewOffset(@NonNull final View drawerView, final float slideOffset) { |
|||
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); |
|||
if (slideOffset != lp.onScreen) { |
|||
lp.onScreen = slideOffset; |
|||
|
|||
if (mListeners != null) { |
|||
final int listenerCount = mListeners.size(); |
|||
for (int i = listenerCount - 1; i >= 0; i--) |
|||
mListeners.get(i).onDrawerSlide(drawerView, lp.gravity, slideOffset); |
|||
} |
|||
} |
|||
} |
|||
|
|||
float getDrawerViewOffset(@NonNull final View drawerView) { |
|||
return ((LayoutParams) drawerView.getLayoutParams()).onScreen; |
|||
} |
|||
|
|||
int getDrawerViewAbsoluteGravity(@NonNull final View drawerView) { |
|||
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; |
|||
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); |
|||
} |
|||
|
|||
boolean checkDrawerViewAbsoluteGravity(final View drawerView, final int checkFor) { |
|||
final int absGravity = getDrawerViewAbsoluteGravity(drawerView); |
|||
return (absGravity & checkFor) == checkFor; |
|||
} |
|||
|
|||
void moveDrawerToOffset(final View drawerView, final float slideOffset) { |
|||
final float oldOffset = getDrawerViewOffset(drawerView); |
|||
final int width = drawerView.getWidth(); |
|||
final int oldPos = (int) (width * oldOffset); |
|||
final int newPos = (int) (width * slideOffset); |
|||
final int dx = newPos - oldPos; |
|||
|
|||
drawerView.offsetLeftAndRight(checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx); |
|||
setDrawerViewOffset(drawerView, slideOffset); |
|||
} |
|||
|
|||
public View findOpenDrawer() { |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
final LayoutParams childLp = (LayoutParams) child.getLayoutParams(); |
|||
if ((childLp.openState & LayoutParams.FLAG_IS_OPENED) == 1) return child; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
public View findDrawerWithGravity(final int gravity) { |
|||
final int absHorizGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK; |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
final int childAbsGravity = getDrawerViewAbsoluteGravity(child); |
|||
if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) return child; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@NonNull |
|||
static String gravityToString(@EdgeGravity final int gravity) { |
|||
if ((gravity & Gravity.LEFT) == Gravity.LEFT) return "LEFT"; |
|||
if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) return "RIGHT"; |
|||
return Integer.toHexString(gravity); |
|||
} |
|||
|
|||
@Override |
|||
protected void onDetachedFromWindow() { |
|||
super.onDetachedFromWindow(); |
|||
mFirstLayout = true; |
|||
} |
|||
|
|||
@Override |
|||
protected void onAttachedToWindow() { |
|||
super.onAttachedToWindow(); |
|||
mFirstLayout = true; |
|||
} |
|||
|
|||
@SuppressLint("WrongConstant") |
|||
@Override |
|||
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) { |
|||
final int widthSize = MeasureSpec.getSize(widthMeasureSpec); |
|||
final int heightSize = MeasureSpec.getSize(heightMeasureSpec); |
|||
|
|||
setMeasuredDimension(widthSize, heightSize); |
|||
|
|||
boolean hasDrawerOnLeftEdge = false; |
|||
boolean hasDrawerOnRightEdge = false; |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
|
|||
if (child.getVisibility() != GONE) { |
|||
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
|||
|
|||
if (isContentView(child)) { |
|||
// Content views get measured at exactly the layout's size. |
|||
final int contentWidthSpec = MeasureSpec.makeMeasureSpec(widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); |
|||
final int contentHeightSpec = MeasureSpec.makeMeasureSpec(heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); |
|||
child.measure(contentWidthSpec, contentHeightSpec); |
|||
|
|||
} else if (isDrawerView(child)) { |
|||
if (Build.VERSION.SDK_INT >= 21 && ViewCompat.getElevation(child) != mDrawerElevation) |
|||
ViewCompat.setElevation(child, mDrawerElevation); |
|||
final int childGravity = getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; |
|||
|
|||
final boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT); |
|||
if (isLeftEdgeDrawer && hasDrawerOnLeftEdge || !isLeftEdgeDrawer && hasDrawerOnRightEdge) |
|||
throw new IllegalStateException("Child drawer has absolute gravity " + gravityToString(childGravity) |
|||
+ " but this MouseDrawer already has a drawer view along that edge"); |
|||
|
|||
if (isLeftEdgeDrawer) hasDrawerOnLeftEdge = true; |
|||
else hasDrawerOnRightEdge = true; |
|||
|
|||
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, lp.leftMargin + lp.rightMargin, lp.width); |
|||
final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, lp.topMargin + lp.bottomMargin, lp.height); |
|||
child.measure(drawerWidthSpec, drawerHeightSpec); |
|||
} else |
|||
throw new IllegalStateException("Child " + child + " at index " + i |
|||
+ " does not have a valid layout_gravity - must be Gravity.LEFT, Gravity.RIGHT or Gravity.NO_GRAVITY"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
protected void onLayout(final boolean changed, final int left, final int top, final int right, final int bottom) { |
|||
mInLayout = true; |
|||
final int width = right - left; |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
|
|||
if (child.getVisibility() != GONE) { |
|||
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
|||
|
|||
if (isContentView(child)) { |
|||
child.layout(lp.leftMargin, lp.topMargin, lp.leftMargin + child.getMeasuredWidth(), |
|||
lp.topMargin + child.getMeasuredHeight()); |
|||
|
|||
} else { // Drawer, if it wasn't onMeasure would have thrown an exception. |
|||
final int childWidth = child.getMeasuredWidth(); |
|||
final int childHeight = child.getMeasuredHeight(); |
|||
final int childLeft; |
|||
final float newOffset; |
|||
|
|||
if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { |
|||
childLeft = -childWidth + (int) (childWidth * lp.onScreen); |
|||
newOffset = (float) (childWidth + childLeft) / childWidth; |
|||
} else { // Right; onMeasure checked for us. |
|||
childLeft = width - (int) (childWidth * lp.onScreen); |
|||
newOffset = (float) (width - childLeft) / childWidth; |
|||
} |
|||
|
|||
final boolean changeOffset = newOffset != lp.onScreen; |
|||
|
|||
final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; |
|||
switch (vgrav) { |
|||
default: |
|||
case Gravity.TOP: |
|||
child.layout(childLeft, lp.topMargin, childLeft + childWidth, lp.topMargin + childHeight); |
|||
break; |
|||
|
|||
case Gravity.BOTTOM: { |
|||
final int height = bottom - top; |
|||
child.layout(childLeft, height - lp.bottomMargin - child.getMeasuredHeight(), |
|||
childLeft + childWidth, height - lp.bottomMargin); |
|||
break; |
|||
} |
|||
|
|||
case Gravity.CENTER_VERTICAL: { |
|||
final int height = bottom - top; |
|||
int childTop = (height - childHeight) / 2; |
|||
|
|||
if (childTop < lp.topMargin) childTop = lp.topMargin; |
|||
else if (childTop + childHeight > height - lp.bottomMargin) |
|||
childTop = height - lp.bottomMargin - childHeight; |
|||
|
|||
child.layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (changeOffset) setDrawerViewOffset(child, newOffset); |
|||
|
|||
final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE; |
|||
if (child.getVisibility() != newVisibility) child.setVisibility(newVisibility); |
|||
} |
|||
} |
|||
} |
|||
mInLayout = false; |
|||
mFirstLayout = false; |
|||
} |
|||
|
|||
@Override |
|||
public void requestLayout() { |
|||
if (!mInLayout) super.requestLayout(); |
|||
} |
|||
|
|||
@Override |
|||
public void computeScroll() { |
|||
final boolean leftDraggerSettling = mLeftDragger.continueSettling(true); |
|||
final boolean rightDraggerSettling = mRightDragger.continueSettling(true); |
|||
if (leftDraggerSettling || rightDraggerSettling) postInvalidateOnAnimation(); |
|||
} |
|||
|
|||
private static boolean hasOpaqueBackground(@NonNull final View v) { |
|||
final Drawable bg = v.getBackground(); |
|||
if (bg != null) return bg.getOpacity() == PixelFormat.OPAQUE; |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
protected boolean drawChild(@NonNull final Canvas canvas, final View child, final long drawingTime) { |
|||
final int height = getHeight(); |
|||
final boolean drawingContent = isContentView(child); |
|||
int clipLeft = 0, clipRight = getWidth(); |
|||
|
|||
final int restoreCount = canvas.save(); |
|||
if (drawingContent) { |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View v = getChildAt(i); |
|||
if (v != child && v.getVisibility() == VISIBLE && hasOpaqueBackground(v) && isDrawerView(v) && v.getHeight() >= height) { |
|||
if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { |
|||
final int vright = v.getRight(); |
|||
if (vright > clipLeft) clipLeft = vright; |
|||
} else { |
|||
final int vleft = v.getLeft(); |
|||
if (vleft < clipRight) clipRight = vleft; |
|||
} |
|||
} |
|||
} |
|||
canvas.clipRect(clipLeft, 0, clipRight, getHeight()); |
|||
} |
|||
|
|||
final boolean result = super.drawChild(canvas, child, drawingTime); |
|||
canvas.restoreToCount(restoreCount); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
boolean isContentView(@NonNull final View child) { |
|||
return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; |
|||
} |
|||
|
|||
boolean isDrawerView(@NonNull final View child) { |
|||
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; |
|||
final int absGravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(child)); |
|||
return (absGravity & Gravity.LEFT) != 0 || (absGravity & Gravity.RIGHT) != 0; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onInterceptTouchEvent(@NonNull final MotionEvent ev) { |
|||
final int action = ev.getActionMasked(); |
|||
|
|||
// "|" used deliberately here; both methods should be invoked. |
|||
final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | mRightDragger.shouldInterceptTouchEvent(ev); |
|||
|
|||
switch (action) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
mInitialMotionX = ev.getX(); |
|||
mInitialMotionY = ev.getY(); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_MOVE: |
|||
mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_CANCEL: |
|||
case MotionEvent.ACTION_UP: |
|||
closeDrawers(true); |
|||
} |
|||
|
|||
return interceptForDrag || hasPeekingDrawer(); |
|||
} |
|||
|
|||
@Override |
|||
public boolean dispatchGenericMotionEvent(@NonNull final MotionEvent event) { |
|||
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0 || event.getAction() == MotionEvent.ACTION_HOVER_EXIT) |
|||
return super.dispatchGenericMotionEvent(event); |
|||
|
|||
final int childrenCount = getChildCount(); |
|||
if (childrenCount != 0) { |
|||
final float x = event.getX(); |
|||
final float y = event.getY(); |
|||
|
|||
// Walk through children from top to bottom. |
|||
for (int i = childrenCount - 1; i >= 0; i--) { |
|||
final View child = getChildAt(i); |
|||
if (isInBoundsOfChild(x, y, child) && !isContentView(child) && dispatchTransformedGenericPointerEvent(event, child)) |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
@Override |
|||
public boolean onTouchEvent(final MotionEvent ev) { |
|||
mLeftDragger.processTouchEvent(ev); |
|||
mRightDragger.processTouchEvent(ev); |
|||
|
|||
final int action = ev.getActionMasked(); |
|||
switch (action) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
mInitialMotionX = ev.getX(); |
|||
mInitialMotionY = ev.getY(); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_UP: |
|||
final float x = ev.getX(); |
|||
final float y = ev.getY(); |
|||
|
|||
boolean peekingOnly = true; |
|||
final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); |
|||
if (touchedView != null && isContentView(touchedView)) { |
|||
final float dx = x - mInitialMotionX; |
|||
final float dy = y - mInitialMotionY; |
|||
final int slop = mLeftDragger.getTouchSlop(); |
|||
if (dx * dx + dy * dy < slop * slop) { |
|||
// Taps close a dimmed open drawer but only if it isn't locked open. |
|||
final View openDrawer = findOpenDrawer(); |
|||
if (openDrawer != null) peekingOnly = false; |
|||
} |
|||
} |
|||
closeDrawers(peekingOnly); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_CANCEL: |
|||
closeDrawers(true); |
|||
break; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public void requestDisallowInterceptTouchEvent(final boolean disallowIntercept) { |
|||
if (CHILDREN_DISALLOW_INTERCEPT || (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) |
|||
super.requestDisallowInterceptTouchEvent(disallowIntercept); |
|||
if (disallowIntercept) closeDrawers(true); |
|||
} |
|||
|
|||
public void closeDrawers() { |
|||
closeDrawers(false); |
|||
} |
|||
|
|||
void closeDrawers(final boolean peekingOnly) { |
|||
boolean needsInvalidate = false; |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
|||
|
|||
if (isDrawerView(child) && (!peekingOnly || lp.isPeeking)) { |
|||
final int childWidth = child.getWidth(); |
|||
|
|||
if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) |
|||
needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, -childWidth, child.getTop()); |
|||
else |
|||
needsInvalidate |= mRightDragger.smoothSlideViewTo(child, getWidth(), child.getTop()); |
|||
|
|||
lp.isPeeking = false; |
|||
} |
|||
} |
|||
|
|||
if (needsInvalidate) invalidate(); |
|||
} |
|||
|
|||
public void openDrawer(@NonNull final View drawerView, final boolean animate) { |
|||
if (isDrawerView(drawerView)) { |
|||
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); |
|||
|
|||
if (mFirstLayout) { |
|||
lp.onScreen = 1.f; |
|||
lp.openState = LayoutParams.FLAG_IS_OPENED; |
|||
} else if (animate) { |
|||
lp.openState |= LayoutParams.FLAG_IS_OPENING; |
|||
|
|||
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) |
|||
mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); |
|||
else |
|||
mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), drawerView.getTop()); |
|||
} else { |
|||
moveDrawerToOffset(drawerView, 1.f); |
|||
updateDrawerState(ViewDragHelper.STATE_IDLE, drawerView); |
|||
drawerView.setVisibility(VISIBLE); |
|||
} |
|||
|
|||
invalidate(); |
|||
return; |
|||
} |
|||
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); |
|||
} |
|||
|
|||
public void openDrawer(@NonNull final View drawerView) { |
|||
openDrawer(drawerView, true); |
|||
} |
|||
|
|||
// public void openDrawer(@EdgeGravity final int gravity, final boolean animate) { |
|||
// final View drawerView = findDrawerWithGravity(gravity); |
|||
// if (drawerView != null) openDrawer(drawerView, animate); |
|||
// else throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)); |
|||
// } |
|||
|
|||
// public void openDrawer(@EdgeGravity final int gravity) { |
|||
// openDrawer(gravity, true); |
|||
// } |
|||
|
|||
public void closeDrawer(@NonNull final View drawerView) { |
|||
closeDrawer(drawerView, true); |
|||
} |
|||
|
|||
public void closeDrawer(@NonNull final View drawerView, final boolean animate) { |
|||
if (isDrawerView(drawerView)) { |
|||
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); |
|||
if (mFirstLayout) { |
|||
lp.onScreen = 0.f; |
|||
lp.openState = 0; |
|||
} else if (animate) { |
|||
lp.openState |= LayoutParams.FLAG_IS_CLOSING; |
|||
|
|||
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) |
|||
mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), drawerView.getTop()); |
|||
else |
|||
mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); |
|||
} else { |
|||
moveDrawerToOffset(drawerView, 0.f); |
|||
updateDrawerState(ViewDragHelper.STATE_IDLE, drawerView); |
|||
drawerView.setVisibility(INVISIBLE); |
|||
} |
|||
invalidate(); |
|||
} else throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); |
|||
} |
|||
|
|||
// public void closeDrawer(@EdgeGravity final int gravity) { |
|||
// closeDrawer(gravity, true); |
|||
// } |
|||
|
|||
// public void closeDrawer(@EdgeGravity final int gravity, final boolean animate) { |
|||
// final View drawerView = findDrawerWithGravity(gravity); |
|||
// if (drawerView != null) closeDrawer(drawerView, animate); |
|||
// else throw new IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)); |
|||
// } |
|||
|
|||
public boolean isDrawerOpen(@NonNull final View drawer) { |
|||
if (isDrawerView(drawer)) return (((LayoutParams) drawer.getLayoutParams()).openState & LayoutParams.FLAG_IS_OPENED) == 1; |
|||
else throw new IllegalArgumentException("View " + drawer + " is not a drawer"); |
|||
} |
|||
|
|||
// public boolean isDrawerOpen(@EdgeGravity final int drawerGravity) { |
|||
// final View drawerView = findDrawerWithGravity(drawerGravity); |
|||
// return drawerView != null && isDrawerOpen(drawerView); |
|||
// } |
|||
|
|||
public boolean isDrawerVisible(@NonNull final View drawer) { |
|||
if (isDrawerView(drawer)) return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; |
|||
throw new IllegalArgumentException("View " + drawer + " is not a drawer"); |
|||
} |
|||
|
|||
// public boolean isDrawerVisible(@EdgeGravity final int drawerGravity) { |
|||
// final View drawerView = findDrawerWithGravity(drawerGravity); |
|||
// return drawerView != null && isDrawerVisible(drawerView); |
|||
// } |
|||
|
|||
private boolean hasPeekingDrawer() { |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); |
|||
if (lp.isPeeking) return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
@Override |
|||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() { |
|||
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); |
|||
} |
|||
|
|||
@Override |
|||
protected ViewGroup.LayoutParams generateLayoutParams(final ViewGroup.LayoutParams params) { |
|||
return params instanceof LayoutParams ? new LayoutParams((LayoutParams) params) : |
|||
params instanceof ViewGroup.MarginLayoutParams ? new LayoutParams((MarginLayoutParams) params) : new LayoutParams(params); |
|||
} |
|||
|
|||
@Override |
|||
protected boolean checkLayoutParams(final ViewGroup.LayoutParams params) { |
|||
return params instanceof LayoutParams && super.checkLayoutParams(params); |
|||
} |
|||
|
|||
@Override |
|||
public ViewGroup.LayoutParams generateLayoutParams(final AttributeSet attrs) { |
|||
return new LayoutParams(getContext(), attrs); |
|||
} |
|||
|
|||
@Override |
|||
public void addFocusables(final ArrayList<View> views, final int direction, final int focusableMode) { |
|||
if (getDescendantFocusability() != FOCUS_BLOCK_DESCENDANTS) { |
|||
final int childCount = getChildCount(); |
|||
boolean isDrawerOpen = false; |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
if (!isDrawerView(child)) mNonDrawerViews.add(child); |
|||
else if (isDrawerOpen(child)) { |
|||
isDrawerOpen = true; |
|||
child.addFocusables(views, direction, focusableMode); |
|||
} |
|||
} |
|||
|
|||
if (!isDrawerOpen) { |
|||
final int nonDrawerViewsCount = mNonDrawerViews.size(); |
|||
for (int i = 0; i < nonDrawerViewsCount; ++i) { |
|||
final View child = mNonDrawerViews.get(i); |
|||
if (child.getVisibility() == View.VISIBLE) child.addFocusables(views, direction, focusableMode); |
|||
} |
|||
} |
|||
|
|||
mNonDrawerViews.clear(); |
|||
} |
|||
} |
|||
|
|||
private boolean hasVisibleDrawer() { |
|||
return findVisibleDrawer() != null; |
|||
} |
|||
|
|||
@Nullable |
|||
final View findVisibleDrawer() { |
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
if (isDrawerView(child) && isDrawerVisible(child)) return child; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onKeyDown(final int keyCode, final KeyEvent event) { |
|||
if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { |
|||
event.startTracking(); |
|||
return true; |
|||
} |
|||
return super.onKeyDown(keyCode, event); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onKeyUp(final int keyCode, final KeyEvent event) { |
|||
if (keyCode == KeyEvent.KEYCODE_BACK) { |
|||
final View visibleDrawer = findVisibleDrawer(); |
|||
if (visibleDrawer != null && isDrawerView(visibleDrawer)) closeDrawers(); |
|||
return visibleDrawer != null; |
|||
} |
|||
return super.onKeyUp(keyCode, event); |
|||
} |
|||
|
|||
@Override |
|||
protected void onRestoreInstanceState(final Parcelable state) { |
|||
if (state instanceof SavedState) { |
|||
final SavedState ss = (SavedState) state; |
|||
super.onRestoreInstanceState(ss.getSuperState()); |
|||
|
|||
if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { |
|||
final View toOpen = findDrawerWithGravity(ss.openDrawerGravity); |
|||
if (toOpen != null) openDrawer(toOpen); |
|||
} |
|||
} else super.onRestoreInstanceState(state); |
|||
} |
|||
|
|||
@Override |
|||
protected Parcelable onSaveInstanceState() { |
|||
final Parcelable superState = super.onSaveInstanceState(); |
|||
assert superState != null; |
|||
final SavedState ss = new SavedState(superState); |
|||
|
|||
final int childCount = getChildCount(); |
|||
for (int i = 0; i < childCount; i++) { |
|||
final View child = getChildAt(i); |
|||
final LayoutParams lp = (LayoutParams) child.getLayoutParams(); |
|||
// Is the current child fully opened (that is, not closing)? |
|||
final boolean isOpenedAndNotClosing = (lp.openState == LayoutParams.FLAG_IS_OPENED); |
|||
// Is the current child opening? |
|||
final boolean isClosedAndOpening = (lp.openState == LayoutParams.FLAG_IS_OPENING); |
|||
if (isOpenedAndNotClosing || isClosedAndOpening) { |
|||
// If one of the conditions above holds, save the child's gravity so that we open that child during state restore. |
|||
ss.openDrawerGravity = lp.gravity; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return ss; |
|||
} |
|||
|
|||
@Override |
|||
public void addView(final View child, final int index, final ViewGroup.LayoutParams params) { |
|||
super.addView(child, index, params); |
|||
final View openDrawer = findOpenDrawer(); |
|||
if (openDrawer == null) isDrawerView(child); |
|||
} |
|||
|
|||
protected static class SavedState extends AbsSavedState { |
|||
public static final Creator<SavedState> CREATOR = new ClassLoaderCreator<SavedState>() { |
|||
@NonNull |
|||
@Override |
|||
public SavedState createFromParcel(final Parcel in, final ClassLoader loader) { |
|||
return new SavedState(in, loader); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public SavedState createFromParcel(final Parcel in) { |
|||
return new SavedState(in, null); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public SavedState[] newArray(final int size) { |
|||
return new SavedState[size]; |
|||
} |
|||
}; |
|||
int openDrawerGravity = Gravity.NO_GRAVITY; |
|||
|
|||
public SavedState(@NonNull final Parcelable superState) { |
|||
super(superState); |
|||
} |
|||
|
|||
public SavedState(@NonNull final Parcel in, @Nullable final ClassLoader loader) { |
|||
super(in, loader); |
|||
openDrawerGravity = in.readInt(); |
|||
} |
|||
|
|||
@Override |
|||
public void writeToParcel(final Parcel dest, final int flags) { |
|||
super.writeToParcel(dest, flags); |
|||
dest.writeInt(openDrawerGravity); |
|||
} |
|||
} |
|||
|
|||
private class ViewDragCallback extends ViewDragHelper.Callback { |
|||
private final int mAbsGravity; |
|||
private ViewDragHelper mDragger; |
|||
|
|||
ViewDragCallback(final int gravity) { |
|||
mAbsGravity = gravity; |
|||
} |
|||
|
|||
public void setDragger(final ViewDragHelper dragger) { |
|||
mDragger = dragger; |
|||
} |
|||
|
|||
@Override |
|||
public boolean tryCaptureView(@NonNull final View child, final int pointerId) { |
|||
return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewDragStateChanged(final int state) { |
|||
updateDrawerState(state, mDragger.getCapturedView()); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewPositionChanged(@NonNull final View changedView, final int left, final int top, final int dx, final int dy) { |
|||
final float offset; |
|||
final int childWidth = changedView.getWidth(); |
|||
|
|||
if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) offset = (float) (childWidth + left) / childWidth; |
|||
else offset = (float) (getWidth() - left) / childWidth; |
|||
|
|||
setDrawerViewOffset(changedView, offset); |
|||
changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); |
|||
invalidate(); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewCaptured(@NonNull final View capturedChild, final int activePointerId) { |
|||
final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); |
|||
lp.isPeeking = false; |
|||
closeOtherDrawer(); |
|||
} |
|||
|
|||
private void closeOtherDrawer() { |
|||
final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; |
|||
final View toClose = findDrawerWithGravity(otherGrav); |
|||
if (toClose != null) closeDrawer(toClose); |
|||
} |
|||
|
|||
@Override |
|||
public void onViewReleased(@NonNull final View releasedChild, final float xvel, final float yvel) { |
|||
final float offset = getDrawerViewOffset(releasedChild); |
|||
final int childWidth = releasedChild.getWidth(); |
|||
|
|||
final int left; |
|||
if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) |
|||
left = xvel > 0 || (xvel == 0 && offset > 0.5f) ? 0 : -childWidth; |
|||
else { |
|||
final int width = getWidth(); |
|||
left = xvel < 0 || (xvel == 0 && offset > 0.5f) ? width - childWidth : width; |
|||
} |
|||
|
|||
mDragger.settleCapturedViewAt(left, releasedChild.getTop()); |
|||
invalidate(); |
|||
} |
|||
|
|||
@Override |
|||
public void onEdgeDragStarted(final int edgeFlags, final int pointerId) { |
|||
final View toCapture; |
|||
if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) |
|||
toCapture = findDrawerWithGravity(Gravity.LEFT); |
|||
else toCapture = findDrawerWithGravity(Gravity.RIGHT); |
|||
|
|||
if (toCapture != null && isDrawerView(toCapture)) mDragger.captureChildView(toCapture, pointerId); |
|||
} |
|||
|
|||
@Override |
|||
public int getViewHorizontalDragRange(@NonNull final View child) { |
|||
return isDrawerView(child) ? child.getWidth() : 0; |
|||
} |
|||
|
|||
@Override |
|||
public int clampViewPositionHorizontal(@NonNull final View child, final int left, final int dx) { |
|||
if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) return Math.max(-child.getWidth(), Math.min(left, 0)); |
|||
final int width = getWidth(); |
|||
return Math.max(width - child.getWidth(), Math.min(left, width)); |
|||
} |
|||
|
|||
@Override |
|||
public int clampViewPositionVertical(@NonNull final View child, final int top, final int dy) { |
|||
return child.getTop(); |
|||
} |
|||
} |
|||
|
|||
public static class LayoutParams extends ViewGroup.MarginLayoutParams { |
|||
private static final int FLAG_IS_CLOSING = 0x4; |
|||
public static final int FLAG_IS_OPENED = 0x1; |
|||
public static final int FLAG_IS_OPENING = 0x2; |
|||
public int openState; |
|||
@EdgeGravity |
|||
public int gravity = Gravity.NO_GRAVITY; |
|||
public boolean isPeeking; |
|||
public float onScreen; |
|||
|
|||
public LayoutParams(@NonNull final Context c, @Nullable final AttributeSet attrs) { |
|||
super(c, attrs); |
|||
final TypedArray a = c.obtainStyledAttributes(attrs, new int[]{android.R.attr.layout_gravity}); |
|||
try { |
|||
this.gravity = a.getInt(0, Gravity.NO_GRAVITY); |
|||
} finally { |
|||
a.recycle(); |
|||
} |
|||
} |
|||
|
|||
public LayoutParams(final int width, final int height) { |
|||
super(width, height); |
|||
} |
|||
|
|||
public LayoutParams(@NonNull final LayoutParams source) { |
|||
super(source); |
|||
this.gravity = source.gravity; |
|||
} |
|||
|
|||
public LayoutParams(@NonNull final ViewGroup.LayoutParams source) { |
|||
super(source); |
|||
} |
|||
|
|||
public LayoutParams(@NonNull final ViewGroup.MarginLayoutParams source) { |
|||
super(source); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,179 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Context; |
|||
import android.graphics.RectF; |
|||
import android.text.Layout; |
|||
import android.text.Selection; |
|||
import android.text.Spannable; |
|||
import android.text.SpannableString; |
|||
import android.text.SpannableStringBuilder; |
|||
import android.text.Spanned; |
|||
import android.text.style.BackgroundColorSpan; |
|||
import android.text.style.ClickableSpan; |
|||
import android.text.style.URLSpan; |
|||
import android.util.AttributeSet; |
|||
import android.view.MotionEvent; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.widget.AppCompatTextView; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.interfaces.MentionClickListener; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class RamboTextView extends AppCompatTextView { |
|||
private static final int highlightBackgroundSpanKey = R.id.tvComment; |
|||
private static final RectF touchedLineBounds = new RectF(); |
|||
private ClickableSpan clickableSpanUnderTouchOnActionDown; |
|||
private MentionClickListener mentionClickListener; |
|||
private boolean isUrlHighlighted, isExpandable, isExpanded; |
|||
|
|||
public RamboTextView(final Context context) { |
|||
super(context); |
|||
} |
|||
|
|||
public RamboTextView(final Context context, final AttributeSet attrs) { |
|||
super(context, attrs); |
|||
} |
|||
|
|||
public RamboTextView(final Context context, final AttributeSet attrs, final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
} |
|||
|
|||
public void setMentionClickListener(final MentionClickListener mentionClickListener) { |
|||
this.mentionClickListener = mentionClickListener; |
|||
} |
|||
|
|||
public void setCaptionIsExpandable(final boolean isExpandable) { |
|||
this.isExpandable = isExpandable; |
|||
} |
|||
|
|||
public void setCaptionIsExpanded(final boolean isExpanded) { |
|||
this.isExpanded = isExpanded; |
|||
} |
|||
|
|||
@SuppressLint("ClickableViewAccessibility") |
|||
@Override |
|||
public boolean onTouchEvent(final MotionEvent event) { |
|||
final CharSequence text = getText(); |
|||
if (text instanceof SpannableString || text instanceof SpannableStringBuilder) { |
|||
final Spannable spanText = (Spannable) text; |
|||
final ClickableSpan clickableSpanUnderTouch = findClickableSpanUnderTouch(this, spanText, event); |
|||
|
|||
final int action = event.getAction(); |
|||
|
|||
if (action == MotionEvent.ACTION_DOWN) clickableSpanUnderTouchOnActionDown = clickableSpanUnderTouch; |
|||
final boolean touchStartedOverAClickableSpan = clickableSpanUnderTouchOnActionDown != null; |
|||
final boolean isURLSpan = clickableSpanUnderTouch instanceof URLSpan; |
|||
|
|||
// feed view caption hacks |
|||
if (isExpandable && !touchStartedOverAClickableSpan) |
|||
return !isExpanded | super.onTouchEvent(event); // short operator, because we want two shits to work |
|||
|
|||
final Object tag = getTag(); |
|||
final FeedModel feedModel = tag instanceof FeedModel ? (FeedModel) tag : null; |
|||
|
|||
switch (action) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
if (feedModel != null) feedModel.setMentionClicked(false); |
|||
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText); |
|||
return isURLSpan ? super.onTouchEvent(event) : touchStartedOverAClickableSpan; |
|||
|
|||
case MotionEvent.ACTION_UP: |
|||
if (touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) { |
|||
dispatchUrlClick(spanText, clickableSpanUnderTouch); |
|||
if (feedModel != null) feedModel.setMentionClicked(true); |
|||
} |
|||
cleanupOnTouchUp(spanText); |
|||
return isURLSpan ? super.onTouchEvent(event) : touchStartedOverAClickableSpan; |
|||
|
|||
case MotionEvent.ACTION_MOVE: |
|||
if (feedModel != null) feedModel.setMentionClicked(false); |
|||
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText); |
|||
else removeUrlHighlightColor(spanText); |
|||
return isURLSpan ? super.onTouchEvent(event) : touchStartedOverAClickableSpan; |
|||
|
|||
case MotionEvent.ACTION_CANCEL: |
|||
if (feedModel != null) feedModel.setMentionClicked(false); |
|||
cleanupOnTouchUp(spanText); |
|||
return super.onTouchEvent(event); |
|||
} |
|||
} |
|||
|
|||
return super.onTouchEvent(event); |
|||
} |
|||
|
|||
protected void dispatchUrlClick(final Spanned s, final ClickableSpan clickableSpan) { |
|||
if (mentionClickListener != null) { |
|||
final int spanStart = s.getSpanStart(clickableSpan); |
|||
final boolean ishHashtag = s.charAt(spanStart) == '#'; |
|||
|
|||
final int start = ishHashtag || s.charAt(spanStart) != '@' ? spanStart : spanStart + 1; |
|||
|
|||
CharSequence subSequence = s.subSequence(start, s.getSpanEnd(clickableSpan)); |
|||
|
|||
// for feed ellipsize |
|||
final int indexOfEllipsize = Utils.indexOfChar(subSequence, '…', 0); |
|||
if (indexOfEllipsize != -1) |
|||
subSequence = subSequence.subSequence(0, indexOfEllipsize - 1); |
|||
|
|||
mentionClickListener.onClick(this, subSequence.toString(), ishHashtag); |
|||
} |
|||
} |
|||
|
|||
protected void highlightUrl(final ClickableSpan clickableSpan, final Spannable text) { |
|||
if (!isUrlHighlighted) { |
|||
isUrlHighlighted = true; |
|||
|
|||
final int spanStart = text.getSpanStart(clickableSpan); |
|||
final int spanEnd = text.getSpanEnd(clickableSpan); |
|||
final BackgroundColorSpan highlightSpan = new BackgroundColorSpan(getHighlightColor()); |
|||
text.setSpan(highlightSpan, spanStart, spanEnd, Spannable.SPAN_INCLUSIVE_INCLUSIVE); |
|||
setTag(highlightBackgroundSpanKey, highlightSpan); |
|||
Selection.setSelection(text, spanStart, spanEnd); |
|||
} |
|||
} |
|||
|
|||
protected void removeUrlHighlightColor(final Spannable text) { |
|||
if (isUrlHighlighted) { |
|||
isUrlHighlighted = false; |
|||
|
|||
final BackgroundColorSpan highlightSpan = (BackgroundColorSpan) getTag(highlightBackgroundSpanKey); |
|||
text.removeSpan(highlightSpan); |
|||
|
|||
Selection.removeSelection(text); |
|||
} |
|||
} |
|||
|
|||
private void cleanupOnTouchUp(final Spannable text) { |
|||
clickableSpanUnderTouchOnActionDown = null; |
|||
removeUrlHighlightColor(text); |
|||
} |
|||
|
|||
@Nullable |
|||
private static ClickableSpan findClickableSpanUnderTouch(@NonNull final TextView textView, final Spannable text, @NonNull final MotionEvent event) { |
|||
final int touchX = (int) (event.getX() - textView.getTotalPaddingLeft() + textView.getScrollX()); |
|||
final int touchY = (int) (event.getY() - textView.getTotalPaddingTop() + textView.getScrollY()); |
|||
|
|||
final Layout layout = textView.getLayout(); |
|||
final int touchedLine = layout.getLineForVertical(touchY); |
|||
final int touchOffset = layout.getOffsetForHorizontal(touchedLine, touchX); |
|||
|
|||
touchedLineBounds.left = layout.getLineLeft(touchedLine); |
|||
touchedLineBounds.top = layout.getLineTop(touchedLine); |
|||
touchedLineBounds.right = layout.getLineWidth(touchedLine) + touchedLineBounds.left; |
|||
touchedLineBounds.bottom = layout.getLineBottom(touchedLine); |
|||
|
|||
if (touchedLineBounds.contains(touchX, touchY)) { |
|||
final Object[] spans = text.getSpans(touchOffset, touchOffset, ClickableSpan.class); |
|||
for (final Object span : spans) |
|||
if (span instanceof ClickableSpan) return (ClickableSpan) span; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
} |
@ -0,0 +1,182 @@ |
|||
package awais.instagrabber.customviews; |
|||
|
|||
import android.app.Activity; |
|||
import android.content.Context; |
|||
import android.content.ContextWrapper; |
|||
import android.content.res.Configuration; |
|||
import android.graphics.Rect; |
|||
import android.os.Build; |
|||
import android.util.AttributeSet; |
|||
import android.view.Gravity; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.view.Window; |
|||
import android.widget.FrameLayout; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.view.GravityCompat; |
|||
import androidx.core.view.ViewCompat; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
import androidx.viewpager.widget.PagerAdapter; |
|||
import androidx.viewpager.widget.ViewPager; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
public final class RemixDrawerLayout extends MouseDrawer implements MouseDrawer.DrawerListener { |
|||
private final FrameLayout frameLayout; |
|||
private View drawerView; |
|||
private RecyclerView scroll, feedPosts; |
|||
private float startX; |
|||
|
|||
public RemixDrawerLayout(@NonNull final Context context) { |
|||
this(context, null); |
|||
} |
|||
|
|||
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) { |
|||
this(context, attrs, 0); |
|||
} |
|||
|
|||
public RemixDrawerLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyle) { |
|||
super(context, attrs, defStyle); |
|||
|
|||
super.setDrawerElevation(getDrawerElevation()); |
|||
|
|||
addDrawerListener(this); |
|||
|
|||
frameLayout = new FrameLayout(context); |
|||
frameLayout.setPadding(0, 0, 0, 0); |
|||
super.addView(frameLayout); |
|||
} |
|||
|
|||
@Override |
|||
public void addView(@NonNull final View child, final ViewGroup.LayoutParams params) { |
|||
child.setLayoutParams(params); |
|||
addView(child); |
|||
} |
|||
|
|||
@Override |
|||
public void addView(@NonNull final View child) { |
|||
if (child.getTag() != null) super.addView(child); |
|||
else frameLayout.addView(child); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onInterceptTouchEvent(@NonNull final MotionEvent ev) { |
|||
final float x = ev.getX(); |
|||
final float y = ev.getY(); |
|||
|
|||
// another one of my own weird hack thingies to make this app work |
|||
if (feedPosts == null) feedPosts = findViewById(R.id.feedPosts); |
|||
if (feedPosts != null) { |
|||
for (int i = 0; i < feedPosts.getChildCount(); ++i) { |
|||
final View viewHolder = feedPosts.getChildAt(i); |
|||
final View mediaList = viewHolder.findViewById(R.id.media_list); |
|||
if (mediaList instanceof ViewPager) { |
|||
final ViewPager viewPager = (ViewPager) mediaList; |
|||
|
|||
final Rect rect = new Rect(); |
|||
viewPager.getGlobalVisibleRect(rect); |
|||
|
|||
final boolean touchIsInMediaList = rect.contains((int) x, (int) y); |
|||
if (touchIsInMediaList) { |
|||
final PagerAdapter adapter = viewPager.getAdapter(); |
|||
final int count = adapter != null ? adapter.getCount() : 0; |
|||
if (count < 1 || viewPager.getCurrentItem() != count - 1) return false; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// thanks to Fede @ https://stackoverflow.com/questions/6920137/android-viewpager-and-horizontalscrollview/7258579#7258579 |
|||
if (scroll == null) scroll = findViewById(R.id.highlightsList); |
|||
if (scroll != null) { |
|||
final boolean touchIsInRecycler = x >= scroll.getLeft() && x < scroll.getRight() |
|||
&& y >= scroll.getTop() && scroll.getBottom() > y; |
|||
|
|||
if (touchIsInRecycler) { |
|||
final int action = ev.getActionMasked(); |
|||
|
|||
if (action == MotionEvent.ACTION_CANCEL) return super.onInterceptTouchEvent(ev); |
|||
|
|||
if (action == MotionEvent.ACTION_DOWN) startX = x; |
|||
else if (action == MotionEvent.ACTION_MOVE) { |
|||
final int scrollRange = scroll.computeHorizontalScrollRange(); |
|||
final int scrollOffset = scroll.computeHorizontalScrollOffset(); |
|||
final boolean scrollable = scrollRange > scroll.getWidth(); |
|||
final boolean draggingFromRight = startX > x; |
|||
|
|||
if (scrollOffset < 1) { |
|||
if (!scrollable) return super.onInterceptTouchEvent(ev); |
|||
else if (!draggingFromRight) return super.onInterceptTouchEvent(ev); |
|||
} else if (scrollable && draggingFromRight && scrollRange - scrollOffset == scroll.computeHorizontalScrollExtent()) { |
|||
return super.onInterceptTouchEvent(ev); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
return super.onInterceptTouchEvent(ev); |
|||
} |
|||
|
|||
@Override |
|||
public void onDrawerSlide(@NonNull final View view, @EdgeGravity final int gravity, final float slideOffset) { |
|||
drawerView = view; |
|||
final int absHorizGravity = getDrawerViewAbsoluteGravity(GravityCompat.START); |
|||
final int childAbsGravity = getDrawerViewAbsoluteGravity(drawerView); |
|||
|
|||
final Window window = getActivity(getContext()).getWindow(); |
|||
final boolean isRtl = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL |
|||
|| window.getDecorView().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL |
|||
|| getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); |
|||
|
|||
final int drawerViewWidth = drawerView.getWidth(); |
|||
|
|||
// for (int i = 0; i < frameLayout.getChildCount(); i++) { |
|||
// final View child = frameLayout.getChildAt(i); |
|||
// |
|||
// final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity); |
|||
// float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth; |
|||
// |
|||
// child.setX(width * slideOffset); |
|||
// } |
|||
|
|||
final boolean isLeftDrawer = isRtl == (childAbsGravity != absHorizGravity); |
|||
float width = isLeftDrawer ? drawerViewWidth : -drawerViewWidth; |
|||
|
|||
frameLayout.setX(width * (isRtl ? -slideOffset : slideOffset)); |
|||
} |
|||
|
|||
@Override |
|||
public void openDrawer(@NonNull final View drawerView, final boolean animate) { |
|||
super.openDrawer(drawerView, animate); |
|||
post(() -> onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f)); |
|||
} |
|||
|
|||
@Override |
|||
protected void onConfigurationChanged(final Configuration newConfig) { |
|||
super.onConfigurationChanged(newConfig); |
|||
if (drawerView != null) onDrawerSlide(drawerView, Gravity.NO_GRAVITY, isDrawerOpen(drawerView) ? 1f : 0f); |
|||
} |
|||
|
|||
private static Activity getActivity(final Context context) { |
|||
if (context != null) { |
|||
if (context instanceof Activity) return (Activity) context; |
|||
if (context instanceof ContextWrapper) |
|||
return getActivity(((ContextWrapper) context).getBaseContext()); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
final int getDrawerViewAbsoluteGravity(final int gravity) { |
|||
return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK; |
|||
} |
|||
|
|||
final int getDrawerViewAbsoluteGravity(@NonNull final View drawerView) { |
|||
final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; |
|||
return getDrawerViewAbsoluteGravity(gravity); |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import android.content.Context; |
|||
|
|||
import androidx.recyclerview.widget.GridLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public class GridAutofitLayoutManager extends GridLayoutManager { |
|||
private int mColumnWidth; |
|||
private boolean mColumnWidthChanged = true; |
|||
|
|||
public GridAutofitLayoutManager(Context context, int columnWidth) { |
|||
super(context, 1); |
|||
if (columnWidth <= 0) columnWidth = (int) (48 * Utils.displayMetrics.density); |
|||
if (columnWidth > 0 && columnWidth != mColumnWidth) { |
|||
mColumnWidth = columnWidth; |
|||
mColumnWidthChanged = true; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onLayoutChildren(final RecyclerView.Recycler recycler, final RecyclerView.State state) { |
|||
final int width = getWidth(); |
|||
final int height = getHeight(); |
|||
if (mColumnWidthChanged && mColumnWidth > 0 && width > 0 && height > 0) { |
|||
final int totalSpace = getOrientation() == VERTICAL ? width - getPaddingRight() - getPaddingLeft() |
|||
: height - getPaddingTop() - getPaddingBottom(); |
|||
|
|||
setSpanCount(Math.max(1, totalSpace / mColumnWidth)); |
|||
|
|||
mColumnWidthChanged = false; |
|||
} |
|||
super.onLayoutChildren(recycler, state); |
|||
} |
|||
} |
@ -0,0 +1,31 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import android.graphics.Rect; |
|||
import android.view.View; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.GridLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { |
|||
private final int spacing; |
|||
|
|||
public GridSpacingItemDecoration(int spacing) { |
|||
this.spacing = spacing; |
|||
} |
|||
|
|||
@Override |
|||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { |
|||
final RecyclerView.LayoutManager manager = parent.getLayoutManager(); |
|||
if (manager instanceof GridLayoutManager) { |
|||
final int spanCount = ((GridLayoutManager) manager).getSpanCount(); |
|||
final int position = parent.getChildAdapterPosition(view); |
|||
final int column = position % spanCount; |
|||
|
|||
outRect.left = column * spacing / spanCount; |
|||
outRect.right = spacing - (column + 1) * spacing / spanCount; |
|||
if (position < spanCount) outRect.top = spacing; |
|||
outRect.bottom = spacing; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,67 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.GridLayoutManager; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import awais.instagrabber.interfaces.LazyLoadListener; |
|||
|
|||
// thanks to nesquena's EndlessRecyclerViewScrollListener |
|||
// https://gist.github.com/nesquena/d09dc68ff07e845cc622 |
|||
public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener { |
|||
private int currentPage = 0; // The current offset index of data you have loaded |
|||
private int previousTotalItemCount = 0; // The total number of items in the dataset after the last load |
|||
private boolean loading = true; // True if we are still waiting for the last set of data to load. |
|||
private final int visibleThreshold; // The minimum amount of items to have below your current scroll position before loading more. |
|||
private final LazyLoadListener lazyLoadListener; |
|||
private final RecyclerView.LayoutManager layoutManager; |
|||
|
|||
public RecyclerLazyLoader(@NonNull final RecyclerView.LayoutManager layoutManager, final LazyLoadListener lazyLoadListener) { |
|||
this.layoutManager = layoutManager; |
|||
this.lazyLoadListener = lazyLoadListener; |
|||
if (layoutManager instanceof GridLayoutManager) { |
|||
this.visibleThreshold = 5 * Math.max(3, ((GridLayoutManager) layoutManager).getSpanCount()); |
|||
} else if (layoutManager instanceof LinearLayoutManager) { |
|||
this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8; |
|||
} else { |
|||
this.visibleThreshold = 5; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { |
|||
final int totalItemCount = layoutManager.getItemCount(); |
|||
|
|||
if (totalItemCount < previousTotalItemCount) { |
|||
currentPage = 0; |
|||
previousTotalItemCount = totalItemCount; |
|||
if (totalItemCount == 0) loading = true; |
|||
} |
|||
|
|||
if (loading && totalItemCount > previousTotalItemCount) { |
|||
loading = false; |
|||
previousTotalItemCount = totalItemCount; |
|||
} |
|||
|
|||
final int lastVisibleItemPosition; |
|||
if (layoutManager instanceof GridLayoutManager) { |
|||
final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager; |
|||
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); |
|||
} else { |
|||
final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager; |
|||
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); |
|||
} |
|||
|
|||
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) { |
|||
if (lazyLoadListener != null) lazyLoadListener.onLoadMore(++currentPage, totalItemCount); |
|||
loading = true; |
|||
} |
|||
} |
|||
|
|||
public void resetState() { |
|||
this.currentPage = 0; |
|||
this.previousTotalItemCount = 0; |
|||
this.loading = true; |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import android.util.Log; |
|||
import android.view.GestureDetector; |
|||
import android.view.MotionEvent; |
|||
|
|||
import awais.instagrabber.interfaces.SwipeEvent; |
|||
|
|||
public final class SwipeGestureListener extends GestureDetector.SimpleOnGestureListener { |
|||
public static final int SWIPE_THRESHOLD = 200; |
|||
public static final int SWIPE_VELOCITY_THRESHOLD = 200; |
|||
private final SwipeEvent swipeEvent; |
|||
|
|||
public SwipeGestureListener(final SwipeEvent swipeEvent) { |
|||
this.swipeEvent = swipeEvent; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX, final float velocityY) { |
|||
try { |
|||
final float diffY = e2.getY() - e1.getY(); |
|||
final float diffX = e2.getX() - e1.getX(); |
|||
final float diffXAbs = Math.abs(diffX); |
|||
if (diffXAbs > Math.abs(diffY) && diffXAbs > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) { |
|||
if (diffX > 0) swipeEvent.onSwipe(true); |
|||
else swipeEvent.onSwipe(false); |
|||
return true; |
|||
} |
|||
} catch (final Exception e) { |
|||
Log.e("AWAISKING_APP", "", e); |
|||
} |
|||
return false; |
|||
} |
|||
} |
@ -0,0 +1,282 @@ |
|||
package awais.instagrabber.customviews.helpers; |
|||
|
|||
import android.app.Activity; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.graphics.Rect; |
|||
import android.net.Uri; |
|||
import android.view.View; |
|||
import android.widget.ImageView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.recyclerview.widget.LinearLayoutManager; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.google.android.exoplayer2.Player; |
|||
import com.google.android.exoplayer2.SimpleExoPlayer; |
|||
import com.google.android.exoplayer2.source.ProgressiveMediaSource; |
|||
import com.google.android.exoplayer2.ui.PlayerView; |
|||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; |
|||
|
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.CommentsViewer; |
|||
import awais.instagrabber.adapters.FeedAdapter; |
|||
import awais.instagrabber.models.FeedModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
// wasted around 3 hours to get this working, made from scrach, forgot to take a shower so i'm gonna go take a shower (time: May 11, 2020 @ 8:09:30 PM) |
|||
public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener { |
|||
private static final Object LOCK = new Object(); |
|||
private LinearLayoutManager layoutManager; |
|||
private View firstItemView, lastItemView; |
|||
private int videoPosShown = -1, lastVideoPos = -1, lastChangedVideoPos, lastStoppedVideoPos, lastPlayedVideoPos; |
|||
private boolean videoAttached = false; |
|||
private final List<FeedModel> feedModels; |
|||
//////////////////////////////////////////////////// |
|||
private SimpleExoPlayer player; |
|||
private ImageView btnMute; |
|||
private final Context context; |
|||
private final View.OnClickListener commentClickListener = new View.OnClickListener() { |
|||
@Override |
|||
public void onClick(@NonNull final View v) { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof FeedModel && context instanceof Activity) { |
|||
if (player != null) player.setPlayWhenReady(false); |
|||
((Activity) context).startActivityForResult(new Intent(context, CommentsViewer.class) |
|||
.putExtra(Constants.EXTRAS_SHORTCODE, ((FeedModel) tag).getShortCode()), 6969); |
|||
} |
|||
} |
|||
}; |
|||
private final View.OnClickListener muteClickListener = v -> { |
|||
if (player == null) return; |
|||
final float intVol = player.getVolume() == 0f ? 1f : 0f; |
|||
player.setVolume(intVol); |
|||
if (btnMute != null) btnMute.setImageResource(intVol == 0f ? R.drawable.vol : R.drawable.mute); |
|||
Utils.sessionVolumeFull = intVol == 1f; |
|||
}; |
|||
private final VideoChangeCallback videoChangeCallback; |
|||
// private final ScrollerVideoCallback videoCallback; |
|||
// private View lastVideoHolder; |
|||
// private int videoState = -1; |
|||
|
|||
public VideoAwareRecyclerScroller(final Context context, final List<FeedModel> feedModels, |
|||
final VideoChangeCallback videoChangeCallback) { |
|||
this.context = context; |
|||
this.feedModels = feedModels; |
|||
this.videoChangeCallback = videoChangeCallback; |
|||
} |
|||
|
|||
@Override |
|||
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { |
|||
if (layoutManager == null) { |
|||
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); |
|||
if (layoutManager instanceof LinearLayoutManager) this.layoutManager = (LinearLayoutManager) layoutManager; |
|||
} |
|||
|
|||
if (feedModels.size() > 0 && layoutManager != null) { |
|||
int firstVisibleItemPos = layoutManager.findFirstCompletelyVisibleItemPosition(); |
|||
int lastVisibleItemPos = layoutManager.findLastCompletelyVisibleItemPosition(); |
|||
|
|||
if (firstVisibleItemPos == -1 && lastVisibleItemPos == -1) { |
|||
firstVisibleItemPos = layoutManager.findFirstVisibleItemPosition(); |
|||
lastVisibleItemPos = layoutManager.findLastVisibleItemPosition(); |
|||
} |
|||
|
|||
boolean processFirstItem = false, processLastItem = false; |
|||
View currView; |
|||
if (firstVisibleItemPos != -1) { |
|||
currView = layoutManager.findViewByPosition(firstVisibleItemPos); |
|||
if (currView != null && currView.getId() == R.id.videoHolder) { |
|||
firstItemView = currView; |
|||
processFirstItem = true; |
|||
} |
|||
} |
|||
if (lastVisibleItemPos != -1) { |
|||
currView = layoutManager.findViewByPosition(lastVisibleItemPos); |
|||
if (currView != null && currView.getId() == R.id.videoHolder) { |
|||
lastItemView = currView; |
|||
processLastItem = true; |
|||
} |
|||
} |
|||
|
|||
final Rect visibleItemRect = new Rect(); |
|||
|
|||
int firstVisibleItemHeight = 0, lastVisibleItemHeight = 0; |
|||
|
|||
final boolean isFirstItemVideoHolder = firstItemView != null && firstItemView.getId() == R.id.videoHolder; |
|||
if (isFirstItemVideoHolder) { |
|||
firstItemView.getGlobalVisibleRect(visibleItemRect); |
|||
firstVisibleItemHeight = visibleItemRect.height(); |
|||
} |
|||
final boolean isLastItemVideoHolder = lastItemView != null && lastItemView.getId() == R.id.videoHolder; |
|||
if (isLastItemVideoHolder) { |
|||
lastItemView.getGlobalVisibleRect(visibleItemRect); |
|||
lastVisibleItemHeight = visibleItemRect.height(); |
|||
} |
|||
|
|||
if (processFirstItem && firstVisibleItemHeight > lastVisibleItemHeight) videoPosShown = firstVisibleItemPos; |
|||
else if (processLastItem && lastVisibleItemHeight != 0) videoPosShown = lastVisibleItemPos; |
|||
|
|||
if (firstItemView != lastItemView) { |
|||
final int mox = lastVisibleItemHeight - firstVisibleItemHeight; |
|||
if (processLastItem && lastVisibleItemHeight > firstVisibleItemHeight) videoPosShown = lastVisibleItemPos; |
|||
if ((processFirstItem || processLastItem) && mox >= 0) videoPosShown = lastVisibleItemPos; |
|||
} |
|||
|
|||
if (lastChangedVideoPos != -1 && lastVideoPos != -1) { |
|||
currView = layoutManager.findViewByPosition(lastChangedVideoPos); |
|||
if (currView != null && currView.getId() == R.id.videoHolder && |
|||
lastStoppedVideoPos != lastChangedVideoPos && lastPlayedVideoPos != lastChangedVideoPos) { |
|||
lastStoppedVideoPos = lastChangedVideoPos; |
|||
stopVideo(lastChangedVideoPos, recyclerView, currView); |
|||
} |
|||
|
|||
currView = layoutManager.findViewByPosition(lastVideoPos); |
|||
if (currView != null && currView.getId() == R.id.videoHolder) { |
|||
final Rect rect = new Rect(); |
|||
currView.getGlobalVisibleRect(rect); |
|||
|
|||
final int holderTop = currView.getTop(); |
|||
final int holderHeight = currView.getBottom() - holderTop; |
|||
final int halfHeight = holderHeight / 2; |
|||
//halfHeight -= halfHeight / 5; |
|||
|
|||
if (rect.height() < halfHeight) { |
|||
if (lastStoppedVideoPos != lastVideoPos) { |
|||
lastStoppedVideoPos = lastVideoPos; |
|||
stopVideo(lastVideoPos, recyclerView, currView); |
|||
} |
|||
} else if (lastPlayedVideoPos != lastVideoPos) { |
|||
lastPlayedVideoPos = lastVideoPos; |
|||
playVideo(lastVideoPos, recyclerView, currView); |
|||
} |
|||
} |
|||
|
|||
if (lastChangedVideoPos != lastVideoPos) lastChangedVideoPos = lastVideoPos; |
|||
} |
|||
|
|||
if (lastVideoPos != -1 && lastVideoPos != videoPosShown) { |
|||
if (videoAttached) { |
|||
//if ((currView = layoutManager.findViewByPosition(lastVideoPos)) != null && currView.getId() == R.id.videoHolder) |
|||
releaseVideo(lastVideoPos, recyclerView, null); |
|||
videoAttached = false; |
|||
} |
|||
} |
|||
if (videoPosShown != -1) { |
|||
lastVideoPos = videoPosShown; |
|||
if (!videoAttached) { |
|||
if ((currView = layoutManager.findViewByPosition(videoPosShown)) != null && currView.getId() == R.id.videoHolder) |
|||
attachVideo(videoPosShown, recyclerView, currView); |
|||
videoAttached = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { |
|||
synchronized (LOCK) { |
|||
if (recyclerView != null) { |
|||
final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter(); |
|||
if (adapter instanceof FeedAdapter) { |
|||
final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer; |
|||
if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false); |
|||
} |
|||
} |
|||
|
|||
if (player != null) { |
|||
player.stop(true); |
|||
player.release(); |
|||
player = null; |
|||
} |
|||
|
|||
player = new SimpleExoPlayer.Builder(context).build(); |
|||
|
|||
if (itemView != null) { |
|||
final Object tag = itemView.getTag(); |
|||
|
|||
final View btnComments = itemView.findViewById(R.id.btnComments); |
|||
if (btnComments != null && tag instanceof FeedModel) { |
|||
final FeedModel feedModel = (FeedModel) tag; |
|||
|
|||
if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false); |
|||
else { |
|||
btnComments.setTag(feedModel); |
|||
btnComments.setEnabled(true); |
|||
btnComments.setOnClickListener(commentClickListener); |
|||
} |
|||
} |
|||
|
|||
final PlayerView playerView = itemView.findViewById(R.id.playerView); |
|||
if (playerView == null) return; |
|||
playerView.setPlayer(player); |
|||
|
|||
if (player != null) { |
|||
btnMute = itemView.findViewById(R.id.btnMute); |
|||
|
|||
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; |
|||
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f; |
|||
player.setVolume(vol); |
|||
|
|||
if (btnMute != null) { |
|||
btnMute.setVisibility(View.VISIBLE); |
|||
btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute); |
|||
btnMute.setOnClickListener(muteClickListener); |
|||
} |
|||
|
|||
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); |
|||
|
|||
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram")) |
|||
.createMediaSource(Uri.parse(feedModels.get(itemPos).getDisplayUrl())); |
|||
|
|||
player.setRepeatMode(Player.REPEAT_MODE_ALL); |
|||
player.prepare(mediaSource); |
|||
player.setVolume(vol); |
|||
|
|||
playerView.setOnClickListener(muteClickListener); |
|||
} |
|||
} |
|||
|
|||
if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player); |
|||
} |
|||
} |
|||
|
|||
private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { |
|||
// Log.d("AWAISKING_APP", "release: " + itemPos); |
|||
// if (player != null) { |
|||
// player.stop(true); |
|||
// player.release(); |
|||
// } |
|||
// player = null; |
|||
} |
|||
|
|||
private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { |
|||
// if (player != null) { |
|||
// final int playbackState = player.getPlaybackState(); |
|||
// if (!player.isPlaying() |
|||
// || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED |
|||
// ) { |
|||
// player.setPlayWhenReady(true); |
|||
// } |
|||
// } |
|||
// if (player != null) { |
|||
// player.setPlayWhenReady(true); |
|||
// player.getPlaybackState(); |
|||
// } |
|||
} |
|||
|
|||
private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { |
|||
if (player != null) { |
|||
player.setPlayWhenReady(false); |
|||
player.getPlaybackState(); |
|||
} |
|||
} |
|||
|
|||
public interface VideoChangeCallback { |
|||
void playerChanged(final int itemPos, final SimpleExoPlayer player); |
|||
} |
|||
} |
@ -0,0 +1,252 @@ |
|||
package awais.instagrabber.customviews.masoudss_waveform; |
|||
|
|||
import android.media.MediaCodec; |
|||
import android.media.MediaExtractor; |
|||
import android.media.MediaFormat; |
|||
import android.nfc.FormatException; |
|||
import android.os.Build; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
|
|||
import java.io.File; |
|||
import java.io.FileNotFoundException; |
|||
import java.io.IOException; |
|||
import java.nio.ByteBuffer; |
|||
import java.nio.ByteOrder; |
|||
import java.nio.ShortBuffer; |
|||
import java.util.ArrayList; |
|||
import java.util.List; |
|||
import java.util.Objects; |
|||
|
|||
final class SoundParser { |
|||
private ProgressListener progressListener; |
|||
int[] frameGains; |
|||
////////////////// |
|||
private static String[] supportedExtensions = {"mp3", "wav", "3gpp", "3gp", "amr", "aac", "m4a", "ogg"}; |
|||
private static ArrayList<String> additionalExtensions = new ArrayList<>(); |
|||
|
|||
static void addCustomExtension(final String extension) { |
|||
additionalExtensions.add(extension); |
|||
} |
|||
|
|||
static void removeCustomExtension(final String extension) { |
|||
additionalExtensions.remove(extension); |
|||
} |
|||
|
|||
static void addCustomExtensions(final List<String> extensions) { |
|||
additionalExtensions.addAll(extensions); |
|||
} |
|||
|
|||
static void removeCustomExtensions(final List<String> extensions) { |
|||
additionalExtensions.removeAll(extensions); |
|||
} |
|||
|
|||
private static boolean isFilenameSupported(final String filename) { |
|||
for (final String supportedExtension : supportedExtensions) |
|||
if (filename.endsWith('.' + supportedExtension)) return true; |
|||
for (final String additionalExtension : additionalExtensions) |
|||
if (filename.endsWith('.' + additionalExtension)) return true; |
|||
return false; |
|||
} |
|||
|
|||
@NonNull |
|||
public static SoundParser create(final String fileName, final boolean ignoreExtension) throws IOException, FormatException { |
|||
if (!ignoreExtension && !isFilenameSupported(fileName)) |
|||
throw new FormatException("Not supported file extension."); |
|||
|
|||
final File f = new File(fileName); |
|||
if (!f.exists()) throw new FileNotFoundException(fileName); |
|||
|
|||
final SoundParser soundFile = new SoundParser(); |
|||
soundFile.readFile(f); |
|||
|
|||
return soundFile; |
|||
} |
|||
|
|||
public void setProgressListener(final ProgressListener progressListener) { |
|||
this.progressListener = progressListener; |
|||
} |
|||
|
|||
@SuppressWarnings("deprecation") |
|||
private void readFile(@NonNull final File inputFile) throws IOException, FormatException { |
|||
final MediaExtractor extractor = new MediaExtractor(); |
|||
MediaFormat format = null; |
|||
|
|||
final int fileSizeBytes = (int) inputFile.length(); |
|||
extractor.setDataSource(inputFile.getPath()); |
|||
|
|||
final int numTracks = extractor.getTrackCount(); |
|||
|
|||
int i = 0; |
|||
while (i < numTracks) { |
|||
format = extractor.getTrackFormat(i); |
|||
if (Objects.requireNonNull(format.getString(MediaFormat.KEY_MIME)).startsWith("audio/")) { |
|||
extractor.selectTrack(i); |
|||
break; |
|||
} |
|||
i++; |
|||
} |
|||
|
|||
if (i == numTracks) throw new FormatException("No audio track found in " + inputFile); |
|||
assert format != null; |
|||
|
|||
final int channels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT); |
|||
final int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); |
|||
|
|||
final int expectedNumSamples = (int) (format.getLong(MediaFormat.KEY_DURATION) / 1000000f * sampleRate + 0.5f); |
|||
|
|||
final MediaCodec codec = MediaCodec.createDecoderByType(Objects.requireNonNull(format.getString(MediaFormat.KEY_MIME))); |
|||
codec.configure(format, null, null, 0); |
|||
codec.start(); |
|||
|
|||
final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); |
|||
final ByteBuffer[] inputBuffers = codec.getInputBuffers(); |
|||
|
|||
boolean firstSampleData = true, doneReading = false; |
|||
long presentationTime; |
|||
int sampleSize, decodedSamplesSize = 0, totSizeRead = 0; |
|||
byte[] decodedSamples = null; |
|||
ByteBuffer mDecodedBytes = ByteBuffer.allocate(1 << 20); |
|||
ByteBuffer[] outputBuffers = codec.getOutputBuffers(); |
|||
|
|||
while (true) { |
|||
final int inputBufferIndex = codec.dequeueInputBuffer(100); |
|||
|
|||
if (!doneReading && inputBufferIndex >= 0) { |
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) |
|||
sampleSize = extractor.readSampleData(Objects.requireNonNull(codec.getInputBuffer(inputBufferIndex)), 0); |
|||
else |
|||
sampleSize = extractor.readSampleData(inputBuffers[inputBufferIndex], 0); |
|||
|
|||
if (firstSampleData && sampleSize == 2 && "audio/mp4a-latm".equals(format.getString(MediaFormat.KEY_MIME))) { |
|||
extractor.advance(); |
|||
totSizeRead += sampleSize; |
|||
} else if (sampleSize < 0) { |
|||
codec.queueInputBuffer(inputBufferIndex, 0, 0, -1, MediaCodec.BUFFER_FLAG_END_OF_STREAM); |
|||
doneReading = true; |
|||
} else { |
|||
presentationTime = extractor.getSampleTime(); |
|||
codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTime, 0); |
|||
extractor.advance(); |
|||
totSizeRead += sampleSize; |
|||
|
|||
if (progressListener != null && !progressListener.reportProgress((double) totSizeRead / fileSizeBytes)) { |
|||
// We are asked to stop reading the file. Returning immediately. |
|||
// The SoundFile object is invalid and should NOT be used afterward! |
|||
extractor.release(); |
|||
codec.stop(); |
|||
codec.release(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
firstSampleData = false; |
|||
} |
|||
|
|||
// Get decoded stream from the decoder output buffers. |
|||
final int outputBufferIndex = codec.dequeueOutputBuffer(info, 100); |
|||
if (outputBufferIndex >= 0 && info.size > 0) { |
|||
if (decodedSamplesSize < info.size) { |
|||
decodedSamplesSize = info.size; |
|||
decodedSamples = new byte[decodedSamplesSize]; |
|||
} |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
final ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex); |
|||
assert outputBuffer != null; |
|||
outputBuffer.get(decodedSamples, 0, info.size); |
|||
outputBuffer.clear(); |
|||
} else { |
|||
outputBuffers[outputBufferIndex].get(decodedSamples, 0, info.size); |
|||
outputBuffers[outputBufferIndex].clear(); |
|||
} |
|||
|
|||
// Check if buffer is big enough. Resize it if it's too small. |
|||
if (mDecodedBytes.remaining() < info.size) { |
|||
// Getting a rough estimate of the total size, allocate 20% more, and |
|||
// make sure to allocate at least 5MB more than the initial size. |
|||
final int position = mDecodedBytes.position(); |
|||
|
|||
int newSize = (int) (position * (1.0 * fileSizeBytes / totSizeRead) * 1.2); |
|||
final int infoSize = info.size + 5 * (1 << 20); |
|||
if (newSize - position < infoSize) |
|||
newSize = position + infoSize; |
|||
|
|||
ByteBuffer newDecodedBytes = null; |
|||
|
|||
// Try to allocate memory. If we are OOM, try to run the garbage collector. |
|||
int retry = 10; |
|||
while (retry > 0) { |
|||
try { |
|||
newDecodedBytes = ByteBuffer.allocate(newSize); |
|||
break; |
|||
} catch (final OutOfMemoryError e) { |
|||
retry--; |
|||
} |
|||
} |
|||
if (retry == 0) break; |
|||
mDecodedBytes.rewind(); |
|||
assert newDecodedBytes != null; |
|||
newDecodedBytes.put(mDecodedBytes); |
|||
mDecodedBytes = newDecodedBytes; |
|||
mDecodedBytes.position(position); |
|||
} |
|||
|
|||
mDecodedBytes.put(decodedSamples, 0, info.size); |
|||
codec.releaseOutputBuffer(outputBufferIndex, false); |
|||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
|||
if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) |
|||
outputBuffers = codec.getOutputBuffers(); |
|||
} |
|||
|
|||
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0 || mDecodedBytes.position() / (2 * channels) >= expectedNumSamples) |
|||
break; |
|||
} |
|||
|
|||
final int numSamples = mDecodedBytes.position() / (channels * 2); // One sample = 2 bytes. |
|||
mDecodedBytes.rewind(); |
|||
mDecodedBytes.order(ByteOrder.LITTLE_ENDIAN); |
|||
final ShortBuffer mDecodedSamples = mDecodedBytes.asShortBuffer(); |
|||
// final int avgBitrateKbps = (int) (fileSizeBytes * 8F * ((float) sampleRate / numSamples) / 1000F); |
|||
|
|||
extractor.release(); |
|||
codec.stop(); |
|||
codec.release(); |
|||
|
|||
final int samplesPerFrame = 1024; |
|||
int numFrames = numSamples / samplesPerFrame; |
|||
if (numSamples % samplesPerFrame != 0) numFrames++; |
|||
frameGains = new int[numFrames]; |
|||
// final int[] mFrameLens = new int[numFrames]; |
|||
// final int[] mFrameOffsets = new int[numFrames]; |
|||
// final int frameLens = (int) (1000F * avgBitrateKbps / 8F * ((float) samplesPerFrame / sampleRate)); |
|||
int j, gain, value; |
|||
|
|||
i = 0; |
|||
while (i < numFrames) { |
|||
gain = -1; |
|||
j = 0; |
|||
|
|||
while (j < samplesPerFrame) { |
|||
value = 0; |
|||
for (int k = 0; k < channels; ++k) |
|||
if (mDecodedSamples.remaining() > 0) |
|||
value += Math.abs(mDecodedSamples.get()); |
|||
value /= channels; |
|||
if (gain < value) gain = value; |
|||
j++; |
|||
} |
|||
|
|||
frameGains[i] = (int) Math.sqrt(gain); |
|||
// mFrameLens[i] = frameLens; |
|||
// mFrameOffsets[i] = (int) ((float) i * (1000F * avgBitrateKbps / 8F) * ((float) samplesPerFrame / sampleRate)); |
|||
i++; |
|||
} |
|||
|
|||
mDecodedSamples.rewind(); |
|||
} |
|||
|
|||
private interface ProgressListener { |
|||
boolean reportProgress(final double fractionComplete); |
|||
} |
|||
} |
@ -0,0 +1,5 @@ |
|||
package awais.instagrabber.customviews.masoudss_waveform; |
|||
|
|||
public interface WaveFormProgressChangeListener { |
|||
void onProgressChanged(final WaveformSeekBar waveformSeekBar, final int progress, final boolean fromUser); |
|||
} |
@ -0,0 +1,7 @@ |
|||
package awais.instagrabber.customviews.masoudss_waveform; |
|||
|
|||
public enum WaveGravity { |
|||
TOP, |
|||
CENTER, |
|||
BOTTOM, |
|||
} |
@ -0,0 +1,225 @@ |
|||
package awais.instagrabber.customviews.masoudss_waveform; |
|||
|
|||
import android.annotation.SuppressLint; |
|||
import android.content.Context; |
|||
import android.graphics.Bitmap; |
|||
import android.graphics.BitmapShader; |
|||
import android.graphics.Canvas; |
|||
import android.graphics.Paint; |
|||
import android.graphics.RectF; |
|||
import android.graphics.Shader; |
|||
import android.util.AttributeSet; |
|||
import android.view.MotionEvent; |
|||
import android.view.View; |
|||
import android.view.ViewConfiguration; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.core.content.ContextCompat; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class WaveformSeekBar extends View { |
|||
private final int mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); |
|||
private final Paint mWavePaint = new Paint(Paint.ANTI_ALIAS_FLAG); |
|||
private final RectF mWaveRect = new RectF(); |
|||
private final Canvas mProgressCanvas = new Canvas(); |
|||
private final WaveGravity waveGravity = WaveGravity.BOTTOM; |
|||
private final int waveBackgroundColor; |
|||
private final int waveProgressColor; |
|||
private final float waveWidth = Utils.convertDpToPx(3); |
|||
private final float waveMinHeight = Utils.convertDpToPx(4); |
|||
private final float waveCornerRadius = Utils.convertDpToPx(2); |
|||
private final float waveGap = Utils.convertDpToPx(1); |
|||
private int mCanvasWidth = 0; |
|||
private int mCanvasHeight = 0; |
|||
private float mTouchDownX = 0F; |
|||
private int[] sample; |
|||
private int progress = 0; |
|||
private WaveFormProgressChangeListener progressChangeListener; |
|||
|
|||
public WaveformSeekBar(final Context context) { |
|||
this(context, null); |
|||
} |
|||
|
|||
public WaveformSeekBar(final Context context, @Nullable final AttributeSet attrs) { |
|||
this(context, attrs, 0); |
|||
} |
|||
|
|||
public WaveformSeekBar(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { |
|||
super(context, attrs, defStyleAttr); |
|||
this.waveBackgroundColor = ContextCompat.getColor(context, R.color.text_color_light); |
|||
this.waveProgressColor = ContextCompat.getColor(context, R.color.text_color_dark); |
|||
} |
|||
|
|||
private int getSampleMax() { |
|||
int max = -1; |
|||
if (sample != null) for (final int i : sample) if (i >= max) max = i; |
|||
return max; |
|||
} |
|||
|
|||
@SuppressLint("DrawAllocation") |
|||
@Override |
|||
protected void onDraw(final Canvas canvas) { |
|||
super.onDraw(canvas); |
|||
if (sample != null && sample.length != 0) { |
|||
final int availableWidth = getAvailableWidth(); |
|||
final int availableHeight = getAvailableHeight(); |
|||
|
|||
final float step = availableWidth / (waveGap + waveWidth) / sample.length; |
|||
|
|||
float i = 0F; |
|||
float lastWaveRight = (float) getPaddingLeft(); |
|||
|
|||
final int sampleMax = getSampleMax(); |
|||
while (i < sample.length) { |
|||
float waveHeight = availableHeight * ((float) sample[(int) i] / sampleMax); |
|||
|
|||
if (waveHeight < waveMinHeight) |
|||
waveHeight = waveMinHeight; |
|||
|
|||
final float top; |
|||
if (waveGravity == WaveGravity.TOP) { |
|||
top = (float) getPaddingTop(); |
|||
} else if (waveGravity == WaveGravity.CENTER) { |
|||
top = (float) getPaddingTop() + availableHeight / 2F - waveHeight / 2F; |
|||
} else if (waveGravity == WaveGravity.BOTTOM) { |
|||
top = mCanvasHeight - (float) getPaddingBottom() - waveHeight; |
|||
} else { |
|||
top = 0; |
|||
} |
|||
|
|||
mWaveRect.set(lastWaveRight, top, lastWaveRight + waveWidth, top + waveHeight); |
|||
|
|||
if (mWaveRect.contains(availableWidth * progress / 100F, mWaveRect.centerY())) { |
|||
int bitHeight = (int) mWaveRect.height(); |
|||
if (bitHeight <= 0) bitHeight = (int) waveWidth; |
|||
|
|||
final Bitmap bitmap = Bitmap.createBitmap(availableWidth, bitHeight, Bitmap.Config.ARGB_8888); |
|||
mProgressCanvas.setBitmap(bitmap); |
|||
|
|||
float fillWidth = availableWidth * progress / 100F; |
|||
|
|||
mWavePaint.setColor(waveProgressColor); |
|||
mProgressCanvas.drawRect(0F, 0F, fillWidth, mWaveRect.bottom, mWavePaint); |
|||
|
|||
mWavePaint.setColor(waveBackgroundColor); |
|||
mProgressCanvas.drawRect(fillWidth, 0F, (float) availableWidth, mWaveRect.bottom, mWavePaint); |
|||
|
|||
mWavePaint.setShader(new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)); |
|||
} else { |
|||
mWavePaint.setColor(mWaveRect.right <= availableWidth * progress / 100F ? waveProgressColor : waveBackgroundColor); |
|||
mWavePaint.setShader(null); |
|||
} |
|||
|
|||
canvas.drawRoundRect(mWaveRect, waveCornerRadius, waveCornerRadius, mWavePaint); |
|||
|
|||
lastWaveRight = mWaveRect.right + waveGap; |
|||
|
|||
if (lastWaveRight + waveWidth > availableWidth + getPaddingLeft()) |
|||
break; |
|||
|
|||
i += 1 / step; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean onTouchEvent(final MotionEvent event) { |
|||
if (!isEnabled()) return false; |
|||
|
|||
switch (event.getActionMasked()) { |
|||
case MotionEvent.ACTION_DOWN: |
|||
if (isParentScrolling()) mTouchDownX = event.getX(); |
|||
else updateProgress(event); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_MOVE: |
|||
updateProgress(event); |
|||
break; |
|||
|
|||
case MotionEvent.ACTION_UP: |
|||
if (Math.abs(event.getX() - mTouchDownX) > mScaledTouchSlop) |
|||
updateProgress(event); |
|||
|
|||
performClick(); |
|||
break; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
protected void onSizeChanged(final int w, final int h, final int oldw, final int oldh) { |
|||
super.onSizeChanged(w, h, oldw, oldh); |
|||
mCanvasWidth = w; |
|||
mCanvasHeight = h; |
|||
} |
|||
|
|||
@Override |
|||
public boolean performClick() { |
|||
super.performClick(); |
|||
return true; |
|||
} |
|||
|
|||
private boolean isParentScrolling() { |
|||
View parent = (View) getParent(); |
|||
final View root = getRootView(); |
|||
|
|||
while (true) { |
|||
if (parent.canScrollHorizontally(1) || parent.canScrollHorizontally(-1) || |
|||
parent.canScrollVertically(1) || parent.canScrollVertically(-1)) |
|||
return true; |
|||
|
|||
if (parent == root) return false; |
|||
|
|||
parent = (View) parent.getParent(); |
|||
} |
|||
} |
|||
|
|||
private void updateProgress(@NonNull final MotionEvent event) { |
|||
progress = (int) (100 * event.getX() / getAvailableWidth()); |
|||
invalidate(); |
|||
|
|||
if (progressChangeListener != null) |
|||
progressChangeListener.onProgressChanged(this, Math.min(Math.max(0, progress), 100), true); |
|||
} |
|||
|
|||
private int getAvailableWidth() { |
|||
return mCanvasWidth - getPaddingLeft() - getPaddingRight(); |
|||
} |
|||
|
|||
private int getAvailableHeight() { |
|||
return mCanvasHeight - getPaddingTop() - getPaddingBottom(); |
|||
} |
|||
|
|||
// public void setSampleFrom(final String path, final boolean ignoreExtension) { // was false |
|||
// try { |
|||
// final SoundParser soundFile = SoundParser.create(path, ignoreExtension); |
|||
// sample = soundFile.frameGains; |
|||
// } catch (final Exception e) { |
|||
// sample = null; |
|||
// } |
|||
// } |
|||
// |
|||
// public void setSampleFrom(@NonNull final File file, final boolean ignoreExtension) { // was false |
|||
// setSampleFrom(file.getAbsolutePath(), ignoreExtension); |
|||
// } |
|||
|
|||
public void setProgress(final int progress) { |
|||
this.progress = progress; |
|||
invalidate(); |
|||
} |
|||
|
|||
public void setProgressChangeListener(final WaveFormProgressChangeListener progressChangeListener) { |
|||
this.progressChangeListener = progressChangeListener; |
|||
} |
|||
|
|||
public void setSample(final int[] sample) { |
|||
if (sample != this.sample) { |
|||
this.sample = sample; |
|||
invalidate(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,98 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Dialog; |
|||
import android.content.Intent; |
|||
import android.net.Uri; |
|||
import android.os.Bundle; |
|||
import android.text.SpannableStringBuilder; |
|||
import android.text.method.LinkMovementMethod; |
|||
import android.text.style.RelativeSizeSpan; |
|||
import android.text.style.URLSpan; |
|||
import android.view.View; |
|||
import android.widget.TextView; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.widget.LinearLayoutCompat; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
public final class AboutDialog extends BottomSheetDialogFragment { |
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { |
|||
final Dialog dialog = super.onCreateDialog(savedInstanceState); |
|||
final View contentView = View.inflate(getContext(), R.layout.dialog_main_about, null); |
|||
|
|||
final LinearLayoutCompat infoContainer = contentView.findViewById(R.id.infoContainer); |
|||
|
|||
final View btnTelegram = infoContainer.getChildAt(1); |
|||
final View btnProject = infoContainer.getChildAt(2); |
|||
final View.OnClickListener onClickListener = v -> { |
|||
final Intent intent = new Intent(Intent.ACTION_VIEW); |
|||
if (v == btnTelegram) { |
|||
intent.setData(Uri.parse("https://t.me/grabber_app")); |
|||
if (!Utils.isEmpty(Utils.telegramPackage)) |
|||
intent.setPackage(Utils.telegramPackage); |
|||
} else |
|||
intent.setData(Uri.parse("https://gitlab.com/AwaisKing/instagrabber/")); |
|||
startActivity(intent); |
|||
}; |
|||
btnProject.setOnClickListener(onClickListener); |
|||
btnTelegram.setOnClickListener(onClickListener); |
|||
|
|||
final String description = getString(R.string.description); |
|||
if (!Utils.isEmpty(description)) { |
|||
final SpannableStringBuilder descriptionText = new SpannableStringBuilder(description, 0, description.length()); |
|||
|
|||
int lastIndex = descriptionText.length() / 2; |
|||
for (int i = 0; i < descriptionText.length(); ++i) { |
|||
char c = descriptionText.charAt(i); |
|||
|
|||
if (c == '[') { |
|||
final int smallTextStart = i; |
|||
descriptionText.delete(i, i + 1); |
|||
|
|||
do { |
|||
c = descriptionText.charAt(i); |
|||
if (c == ']') { |
|||
descriptionText.delete(i, i + 1); |
|||
descriptionText.setSpan(new RelativeSizeSpan(0.5f), smallTextStart, i, 0); |
|||
} |
|||
++i; |
|||
} while (c != ']' || i == descriptionText.length() - 1); |
|||
} else if (c == '{') { |
|||
final int smallerTextStart = i; |
|||
descriptionText.delete(i, i + 1); |
|||
i = smallerTextStart; |
|||
|
|||
do { |
|||
c = descriptionText.charAt(i); |
|||
if (c == '}') { |
|||
descriptionText.delete(i, i + 1); |
|||
descriptionText.setSpan(new RelativeSizeSpan(0.35f), smallerTextStart, i, 0); |
|||
} |
|||
++i; |
|||
lastIndex = i; |
|||
} while (c != '}' || i == descriptionText.length() - 1); |
|||
} |
|||
} |
|||
|
|||
lastIndex = Utils.indexOfChar(descriptionText, '@', lastIndex); |
|||
descriptionText.setSpan(new URLSpan("https://t.me/awais404"), lastIndex, lastIndex + 9, 0); |
|||
|
|||
lastIndex = Utils.indexOfChar(descriptionText, ':', lastIndex + 9) + 2; |
|||
descriptionText.setSpan(new URLSpan("mailto:[email protected]"), lastIndex, lastIndex + 24, 0); |
|||
|
|||
final TextView textView = (TextView) infoContainer.getChildAt(0); |
|||
textView.setMovementMethod(new LinkMovementMethod()); |
|||
textView.setText(descriptionText, TextView.BufferType.SPANNABLE); |
|||
} |
|||
|
|||
dialog.setContentView(contentView); |
|||
return dialog; |
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Activity; |
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.content.DialogInterface; |
|||
import android.os.Bundle; |
|||
import android.view.View; |
|||
import android.widget.AdapterView; |
|||
import android.widget.Spinner; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
|
|||
import awais.instagrabber.R; |
|||
|
|||
import static awais.instagrabber.utils.Constants.PROFILE_FETCH_MODE; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class ProfileSettingsDialog extends BottomSheetDialogFragment implements AdapterView.OnItemSelectedListener { |
|||
private int fetchIndex; |
|||
private Activity activity; |
|||
private Spinner spProfileFetchMode; |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { |
|||
final Dialog dialog = super.onCreateDialog(savedInstanceState); |
|||
|
|||
final Context context = getContext(); |
|||
activity = context instanceof Activity ? (Activity) context : getActivity(); |
|||
|
|||
final View contentView = View.inflate(activity, R.layout.dialog_profile_settings, null); |
|||
|
|||
spProfileFetchMode = contentView.findViewById(R.id.spProfileFetchMode); |
|||
|
|||
fetchIndex = Math.min(2, Math.max(0, settingsHelper.getInteger(PROFILE_FETCH_MODE))); |
|||
spProfileFetchMode.setSelection(fetchIndex); |
|||
spProfileFetchMode.setOnItemSelectedListener(this); |
|||
|
|||
dialog.setContentView(contentView); |
|||
|
|||
return dialog; |
|||
} |
|||
|
|||
@Override |
|||
public void onDismiss(@NonNull final DialogInterface dialog) { |
|||
super.onDismiss(dialog); |
|||
if (activity != null && (spProfileFetchMode == null || fetchIndex != spProfileFetchMode.getSelectedItemPosition())) |
|||
activity.recreate(); |
|||
} |
|||
|
|||
@Override |
|||
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) { |
|||
settingsHelper.putInteger(PROFILE_FETCH_MODE, position); |
|||
} |
|||
|
|||
@Override |
|||
public void onNothingSelected(final AdapterView<?> parent) { } |
|||
} |
@ -0,0 +1,169 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Activity; |
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.content.DialogInterface; |
|||
import android.content.pm.PackageManager; |
|||
import android.os.Bundle; |
|||
import android.view.View; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.app.AlertDialog; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.recyclerview.widget.DividerItemDecoration; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
|
|||
import java.util.ArrayList; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.Main; |
|||
import awais.instagrabber.adapters.SimpleAdapter; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.DataBox; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class QuickAccessDialog extends BottomSheetDialogFragment implements DialogInterface.OnShowListener, |
|||
View.OnClickListener, View.OnLongClickListener { |
|||
private boolean cookieChanged, isQuery; |
|||
private Activity activity; |
|||
private String userQuery; |
|||
private View btnFavorite, btnImportExport; |
|||
private SimpleAdapter<DataBox.FavoriteModel> favoritesAdapter; |
|||
|
|||
public QuickAccessDialog setQuery(final String userQuery) { |
|||
this.userQuery = userQuery; |
|||
return this; |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { |
|||
final Dialog dialog = super.onCreateDialog(savedInstanceState); |
|||
|
|||
dialog.setOnShowListener(this); |
|||
|
|||
final Context context = getContext(); |
|||
activity = context instanceof Activity ? (Activity) context : getActivity(); |
|||
|
|||
final View contentView = View.inflate(activity, R.layout.dialog_quick_access, null); |
|||
|
|||
btnFavorite = contentView.findViewById(R.id.btnFavorite); |
|||
btnImportExport = contentView.findViewById(R.id.importExport); |
|||
|
|||
isQuery = !Utils.isEmpty(userQuery); |
|||
btnFavorite.setVisibility(isQuery ? View.VISIBLE : View.GONE); |
|||
Utils.setTooltipText(btnImportExport, R.string.import_export); |
|||
|
|||
favoritesAdapter = new SimpleAdapter<>(activity, Utils.dataBox.getAllFavorites(), this, this); |
|||
|
|||
btnFavorite.setOnClickListener(this); |
|||
btnImportExport.setOnClickListener(this); |
|||
|
|||
final RecyclerView rvFavorites = contentView.findViewById(R.id.rvFavorites); |
|||
final RecyclerView rvQuickAccess = contentView.findViewById(R.id.rvQuickAccess); |
|||
|
|||
final DividerItemDecoration itemDecoration = new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL); |
|||
rvFavorites.addItemDecoration(itemDecoration); |
|||
rvFavorites.setAdapter(favoritesAdapter); |
|||
|
|||
final String cookieStr = settingsHelper.getString(Constants.COOKIE); |
|||
if (!Utils.isEmpty(cookieStr) |
|||
|| Utils.dataBox.getCookieCount() > 0 // fallback for export / import |
|||
) { |
|||
rvQuickAccess.addItemDecoration(itemDecoration); |
|||
final ArrayList<DataBox.CookieModel> allCookies = Utils.dataBox.getAllCookies(); |
|||
if (!Utils.isEmpty(cookieStr) && allCookies != null) { |
|||
for (final DataBox.CookieModel cookie : allCookies) { |
|||
if (cookieStr.equals(cookie.getCookie())) { |
|||
cookie.setSelected(true); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
rvQuickAccess.setAdapter(new SimpleAdapter<>(activity, allCookies, this, this)); |
|||
} else { |
|||
((View) rvQuickAccess.getParent()).setVisibility(View.GONE); |
|||
} |
|||
|
|||
dialog.setContentView(contentView); |
|||
return dialog; |
|||
} |
|||
|
|||
@Override |
|||
public void onClick(@NonNull final View v) { |
|||
final Object tag = v.getTag(); |
|||
if (v == btnFavorite) { |
|||
if (isQuery) { |
|||
Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis())); |
|||
favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); |
|||
} |
|||
} else if (v == btnImportExport) { |
|||
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED) |
|||
requestPermissions(Utils.PERMS, 6007); |
|||
else Utils.showImportExportDialog(v.getContext()); |
|||
|
|||
} else if (tag instanceof DataBox.FavoriteModel) { |
|||
if (Main.scanHack != null) { |
|||
Main.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery()); |
|||
dismiss(); |
|||
} |
|||
|
|||
} else if (tag instanceof DataBox.CookieModel) { |
|||
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; |
|||
if (!cookieModel.isSelected()) { |
|||
settingsHelper.putString(Constants.COOKIE, cookieModel.getCookie()); |
|||
Utils.setupCookies(cookieModel.getCookie()); |
|||
cookieChanged = true; |
|||
} |
|||
dismiss(); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public boolean onLongClick(@NonNull final View v) { |
|||
final Object tag = v.getTag(); |
|||
|
|||
if (tag instanceof DataBox.FavoriteModel) { |
|||
final DataBox.FavoriteModel favoriteModel = (DataBox.FavoriteModel) tag; |
|||
|
|||
new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> Utils.dataBox.delFavorite(favoriteModel)) |
|||
.setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete, |
|||
favoriteModel.getQuery())).show(); |
|||
|
|||
} else if (tag instanceof DataBox.CookieModel) { |
|||
final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; |
|||
|
|||
if (cookieModel.isSelected()) |
|||
Toast.makeText(v.getContext(), R.string.quick_access_cannot_delete_curr, Toast.LENGTH_SHORT).show(); |
|||
else |
|||
new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> Utils.dataBox.delUserCookie(cookieModel)) |
|||
.setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete, |
|||
cookieModel.getUsername())).show(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public void onDismiss(@NonNull final DialogInterface dialog) { |
|||
super.onDismiss(dialog); |
|||
if (cookieChanged && activity != null) activity.recreate(); |
|||
} |
|||
|
|||
@Override |
|||
public void onShow(final DialogInterface dialog) { |
|||
if (settingsHelper.getBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG)) |
|||
new AlertDialog.Builder(activity) |
|||
.setMessage(R.string.quick_access_info_dialog) |
|||
.setPositiveButton(R.string.ok, null) |
|||
.setNeutralButton(R.string.dont_show_again, (d, which) -> |
|||
settingsHelper.putBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG, false)).show(); |
|||
} |
|||
} |
@ -0,0 +1,213 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Activity; |
|||
import android.app.Dialog; |
|||
import android.content.Context; |
|||
import android.content.DialogInterface; |
|||
import android.content.Intent; |
|||
import android.content.pm.PackageManager; |
|||
import android.os.Bundle; |
|||
import android.view.View; |
|||
import android.view.ViewGroup; |
|||
import android.widget.AdapterView; |
|||
import android.widget.CompoundButton; |
|||
import android.widget.Spinner; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.widget.AppCompatCheckBox; |
|||
import androidx.core.content.ContextCompat; |
|||
import androidx.fragment.app.FragmentManager; |
|||
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.Login; |
|||
import awais.instagrabber.utils.DirectoryChooser; |
|||
import awais.instagrabber.utils.LocaleUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awaisomereport.CrashReporter; |
|||
|
|||
import static awais.instagrabber.utils.Constants.APP_LANGUAGE; |
|||
import static awais.instagrabber.utils.Constants.APP_THEME; |
|||
import static awais.instagrabber.utils.Constants.AUTOLOAD_POSTS; |
|||
import static awais.instagrabber.utils.Constants.AUTOPLAY_VIDEOS; |
|||
import static awais.instagrabber.utils.Constants.BOTTOM_TOOLBAR; |
|||
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_PATH; |
|||
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; |
|||
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS; |
|||
import static awais.instagrabber.utils.Constants.SHOW_FEED; |
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class SettingsDialog extends BottomSheetDialogFragment implements View.OnClickListener, AdapterView.OnItemSelectedListener, |
|||
CompoundButton.OnCheckedChangeListener { |
|||
private Activity activity; |
|||
private FragmentManager fragmentManager; |
|||
private View btnSaveTo, btnImportExport, btnLogin, btnTimeSettings, btnReport; |
|||
private Spinner spAppTheme, spLanguage; |
|||
private boolean somethingChanged = false; |
|||
private int currentTheme, currentLanguage, selectedLanguage; |
|||
|
|||
@Override |
|||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { |
|||
if (requestCode != 6200) return; |
|||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) showDirectoryChooser(); |
|||
else Toast.makeText(activity, R.string.direct_download_perms_ask, Toast.LENGTH_SHORT).show(); |
|||
} |
|||
|
|||
private void showDirectoryChooser() { |
|||
FragmentManager fragmentManager = getFragmentManager(); |
|||
if (fragmentManager == null) fragmentManager = getChildFragmentManager(); |
|||
|
|||
new DirectoryChooser().setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) |
|||
.setInteractionListener(path -> { |
|||
settingsHelper.putString(FOLDER_PATH, path); |
|||
somethingChanged = true; |
|||
}).show(fragmentManager, null); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { |
|||
final Dialog dialog = super.onCreateDialog(savedInstanceState); |
|||
|
|||
final Context context = getContext(); |
|||
activity = context instanceof Activity ? (Activity) context : getActivity(); |
|||
|
|||
fragmentManager = getFragmentManager(); |
|||
if (fragmentManager == null) fragmentManager = getChildFragmentManager(); |
|||
|
|||
final View contentView = View.inflate(activity, R.layout.dialog_main_settings, null); |
|||
|
|||
btnLogin = contentView.findViewById(R.id.btnLogin); |
|||
btnSaveTo = contentView.findViewById(R.id.btnSaveTo); |
|||
btnImportExport = contentView.findViewById(R.id.importExport); |
|||
btnTimeSettings = contentView.findViewById(R.id.btnTimeSettings); |
|||
btnReport = contentView.findViewById(R.id.btnReport); |
|||
|
|||
Utils.setTooltipText(btnImportExport, R.string.import_export); |
|||
|
|||
btnLogin.setOnClickListener(this); |
|||
btnReport.setOnClickListener(this); |
|||
btnSaveTo.setOnClickListener(this); |
|||
btnImportExport.setOnClickListener(this); |
|||
btnTimeSettings.setOnClickListener(this); |
|||
|
|||
spAppTheme = contentView.findViewById(R.id.spAppTheme); |
|||
currentTheme = settingsHelper.getInteger(APP_THEME); |
|||
spAppTheme.setSelection(currentTheme); |
|||
spAppTheme.setOnItemSelectedListener(this); |
|||
|
|||
spLanguage = contentView.findViewById(R.id.spLanguage); |
|||
currentLanguage = settingsHelper.getInteger(APP_LANGUAGE); |
|||
spLanguage.setSelection(currentLanguage); |
|||
spLanguage.setOnItemSelectedListener(this); |
|||
|
|||
final AppCompatCheckBox cbSaveTo = contentView.findViewById(R.id.cbSaveTo); |
|||
final AppCompatCheckBox cbShowFeed = contentView.findViewById(R.id.cbShowFeed); |
|||
final AppCompatCheckBox cbMuteVideos = contentView.findViewById(R.id.cbMuteVideos); |
|||
final AppCompatCheckBox cbBottomToolbar = contentView.findViewById(R.id.cbBottomToolbar); |
|||
final AppCompatCheckBox cbAutoloadPosts = contentView.findViewById(R.id.cbAutoloadPosts); |
|||
final AppCompatCheckBox cbAutoplayVideos = contentView.findViewById(R.id.cbAutoplayVideos); |
|||
final AppCompatCheckBox cbDownloadUsername = contentView.findViewById(R.id.cbDownloadUsername); |
|||
|
|||
cbSaveTo.setChecked(settingsHelper.getBoolean(FOLDER_SAVE_TO)); |
|||
cbMuteVideos.setChecked(settingsHelper.getBoolean(MUTED_VIDEOS)); |
|||
cbBottomToolbar.setChecked(settingsHelper.getBoolean(BOTTOM_TOOLBAR)); |
|||
cbAutoplayVideos.setChecked(settingsHelper.getBoolean(AUTOPLAY_VIDEOS)); |
|||
|
|||
cbShowFeed.setChecked(settingsHelper.getBoolean(SHOW_FEED)); |
|||
cbAutoloadPosts.setChecked(settingsHelper.getBoolean(AUTOLOAD_POSTS)); |
|||
cbDownloadUsername.setChecked(settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER)); |
|||
|
|||
setupListener(cbSaveTo); |
|||
setupListener(cbShowFeed); |
|||
setupListener(cbMuteVideos); |
|||
setupListener(cbBottomToolbar); |
|||
setupListener(cbAutoloadPosts); |
|||
setupListener(cbAutoplayVideos); |
|||
setupListener(cbDownloadUsername); |
|||
|
|||
btnSaveTo.setEnabled(cbSaveTo.isChecked()); |
|||
|
|||
dialog.setContentView(contentView); |
|||
|
|||
return dialog; |
|||
} |
|||
|
|||
private void setupListener(@NonNull final AppCompatCheckBox checkBox) { |
|||
checkBox.setOnCheckedChangeListener(this); |
|||
((View) checkBox.getParent()).setOnClickListener(this); |
|||
} |
|||
|
|||
@Override |
|||
public void onItemSelected(final AdapterView<?> spinner, final View view, final int position, final long id) { |
|||
if (spinner == spAppTheme) { |
|||
if (position != currentTheme) { |
|||
settingsHelper.putInteger(APP_THEME, position); |
|||
somethingChanged = true; |
|||
} |
|||
} else if (spinner == spLanguage) { |
|||
selectedLanguage = position; |
|||
if (position != currentLanguage) { |
|||
settingsHelper.putInteger(APP_LANGUAGE, position); |
|||
somethingChanged = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onClick(final View v) { |
|||
if (v == btnLogin) { |
|||
startActivity(new Intent(v.getContext(), Login.class)); |
|||
somethingChanged = true; |
|||
|
|||
} else if (v == btnImportExport) { |
|||
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED) |
|||
requestPermissions(Utils.PERMS, 6007); |
|||
else Utils.showImportExportDialog(activity); |
|||
|
|||
} else if (v == btnTimeSettings) { |
|||
new TimeSettingsDialog().show(fragmentManager, null); |
|||
|
|||
} else if (v == btnReport) { |
|||
CrashReporter.get(activity.getApplication()).zipLogs().startCrashEmailIntent(activity, true); |
|||
|
|||
} else if (v == btnSaveTo) { |
|||
if (ContextCompat.checkSelfPermission(activity, Utils.PERMS[0]) == PackageManager.PERMISSION_DENIED) |
|||
requestPermissions(Utils.PERMS, 6200); |
|||
else showDirectoryChooser(); |
|||
|
|||
} else if (v instanceof ViewGroup) |
|||
((ViewGroup) v).getChildAt(0).performClick(); |
|||
} |
|||
|
|||
@Override |
|||
public void onCheckedChanged(@NonNull final CompoundButton checkBox, final boolean checked) { |
|||
final int id = checkBox.getId(); |
|||
if (id == R.id.cbDownloadUsername) settingsHelper.putBoolean(DOWNLOAD_USER_FOLDER, checked); |
|||
else if (id == R.id.cbBottomToolbar) settingsHelper.putBoolean(BOTTOM_TOOLBAR, checked); |
|||
else if (id == R.id.cbAutoplayVideos) settingsHelper.putBoolean(AUTOPLAY_VIDEOS, checked); |
|||
else if (id == R.id.cbMuteVideos) settingsHelper.putBoolean(MUTED_VIDEOS, checked); |
|||
else if (id == R.id.cbAutoloadPosts) settingsHelper.putBoolean(AUTOLOAD_POSTS, checked); |
|||
else if (id == R.id.cbShowFeed) settingsHelper.putBoolean(SHOW_FEED, checked); |
|||
else if (id == R.id.cbSaveTo) { |
|||
settingsHelper.putBoolean(FOLDER_SAVE_TO, checked); |
|||
btnSaveTo.setEnabled(checked); |
|||
} |
|||
somethingChanged = true; |
|||
} |
|||
|
|||
@Override |
|||
public void onDismiss(@NonNull final DialogInterface dialog) { |
|||
if (selectedLanguage != currentLanguage) |
|||
LocaleUtils.setLocale(activity != null ? activity.getBaseContext() : getLayoutInflater().getContext().getApplicationContext()); |
|||
super.onDismiss(dialog); |
|||
if (somethingChanged && activity != null) activity.recreate(); |
|||
} |
|||
|
|||
@Override |
|||
public void onNothingSelected(final AdapterView<?> parent) { } |
|||
} |
@ -0,0 +1,173 @@ |
|||
package awais.instagrabber.dialogs; |
|||
|
|||
import android.app.Dialog; |
|||
import android.os.Bundle; |
|||
import android.text.Editable; |
|||
import android.text.TextWatcher; |
|||
import android.view.LayoutInflater; |
|||
import android.view.View; |
|||
import android.widget.AdapterView; |
|||
import android.widget.CompoundButton; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.fragment.app.DialogFragment; |
|||
|
|||
import java.text.SimpleDateFormat; |
|||
import java.util.Calendar; |
|||
import java.util.Date; |
|||
import java.util.GregorianCalendar; |
|||
|
|||
import awais.instagrabber.databinding.DialogTimeSettingsBinding; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.LocaleUtils; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.settingsHelper; |
|||
|
|||
public final class TimeSettingsDialog extends DialogFragment implements AdapterView.OnItemSelectedListener, CompoundButton.OnCheckedChangeListener, |
|||
View.OnClickListener, TextWatcher { |
|||
private DialogTimeSettingsBinding timeSettingsBinding; |
|||
private final Date magicDate; |
|||
private SimpleDateFormat currentFormat; |
|||
private String selectedFormat; |
|||
|
|||
public TimeSettingsDialog() { |
|||
super(); |
|||
final Calendar instance = GregorianCalendar.getInstance(); |
|||
instance.set(2020, 5, 22, 8, 17, 13); |
|||
magicDate = instance.getTime(); |
|||
} |
|||
|
|||
@NonNull |
|||
@Override |
|||
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { |
|||
final Dialog dialog = super.onCreateDialog(savedInstanceState); |
|||
timeSettingsBinding = DialogTimeSettingsBinding.inflate(LayoutInflater.from(getContext())); |
|||
|
|||
timeSettingsBinding.cbCustomFormat.setOnCheckedChangeListener(this); |
|||
|
|||
timeSettingsBinding.cbCustomFormat.setChecked(settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED)); |
|||
timeSettingsBinding.etCustomFormat.setText(settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT)); |
|||
|
|||
final String[] dateTimeFormat = settingsHelper.getString(Constants.DATE_TIME_SELECTION).split(";"); // output = time;separator;date |
|||
timeSettingsBinding.spTimeFormat.setSelection(Integer.parseInt(dateTimeFormat[0])); |
|||
timeSettingsBinding.spSeparator.setSelection(Integer.parseInt(dateTimeFormat[1])); |
|||
timeSettingsBinding.spDateFormat.setSelection(Integer.parseInt(dateTimeFormat[2])); |
|||
|
|||
timeSettingsBinding.cbSwapTimeDate.setOnCheckedChangeListener(this); |
|||
|
|||
refreshTimeFormat(); |
|||
|
|||
timeSettingsBinding.spTimeFormat.setOnItemSelectedListener(this); |
|||
timeSettingsBinding.spDateFormat.setOnItemSelectedListener(this); |
|||
timeSettingsBinding.spSeparator.setOnItemSelectedListener(this); |
|||
|
|||
timeSettingsBinding.etCustomFormat.addTextChangedListener(this); |
|||
timeSettingsBinding.btnConfirm.setOnClickListener(this); |
|||
timeSettingsBinding.btnInfo.setOnClickListener(this); |
|||
|
|||
dialog.setContentView(timeSettingsBinding.getRoot()); |
|||
return dialog; |
|||
} |
|||
|
|||
private void refreshTimeFormat() { |
|||
if (timeSettingsBinding.cbCustomFormat.isChecked()) { |
|||
timeSettingsBinding.btnConfirm.setEnabled(false); |
|||
checkCustomTimeFormat(); |
|||
} else { |
|||
final String sepStr = String.valueOf(timeSettingsBinding.spSeparator.getSelectedItem()); |
|||
final String timeStr = String.valueOf(timeSettingsBinding.spTimeFormat.getSelectedItem()); |
|||
final String dateStr = String.valueOf(timeSettingsBinding.spDateFormat.getSelectedItem()); |
|||
|
|||
final boolean isSwapTime = !timeSettingsBinding.cbSwapTimeDate.isChecked(); |
|||
|
|||
selectedFormat = (isSwapTime ? timeStr : dateStr) |
|||
+ (Utils.isEmpty(sepStr) || timeSettingsBinding.spSeparator.getSelectedItemPosition() == 0 ? " " : " '" + sepStr + "' ") |
|||
+ (isSwapTime ? dateStr : timeStr); |
|||
|
|||
timeSettingsBinding.btnConfirm.setEnabled(true); |
|||
timeSettingsBinding.timePreview.setText((currentFormat = new SimpleDateFormat(selectedFormat, LocaleUtils.getCurrentLocale())).format(magicDate)); |
|||
} |
|||
} |
|||
|
|||
private void checkCustomTimeFormat() { |
|||
try { |
|||
//noinspection ConstantConditions |
|||
final String string = timeSettingsBinding.etCustomFormat.getText().toString(); |
|||
if (Utils.isEmpty(string)) throw new NullPointerException(); |
|||
|
|||
final String format = (currentFormat = new SimpleDateFormat(string, LocaleUtils.getCurrentLocale())).format(magicDate); |
|||
timeSettingsBinding.timePreview.setText(format); |
|||
|
|||
timeSettingsBinding.btnConfirm.setEnabled(true); |
|||
} catch (final Exception e) { |
|||
timeSettingsBinding.btnConfirm.setEnabled(false); |
|||
timeSettingsBinding.timePreview.setText(null); |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onItemSelected(final AdapterView<?> p, final View v, final int pos, final long id) { |
|||
refreshTimeFormat(); |
|||
} |
|||
|
|||
@Override |
|||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { |
|||
if (buttonView == timeSettingsBinding.cbCustomFormat) { |
|||
timeSettingsBinding.etCustomFormat.setEnabled(isChecked); |
|||
timeSettingsBinding.btnInfo.setEnabled(isChecked); |
|||
|
|||
timeSettingsBinding.spTimeFormat.setEnabled(!isChecked); |
|||
timeSettingsBinding.spDateFormat.setEnabled(!isChecked); |
|||
timeSettingsBinding.spSeparator.setEnabled(!isChecked); |
|||
timeSettingsBinding.cbSwapTimeDate.setEnabled(!isChecked); |
|||
} |
|||
refreshTimeFormat(); |
|||
} |
|||
|
|||
@Override |
|||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { |
|||
checkCustomTimeFormat(); |
|||
} |
|||
|
|||
@Override |
|||
public void onClick(final View v) { |
|||
if (v == timeSettingsBinding.btnConfirm) { |
|||
final String formatSelection; |
|||
|
|||
final boolean isCustomFormat = timeSettingsBinding.cbCustomFormat.isChecked(); |
|||
|
|||
if (isCustomFormat) { |
|||
//noinspection ConstantConditions |
|||
formatSelection = timeSettingsBinding.etCustomFormat.getText().toString(); |
|||
settingsHelper.putString(Constants.CUSTOM_DATE_TIME_FORMAT, formatSelection); |
|||
} else { |
|||
formatSelection = timeSettingsBinding.spTimeFormat.getSelectedItemPosition() + ";" |
|||
+ timeSettingsBinding.spSeparator.getSelectedItemPosition() + ';' |
|||
+ timeSettingsBinding.spDateFormat.getSelectedItemPosition(); // time;separator;date |
|||
|
|||
settingsHelper.putString(Constants.DATE_TIME_FORMAT, selectedFormat); |
|||
settingsHelper.putString(Constants.DATE_TIME_SELECTION, formatSelection); |
|||
} |
|||
|
|||
settingsHelper.putBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED, isCustomFormat); |
|||
|
|||
Utils.datetimeParser = (SimpleDateFormat) currentFormat.clone(); |
|||
dismiss(); |
|||
} else if (v == timeSettingsBinding.btnInfo) { |
|||
timeSettingsBinding.customPanel.setVisibility(timeSettingsBinding.customPanel |
|||
.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); |
|||
|
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void onNothingSelected(final AdapterView<?> parent) { } |
|||
|
|||
@Override |
|||
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) { } |
|||
|
|||
@Override |
|||
public void afterTextChanged(final Editable s) { } |
|||
} |
@ -0,0 +1,153 @@ |
|||
package awais.instagrabber.directdownload; |
|||
|
|||
import android.Manifest; |
|||
import android.app.Activity; |
|||
import android.app.Notification; |
|||
import android.app.NotificationChannel; |
|||
import android.app.NotificationManager; |
|||
import android.content.Context; |
|||
import android.content.Intent; |
|||
import android.content.pm.PackageManager; |
|||
import android.content.res.Resources; |
|||
import android.net.Uri; |
|||
import android.os.AsyncTask; |
|||
import android.os.Build; |
|||
import android.os.Bundle; |
|||
import android.os.Handler; |
|||
import android.os.Looper; |
|||
import android.view.WindowManager; |
|||
import android.widget.Toast; |
|||
|
|||
import androidx.core.app.ActivityCompat; |
|||
import androidx.core.app.NotificationCompat; |
|||
import androidx.core.app.NotificationManagerCompat; |
|||
import androidx.core.content.ContextCompat; |
|||
|
|||
import java.util.Arrays; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.asyncs.PostFetcher; |
|||
import awais.instagrabber.interfaces.FetchListener; |
|||
import awais.instagrabber.models.IntentModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
import awais.instagrabber.models.enums.IntentModelType; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
|
|||
import static awais.instagrabber.utils.Utils.CHANNEL_ID; |
|||
import static awais.instagrabber.utils.Utils.CHANNEL_NAME; |
|||
import static awais.instagrabber.utils.Utils.isChannelCreated; |
|||
import static awais.instagrabber.utils.Utils.notificationManager; |
|||
|
|||
public final class DirectDownload extends Activity { |
|||
private boolean isFound = false; |
|||
private Intent intent; |
|||
private Context context; |
|||
|
|||
@Override |
|||
public void onWindowAttributesChanged(final WindowManager.LayoutParams params) { |
|||
super.onWindowAttributesChanged(params); |
|||
if (!isFound) { |
|||
intent = getIntent(); |
|||
context = getApplicationContext(); |
|||
if (intent != null && context != null) { |
|||
isFound = true; |
|||
checkIntent(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public Resources getResources() { |
|||
if (!isFound) { |
|||
intent = getIntent(); |
|||
context = getApplicationContext(); |
|||
if (intent != null && context != null) { |
|||
isFound = true; |
|||
checkIntent(); |
|||
} |
|||
} |
|||
return super.getResources(); |
|||
} |
|||
|
|||
private synchronized void checkIntent() { |
|||
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) |
|||
doDownload(); |
|||
else { |
|||
final Handler handler = new Handler(Looper.getMainLooper()); |
|||
handler.post(new Runnable() { |
|||
@Override |
|||
public void run() { |
|||
Toast.makeText(context, R.string.direct_download_perms_ask, Toast.LENGTH_LONG).show(); |
|||
handler.removeCallbacks(this); |
|||
} |
|||
}); |
|||
ActivityCompat.requestPermissions(this, Utils.PERMS, 8020); |
|||
} |
|||
finish(); |
|||
} |
|||
|
|||
private synchronized void doDownload() { |
|||
final String action = intent.getAction(); |
|||
if (!Utils.isEmpty(action) && !Intent.ACTION_MAIN.equals(action)) { |
|||
boolean error = true; |
|||
|
|||
String data = null; |
|||
final Bundle extras = intent.getExtras(); |
|||
if (extras != null) { |
|||
final Object extraData = extras.get(Intent.EXTRA_TEXT); |
|||
if (extraData != null) { |
|||
error = false; |
|||
data = extraData.toString(); |
|||
} |
|||
} |
|||
|
|||
if (error) { |
|||
final Uri intentData = intent.getData(); |
|||
if (intentData != null) data = intentData.toString(); |
|||
} |
|||
|
|||
if (data != null && !Utils.isEmpty(data)) { |
|||
final IntentModel model = Utils.stripString(data); |
|||
if (model != null && model.getType() == IntentModelType.POST) { |
|||
final String text = model.getText(); |
|||
|
|||
new PostFetcher(text, new FetchListener<ViewerPostModel[]>() { |
|||
@Override |
|||
public void doBefore() { |
|||
if (notificationManager == null) |
|||
notificationManager = NotificationManagerCompat.from(context.getApplicationContext()); |
|||
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isChannelCreated) { |
|||
notificationManager.createNotificationChannel(new NotificationChannel(CHANNEL_ID, |
|||
CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)); |
|||
isChannelCreated = true; |
|||
} |
|||
final Notification fetchingPostNotif = new NotificationCompat.Builder(context, CHANNEL_ID) |
|||
.setCategory(NotificationCompat.CATEGORY_STATUS).setSmallIcon(R.mipmap.ic_launcher) |
|||
.setAutoCancel(false).setPriority(NotificationCompat.PRIORITY_MIN) |
|||
.setContentText(context.getString(R.string.direct_download_loading)).build(); |
|||
notificationManager.notify(1900000000, fetchingPostNotif); |
|||
} |
|||
|
|||
@Override |
|||
public void onResult(final ViewerPostModel[] result) { |
|||
if (notificationManager != null) notificationManager.cancel(1900000000); |
|||
if (result != null) { |
|||
if (result.length == 1) { |
|||
Utils.batchDownload(context, result[0].getUsername(), DownloadMethod.DOWNLOAD_DIRECT, |
|||
Arrays.asList(result)); |
|||
} else if (result.length > 1) { |
|||
context.startActivity(new Intent(context, MultiDirectDialog.class) |
|||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) |
|||
.putExtra(Constants.EXTRAS_POST, result)); |
|||
} |
|||
} |
|||
} |
|||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,117 @@ |
|||
package awais.instagrabber.directdownload; |
|||
|
|||
import android.content.Intent; |
|||
import android.os.Bundle; |
|||
import android.view.Menu; |
|||
import android.view.MenuItem; |
|||
|
|||
import androidx.annotation.NonNull; |
|||
import androidx.annotation.Nullable; |
|||
import androidx.appcompat.widget.Toolbar; |
|||
import androidx.recyclerview.widget.RecyclerView; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
|
|||
import awais.instagrabber.R; |
|||
import awais.instagrabber.activities.BaseLanguageActivity; |
|||
import awais.instagrabber.adapters.PostsAdapter; |
|||
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager; |
|||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; |
|||
import awais.instagrabber.models.BasePostModel; |
|||
import awais.instagrabber.models.PostModel; |
|||
import awais.instagrabber.models.ViewerPostModel; |
|||
import awais.instagrabber.utils.Constants; |
|||
import awais.instagrabber.utils.Utils; |
|||
import awais.instagrabber.models.enums.DownloadMethod; |
|||
|
|||
public final class MultiDirectDialog extends BaseLanguageActivity { |
|||
public final ArrayList<BasePostModel> selectedItems = new ArrayList<>(); |
|||
private PostsAdapter postsAdapter; |
|||
private MenuItem btnDownload; |
|||
private String username = null; |
|||
|
|||
@Override |
|||
protected void onCreate(@Nullable final Bundle savedInstanceState) { |
|||
super.onCreate(savedInstanceState); |
|||
setContentView(R.layout.dialog_direct); |
|||
|
|||
final Toolbar toolbar = findViewById(R.id.toolbar); |
|||
setSupportActionBar(toolbar); |
|||
|
|||
final ViewerPostModel[] postModels; |
|||
final Intent intent = getIntent(); |
|||
if (intent == null || !intent.hasExtra(Constants.EXTRAS_POST) |
|||
|| (postModels = (ViewerPostModel[]) intent.getSerializableExtra(Constants.EXTRAS_POST)) == null) { |
|||
Utils.errorFinish(this); |
|||
return; |
|||
} |
|||
|
|||
username = postModels[0].getUsername(); |
|||
toolbar.setTitle(username); |
|||
toolbar.setSubtitle(postModels[0].getShortCode()); |
|||
|
|||
final RecyclerView recyclerView = findViewById(R.id.mainPosts); |
|||
recyclerView.setNestedScrollingEnabled(false); |
|||
recyclerView.setLayoutManager(new GridAutofitLayoutManager(this, Utils.convertDpToPx(130))); |
|||
recyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); |
|||
|
|||
final ArrayList<PostModel> models = new ArrayList<>(postModels.length - 1); |
|||
for (final ViewerPostModel postModel : postModels) |
|||
models.add(new PostModel(postModel.getItemType(), postModel.getPostId(), postModel.getDisplayUrl(), |
|||
postModel.getSliderDisplayUrl(), postModel.getShortCode(), postModel.getPostCaption(), postModel.getTimestamp())); |
|||
|
|||
postsAdapter = new PostsAdapter(models, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof PostModel) { |
|||
final PostModel postModel = (PostModel) tag; |
|||
if (postsAdapter.isSelecting) toggleSelection(postModel); |
|||
else { |
|||
Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(postModel)); |
|||
finish(); |
|||
} |
|||
} |
|||
}, v -> { |
|||
final Object tag = v.getTag(); |
|||
if (tag instanceof PostModel) { |
|||
postsAdapter.isSelecting = true; |
|||
toggleSelection((PostModel) tag); |
|||
} |
|||
return true; |
|||
}); |
|||
|
|||
recyclerView.setAdapter(postsAdapter); |
|||
} |
|||
|
|||
@Override |
|||
public boolean onOptionsItemSelected(@NonNull final MenuItem item) { |
|||
Utils.batchDownload(this, username, DownloadMethod.DOWNLOAD_DIRECT, selectedItems); |
|||
finish(); |
|||
return true; |
|||
} |
|||
|
|||
@Override |
|||
public boolean onCreateOptionsMenu(final Menu menu) { |
|||
getMenuInflater().inflate(R.menu.menu, menu); |
|||
btnDownload = menu.findItem(R.id.action_download); |
|||
menu.findItem(R.id.action_search).setVisible(false); |
|||
return true; |
|||
} |
|||
|
|||
private void toggleSelection(final PostModel postModel) { |
|||
if (postModel != null && postsAdapter != null) { |
|||
if (postModel.isSelected()) selectedItems.remove(postModel); |
|||
else selectedItems.add(postModel); |
|||
postModel.setSelected(!postModel.isSelected()); |
|||
notifyAdapter(postModel); |
|||
} |
|||
} |
|||
|
|||
private void notifyAdapter(final PostModel postModel) { |
|||
if (selectedItems.size() < 1) postsAdapter.isSelecting = false; |
|||
if (postModel.getPosition() < 0) postsAdapter.notifyDataSetChanged(); |
|||
else postsAdapter.notifyItemChanged(postModel.getPosition(), postModel); |
|||
|
|||
if (btnDownload != null) btnDownload.setVisible(postsAdapter.isSelecting); |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
package awais.instagrabber.interfaces; |
|||
|
|||
public interface FetchListener<T> { |
|||
void onResult(T result); |
|||
default void doBefore() { } |
|||
} |
@ -0,0 +1,11 @@ |
|||
package awais.instagrabber.interfaces; |
|||
|
|||
import java.io.Serializable; |
|||
import java.util.List; |
|||
|
|||
import awais.instagrabber.models.BasePostModel; |
|||
import awais.instagrabber.models.enums.ItemGetType; |
|||
|
|||
public interface ItemGetter extends Serializable { |
|||
List<? extends BasePostModel> get(final ItemGetType itemGetType); |
|||
} |
@ -0,0 +1,5 @@ |
|||
package awais.instagrabber.interfaces; |
|||
|
|||
public interface LazyLoadListener { |
|||
void onLoadMore(final int page, final int totalItemsCount); |
|||
} |
@ -0,0 +1,7 @@ |
|||
package awais.instagrabber.interfaces; |
|||
|
|||
import awais.instagrabber.customviews.RamboTextView; |
|||
|
|||
public interface MentionClickListener { |
|||
void onClick(final RamboTextView view, final String text, final boolean isHashtag); |
|||
} |
@ -0,0 +1,5 @@ |
|||
package awais.instagrabber.interfaces; |
|||
|
|||
public interface OnGroupClickListener { |
|||
void toggleGroup(final int flatPos); |
|||
} |
@ -0,0 +1,5 @@ |
|||
package awais.instagrabber.interfaces; |
|||
|
|||
public interface SwipeEvent { |
|||
void onSwipe(final boolean isRight); |
|||
} |
Some files were not shown because too many files changed in this diff
Write
Preview
Loading…
Cancel
Save
Reference in new issue