mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-22 13:06:58 +00:00
init
This commit is contained in:
commit
13beabf741
16
.gitignore
vendored
Executable file
16
.gitignore
vendored
Executable file
@ -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
|
54
.gitlab-ci.yml
Executable file
54
.gitlab-ci.yml
Executable file
@ -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/
|
1
.idea/.name
Normal file
1
.idea/.name
Normal file
@ -0,0 +1 @@
|
||||
InstaGrabber
|
119
.idea/codeStyles
Executable file
119
.idea/codeStyles
Executable file
@ -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>
|
21
.idea/gradle.xml
Executable file
21
.idea/gradle.xml
Executable file
@ -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>
|
7
.idea/inspectionProfiles/profiles_settings.xml
Executable file
7
.idea/inspectionProfiles/profiles_settings.xml
Executable file
@ -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>
|
35
.idea/jarRepositories.xml
Executable file
35
.idea/jarRepositories.xml
Executable file
@ -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>
|
49
.idea/misc.xml
Executable file
49
.idea/misc.xml
Executable file
@ -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>
|
6
.idea/render.experimental.xml
Executable file
6
.idea/render.experimental.xml
Executable file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RenderSettings">
|
||||
<option name="showDecorations" value="true" />
|
||||
</component>
|
||||
</project>
|
12
.idea/runConfigurations.xml
Executable file
12
.idea/runConfigurations.xml
Executable file
@ -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>
|
53
.idea/runConfigurations/app.xml
Executable file
53
.idea/runConfigurations/app.xml
Executable file
@ -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>
|
6
.idea/vcs.xml
Executable file
6
.idea/vcs.xml
Executable file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
193
CHANGELOG
Executable file
193
CHANGELOG
Executable file
@ -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
|
674
LICENSE
Executable file
674
LICENSE
Executable file
@ -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>.
|
5
README.md
Executable file
5
README.md
Executable file
@ -0,0 +1,5 @@
|
||||
![InstaGrabber](./app/src/main/res/mipmap-hdpi/ic_launcher.png "InstaGrabber") InstaGrabber
|
||||
|
||||
Revived Version by Austin.
|
||||
|
||||
Coming soon™
|
1
app/.gitignore
vendored
Executable file
1
app/.gitignore
vendored
Executable file
@ -0,0 +1 @@
|
||||
/build
|
50
app/build.gradle
Executable file
50
app/build.gradle
Executable file
@ -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 }
|
||||
}
|
29
app/lint.xml
Executable file
29
app/lint.xml
Executable file
@ -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>
|
BIN
app/play_icon.png
Executable file
BIN
app/play_icon.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
21
app/proguard-rules.pro
vendored
Executable file
21
app/proguard-rules.pro
vendored
Executable file
@ -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
|
218
app/src/main/AndroidManifest.xml
Executable file
218
app/src/main/AndroidManifest.xml
Executable file
@ -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>
|
BIN
app/src/main/ic_launcher-playstore.png
Executable file
BIN
app/src/main/ic_launcher-playstore.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 70 KiB |
66
app/src/main/java/awais/instagrabber/InstaApp.java
Executable file
66
app/src/main/java/awais/instagrabber/InstaApp.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
864
app/src/main/java/awais/instagrabber/MainHelper.java
Executable file
864
app/src/main/java/awais/instagrabber/MainHelper.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
11
app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.java
Executable file
11
app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
146
app/src/main/java/awais/instagrabber/activities/CommentsViewer.java
Executable file
146
app/src/main/java/awais/instagrabber/activities/CommentsViewer.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
113
app/src/main/java/awais/instagrabber/activities/DirectMessages.java
Executable file
113
app/src/main/java/awais/instagrabber/activities/DirectMessages.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
101
app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
Executable file
101
app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
348
app/src/main/java/awais/instagrabber/activities/FollowViewer.java
Executable file
348
app/src/main/java/awais/instagrabber/activities/FollowViewer.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
130
app/src/main/java/awais/instagrabber/activities/Login.java
Executable file
130
app/src/main/java/awais/instagrabber/activities/Login.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
480
app/src/main/java/awais/instagrabber/activities/Main.java
Executable file
480
app/src/main/java/awais/instagrabber/activities/Main.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
639
app/src/main/java/awais/instagrabber/activities/PostViewer.java
Executable file
639
app/src/main/java/awais/instagrabber/activities/PostViewer.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
215
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
Executable file
215
app/src/main/java/awais/instagrabber/activities/ProfileViewer.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
354
app/src/main/java/awais/instagrabber/activities/StoryViewer.java
Executable file
354
app/src/main/java/awais/instagrabber/activities/StoryViewer.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
136
app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java
Executable file
136
app/src/main/java/awais/instagrabber/adapters/CommentsAdapter.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
116
app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java
Executable file
116
app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
87
app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
Executable file
87
app/src/main/java/awais/instagrabber/adapters/DiscoverAdapter.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
486
app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
Executable file
486
app/src/main/java/awais/instagrabber/adapters/FeedAdapter.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
57
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
Executable file
57
app/src/main/java/awais/instagrabber/adapters/FeedStoriesAdapter.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
144
app/src/main/java/awais/instagrabber/adapters/FollowAdapter.java
Executable file
144
app/src/main/java/awais/instagrabber/adapters/FollowAdapter.java
Executable file
@ -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)];
|
||||
}
|
||||
}
|
53
app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
Executable file
53
app/src/main/java/awais/instagrabber/adapters/HighlightsAdapter.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
354
app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java
Executable file
354
app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
92
app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java
Executable file
92
app/src/main/java/awais/instagrabber/adapters/PostsAdapter.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
68
app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
Executable file
68
app/src/main/java/awais/instagrabber/adapters/PostsMediaAdapter.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
75
app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java
Executable file
75
app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
84
app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java
Executable file
84
app/src/main/java/awais/instagrabber/adapters/StoriesAdapter.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
60
app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
Executable file
60
app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
23
app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
Executable file
23
app/src/main/java/awais/instagrabber/adapters/viewholder/PostViewHolder.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
265
app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java
Executable file
265
app/src/main/java/awais/instagrabber/asyncs/CommentsFetcher.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
194
app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java
Executable file
194
app/src/main/java/awais/instagrabber/asyncs/DiscoverFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
248
app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
Executable file
248
app/src/main/java/awais/instagrabber/asyncs/DownloadAsync.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
194
app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
Executable file
194
app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
103
app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java
Executable file
103
app/src/main/java/awais/instagrabber/asyncs/FeedStoriesFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
101
app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java
Executable file
101
app/src/main/java/awais/instagrabber/asyncs/FollowFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
87
app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java
Executable file
87
app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
146
app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
Executable file
146
app/src/main/java/awais/instagrabber/asyncs/PostFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
134
app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
Executable file
134
app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
83
app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
Executable file
83
app/src/main/java/awais/instagrabber/asyncs/ProfileFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
120
app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java
Executable file
120
app/src/main/java/awais/instagrabber/asyncs/ProfilePictureFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
102
app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java
Executable file
102
app/src/main/java/awais/instagrabber/asyncs/StoryStatusFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
98
app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
Executable file
98
app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
54
app/src/main/java/awais/instagrabber/asyncs/UsernameFetcher.java
Executable file
54
app/src/main/java/awais/instagrabber/asyncs/UsernameFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
100
app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
Executable file
100
app/src/main/java/awais/instagrabber/asyncs/direct_messages/InboxFetcher.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
105
app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
Executable file
105
app/src/main/java/awais/instagrabber/customviews/CircularImageView.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
25
app/src/main/java/awais/instagrabber/customviews/FixedImageView.java
Executable file
25
app/src/main/java/awais/instagrabber/customviews/FixedImageView.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
986
app/src/main/java/awais/instagrabber/customviews/MouseDrawer.java
Executable file
986
app/src/main/java/awais/instagrabber/customviews/MouseDrawer.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
179
app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
Executable file
179
app/src/main/java/awais/instagrabber/customviews/RamboTextView.java
Executable file
@ -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;
|
||||
}
|
||||
}
|
182
app/src/main/java/awais/instagrabber/customviews/RemixDrawerLayout.java
Executable file
182
app/src/main/java/awais/instagrabber/customviews/RemixDrawerLayout.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
98
app/src/main/java/awais/instagrabber/dialogs/AboutDialog.java
Executable file
98
app/src/main/java/awais/instagrabber/dialogs/AboutDialog.java
Executable file
@ -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:chapter50000@hotmail.com"), 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;
|
||||
}
|
||||
}
|
62
app/src/main/java/awais/instagrabber/dialogs/ProfileSettingsDialog.java
Executable file
62
app/src/main/java/awais/instagrabber/dialogs/ProfileSettingsDialog.java
Executable file
@ -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) { }
|
||||
}
|
169
app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
Executable file
169
app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java
Executable file
@ -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();
|
||||
}
|
||||
}
|
213
app/src/main/java/awais/instagrabber/dialogs/SettingsDialog.java
Executable file
213
app/src/main/java/awais/instagrabber/dialogs/SettingsDialog.java
Executable file
@ -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) { }
|
||||
}
|
173
app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java
Executable file
173
app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java
Executable file
@ -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) { }
|
||||
}
|
153
app/src/main/java/awais/instagrabber/directdownload/DirectDownload.java
Executable file
153
app/src/main/java/awais/instagrabber/directdownload/DirectDownload.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
117
app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
Executable file
117
app/src/main/java/awais/instagrabber/directdownload/MultiDirectDialog.java
Executable file
@ -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);
|
||||
}
|
||||
}
|
6
app/src/main/java/awais/instagrabber/interfaces/FetchListener.java
Executable file
6
app/src/main/java/awais/instagrabber/interfaces/FetchListener.java
Executable file
@ -0,0 +1,6 @@
|
||||
package awais.instagrabber.interfaces;
|
||||
|
||||
public interface FetchListener<T> {
|
||||
void onResult(T result);
|
||||
default void doBefore() { }
|
||||
}
|
11
app/src/main/java/awais/instagrabber/interfaces/ItemGetter.java
Executable file
11
app/src/main/java/awais/instagrabber/interfaces/ItemGetter.java
Executable file
@ -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);
|
||||
}
|
5
app/src/main/java/awais/instagrabber/interfaces/LazyLoadListener.java
Executable file
5
app/src/main/java/awais/instagrabber/interfaces/LazyLoadListener.java
Executable file
@ -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);
|
||||
}
|
5
app/src/main/java/awais/instagrabber/interfaces/SwipeEvent.java
Executable file
5
app/src/main/java/awais/instagrabber/interfaces/SwipeEvent.java
Executable file
@ -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 have changed in this diff Show More
Loading…
Reference in New Issue
Block a user