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