mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-25 16:17:29 +00:00
Merge branch 'master' into pr/934
This commit is contained in:
commit
f961c422ca
@ -79,6 +79,25 @@
|
|||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"login": "stamatiap",
|
||||||
|
"name": "Stamatia Papageorgiou",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/57223967?v=4",
|
||||||
|
"profile": "https://github.com/stamatiap",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "The-EDev",
|
||||||
|
"name": "Farook Al-Sammarraie",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/60552923?v=4",
|
||||||
|
"profile": "https://github.com/The-EDev",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"login": "Zopieux",
|
"login": "Zopieux",
|
||||||
"name": "Alexandre Macabies",
|
"name": "Alexandre Macabies",
|
||||||
|
5
.codebeatsettings
Normal file
5
.codebeatsettings
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"JAVA": {
|
||||||
|
"TOO_MANY_IVARS": [8, 10, 20, 30]
|
||||||
|
}
|
||||||
|
}
|
1
.github/ISSUE_TEMPLATE/ban_report.md
vendored
1
.github/ISSUE_TEMPLATE/ban_report.md
vendored
@ -14,6 +14,7 @@ assignees: 'austinhuang0131'
|
|||||||
- [ ] My app is on the latest version available on GitHub or F-Droid. I understand that any other version is not supported.
|
- [ ] My app is on the latest version available on GitHub or F-Droid. I understand that any other version is not supported.
|
||||||
- [ ] I certify that all actions I have performed on the app constitute human behaviour. I am using the app responsibly and in a way identical to using the official app. Specifically, I did not perform botting or automated key clicks.
|
- [ ] I certify that all actions I have performed on the app constitute human behaviour. I am using the app responsibly and in a way identical to using the official app. Specifically, I did not perform botting or automated key clicks.
|
||||||
- [ ] I have considered other possible reasons for my ban, and I cannot find any substantial claim other than the usage of this app.
|
- [ ] I have considered other possible reasons for my ban, and I cannot find any substantial claim other than the usage of this app.
|
||||||
|
- [ ] Instagram has indicated that this is a permanent ban, not a required password change or a temporary lock.
|
||||||
|
|
||||||
## Answer honestly. Check accordingly to your situation.
|
## Answer honestly. Check accordingly to your situation.
|
||||||
|
|
||||||
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -6,9 +6,9 @@ labels: bug
|
|||||||
assignees: ''
|
assignees: ''
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- If you choose not to follow this format, you should erase the entire body. -->
|
<!-- Frequent reporters can ignore this template as long as it's clear and concise. -->
|
||||||
|
|
||||||
- [ ] My app is on the latest version. I understand that any other version is not supported.
|
- [ ] My app is *at least* at the current release version. I understand that any versions before that is not supported.
|
||||||
- [ ] I have read [the FAQ](https://barinsta.austinhuang.me/en/latest/faq).
|
- [ ] I have read [the FAQ](https://barinsta.austinhuang.me/en/latest/faq).
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@ -30,4 +30,4 @@ Describe the bug here. Don't stress too much, but do include the key points.
|
|||||||
|
|
||||||
## Additional info
|
## Additional info
|
||||||
|
|
||||||
<!-- If you have log (and it's working), upload it here. -->
|
<!-- If you have a crash dump, paste it here. -->
|
||||||
|
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Community Chatrooms
|
||||||
|
url: https://barinsta.austinhuang.me/en/latest/chat/
|
||||||
|
about: Chat with developers and users alike!
|
||||||
|
- name: /r/Barinsta
|
||||||
|
url: https://reddit.com/r/barinsta
|
||||||
|
about: Start a discussion on our subreddit!
|
||||||
|
- name: Repository Discussions
|
||||||
|
url: https://github.com/austinhuang0131/barinsta/discussions
|
||||||
|
about: Start a discussion in this repo!
|
16
.github/ISSUE_TEMPLATE/questions.md
vendored
16
.github/ISSUE_TEMPLATE/questions.md
vendored
@ -1,16 +0,0 @@
|
|||||||
---
|
|
||||||
name: General questions & feedback
|
|
||||||
about: These should be submitted to either one of our chatrooms, the discussions, or r/barinsta on reddit.
|
|
||||||
title: "[Q]"
|
|
||||||
labels: question
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
STOP!!! STOP!!! STOP!!!
|
|
||||||
|
|
||||||
General questions & feedback should be submitted to
|
|
||||||
* one of our chatrooms (see README.md), or
|
|
||||||
* the GitHub discussions section, or
|
|
||||||
* r/barinsta on reddit.
|
|
||||||
|
|
||||||
STOP!!! STOP!!! STOP!!!
|
|
15
.github/workflows/github_nightly_release.yml
vendored
15
.github/workflows/github_nightly_release.yml
vendored
@ -15,18 +15,19 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '8'
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build Github unsigned apk
|
- name: Build Github unsigned apk
|
||||||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre
|
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: ammargitham/sign-android-release@v1.1.1
|
||||||
# ID used to access action output
|
# ID used to access action output
|
||||||
id: sign_app
|
id: sign_app
|
||||||
with:
|
with:
|
||||||
@ -45,7 +46,8 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: barinsta_nightly_${{ steps.date.outputs.date }}
|
name: barinsta_nightly_${{ steps.date.outputs.date }}
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
path: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send success notification
|
# Send success notification
|
||||||
- name: Send success Telegram notification
|
- name: Send success Telegram notification
|
||||||
@ -55,7 +57,8 @@ jobs:
|
|||||||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
||||||
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
||||||
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||||
document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
document: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send failure notification
|
# Send failure notification
|
||||||
- name: Send failure Telegram notification
|
- name: Send failure Telegram notification
|
||||||
|
15
.github/workflows/github_pre_release.yml
vendored
15
.github/workflows/github_pre_release.yml
vendored
@ -16,18 +16,19 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '8'
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build Github unsigned pre-release apk
|
- name: Build Github unsigned pre-release apk
|
||||||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre
|
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: ammargitham/sign-android-release@v1.1.1
|
||||||
# ID used to access action output
|
# ID used to access action output
|
||||||
id: sign_app
|
id: sign_app
|
||||||
with:
|
with:
|
||||||
@ -46,7 +47,8 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: barinsta_pre-release_${{ steps.date.outputs.date }}
|
name: barinsta_pre-release_${{ steps.date.outputs.date }}
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
path: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send success notification
|
# Send success notification
|
||||||
- name: Send success Telegram notification
|
- name: Send success Telegram notification
|
||||||
@ -56,7 +58,8 @@ jobs:
|
|||||||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
||||||
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
||||||
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||||
document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
document: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send failure notification
|
# Send failure notification
|
||||||
- name: Send failure Telegram notification
|
- name: Send failure Telegram notification
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,3 +20,5 @@ app/release
|
|||||||
/sentry.properties
|
/sentry.properties
|
||||||
/app/fdroid/
|
/app/fdroid/
|
||||||
/app/github/
|
/app/github/
|
||||||
|
/repo
|
||||||
|
/.fdroid.yml
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="1.8" />
|
<bytecodeTargetLevel target="11" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -7,7 +7,6 @@
|
|||||||
<option name="testRunner" value="PLATFORM" />
|
<option name="testRunner" value="PLATFORM" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="1.8" />
|
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<component name="RunConfigurationProducerService">
|
<component name="RunConfigurationProducerService">
|
||||||
<option name="ignoredProducers">
|
<option name="ignoredProducers">
|
||||||
<set>
|
<set>
|
||||||
|
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
<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.TestClassGradleConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||||
|
42
README.md
42
README.md
@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
|
[![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech)
|
||||||
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
[![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/)
|
||||||
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
|
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://makeapullrequest.com)
|
||||||
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
|
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
|
||||||
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||||
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg)](#contributors)
|
[![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg)](#contributors)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
Instagram client; previously known as InstaGrabber.
|
Instagram client; previously known as InstaGrabber.
|
||||||
@ -27,12 +27,12 @@ Version status: ![F-Droid](https://img.shields.io/f-droid/v/me.austinhuang.insta
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg" alt="Profile" width="15%"/></a>
|
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" alt="Profile" width="15%"/></a>
|
||||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg" alt="Post" width="15%"/></a>
|
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" alt="Post" width="15%"/></a>
|
||||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg" alt="Comments" width="15%"/></a>
|
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" alt="Comments" width="15%"/></a>
|
||||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg" alt="Story" width="15%"/></a>
|
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" alt="Story" width="15%"/></a>
|
||||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg" alt="Hashtag" width="15%"/></a>
|
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" alt="Hashtag" width="15%"/></a>
|
||||||
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg" alt="Discover Topics" width="15%"/></a>
|
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/6.png" alt="Discover Topics" width="15%"/></a>
|
||||||
|
|
||||||
## We need maintainers!
|
## We need maintainers!
|
||||||
|
|
||||||
@ -63,49 +63,53 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/stamatiap"><img src="https://avatars.githubusercontent.com/u/57223967?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stamatia Papageorgiou</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=stamatiap" title="Code">💻</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/The-EDev"><img src="https://avatars.githubusercontent.com/u/60552923?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Farook Al-Sammarraie</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=The-EDev" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td>
|
<td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td>
|
<td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td>
|
||||||
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td align="center"><a href="https://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
|
||||||
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
<td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
<td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -121,7 +125,7 @@ This app's predecessor, InstaGrabber, was originally made by [@AwaisKing](https:
|
|||||||
|
|
||||||
Barinsta
|
Barinsta
|
||||||
Copyright (C) 2020-2021 Austin Huang <im@austinhuang.me>
|
Copyright (C) 2020-2021 Austin Huang <im@austinhuang.me>
|
||||||
Ammar Githam <ammargitham786@gmail.com>
|
Ammar Githam <ammar.githam@outlook.com>
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
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
|
it under the terms of the GNU General Public License as published by
|
||||||
|
126
app/build.gradle
126
app/build.gradle
@ -1,5 +1,7 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: "androidx.navigation.safeargs"
|
apply plugin: "androidx.navigation.safeargs"
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
apply from: 'sentry.gradle'
|
apply from: 'sentry.gradle'
|
||||||
|
|
||||||
def getGitHash = { ->
|
def getGitHash = { ->
|
||||||
@ -20,8 +22,8 @@ android {
|
|||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
|
|
||||||
versionCode 61
|
versionCode 63
|
||||||
versionName '19.2.0'
|
versionName '19.2.2'
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
@ -33,6 +35,12 @@ android {
|
|||||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -76,6 +84,27 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
splits {
|
||||||
|
// Configures multiple APKs based on ABI.
|
||||||
|
abi {
|
||||||
|
// Enables building multiple APKs per ABI.
|
||||||
|
enable project.hasProperty("split") && !gradle.startParameter.taskNames.isEmpty() && gradle.startParameter.taskNames.get(0).contains('Release')
|
||||||
|
|
||||||
|
// By default all ABIs are included, so use reset() and include to specify that we only
|
||||||
|
// want APKs for x86 and x86_64.
|
||||||
|
|
||||||
|
// Resets the list of ABIs that Gradle should create APKs for to none.
|
||||||
|
reset()
|
||||||
|
|
||||||
|
// Specifies a list of ABIs that Gradle should create APKs for.
|
||||||
|
include "x86", "x86_64", "arm64-v8a", "armeabi-v7a"
|
||||||
|
|
||||||
|
// Specifies that we want to also generate a universal APK that includes all ABIs.
|
||||||
|
universalApk true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
android.applicationVariants.all { variant ->
|
android.applicationVariants.all { variant ->
|
||||||
if (variant.flavorName != "github") return
|
if (variant.flavorName != "github") return
|
||||||
variant.outputs.all { output ->
|
variant.outputs.all { output ->
|
||||||
@ -84,17 +113,47 @@ android {
|
|||||||
// def versionCode = variant.versionCode
|
// def versionCode = variant.versionCode
|
||||||
def flavor = variant.flavorName
|
def flavor = variant.flavorName
|
||||||
|
|
||||||
def suffix = "${versionName}-${flavor}_${builtType}" // eg. 19.1.0-github_debug or release
|
def flavorBuiltType = "${flavor}_${builtType}"
|
||||||
|
def suffix
|
||||||
|
// For x86 and x86_64, the versionNames are already overridden
|
||||||
|
if (versionName.contains(flavorBuiltType)) {
|
||||||
|
suffix = "${versionName}"
|
||||||
|
} else {
|
||||||
|
suffix = "${versionName}-${flavorBuiltType}" // eg. 19.1.0-github_debug or release
|
||||||
|
}
|
||||||
if (builtType.toString() == 'release' && project.hasProperty("pre")) {
|
if (builtType.toString() == 'release' && project.hasProperty("pre")) {
|
||||||
buildConfigField("boolean", "isPre", "true")
|
buildConfigField("boolean", "isPre", "true")
|
||||||
|
|
||||||
|
flavorBuiltType = "${getGitHash()}-${flavor}"
|
||||||
|
|
||||||
|
// For x86 and x86_64, the versionNames are already overridden
|
||||||
|
if (versionName.contains(flavorBuiltType)) {
|
||||||
|
suffix = "${versionName}"
|
||||||
|
} else {
|
||||||
// append latest commit short hash for pre-release
|
// append latest commit short hash for pre-release
|
||||||
suffix = "${versionName}.${getGitHash()}-${flavor}" // eg. 19.1.0.b123456-github
|
suffix = "${versionName}.${flavorBuiltType}" // eg. 19.1.0.b123456-github
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output.versionNameOverride = suffix
|
output.versionNameOverride = suffix
|
||||||
outputFileName = "barinsta_${suffix}.apk"
|
def abi = output.getFilter(com.android.build.OutputFile.ABI)
|
||||||
|
// println(abi + ", " + versionName + ", " + flavor + ", " + builtType + ", " + suffix)
|
||||||
|
outputFileName = abi == null ? "barinsta_${suffix}.apk" : "barinsta_${suffix}_${abi}.apk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
// Exclude file to avoid
|
||||||
|
// Error: Duplicate files during packaging of APK
|
||||||
|
exclude 'META-INF/LICENSE.md'
|
||||||
|
exclude 'META-INF/LICENSE-notice.md'
|
||||||
|
exclude 'META-INF/atomicfu.kotlin_module'
|
||||||
|
}
|
||||||
|
|
||||||
|
testOptions.unitTests {
|
||||||
|
includeAndroidResources = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
@ -104,49 +163,61 @@ configurations.all {
|
|||||||
dependencies {
|
dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
def appcompat_version = "1.2.0"
|
def nav_version = '2.3.5'
|
||||||
def nav_version = '2.3.4'
|
def exoplayer_version = '2.13.3'
|
||||||
def exoplayer_version = '2.13.2'
|
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.4.0-alpha02'
|
implementation 'com.google.android.material:material:1.4.0-beta01'
|
||||||
|
|
||||||
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
|
implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version"
|
||||||
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
|
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
|
||||||
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
||||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
|
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||||
implementation "androidx.preference:preference:1.1.1"
|
implementation "androidx.preference:preference:1.1.1"
|
||||||
implementation "androidx.work:work-runtime:2.5.0"
|
|
||||||
implementation 'androidx.palette:palette:1.0.0'
|
implementation 'androidx.palette:palette:1.0.0'
|
||||||
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
|
||||||
|
|
||||||
implementation 'com.google.guava:guava:27.0.1-android'
|
implementation 'com.google.guava:guava:27.0.1-android'
|
||||||
|
|
||||||
|
def core_version = "1.6.0-beta01"
|
||||||
|
implementation "androidx.core:core:$core_version"
|
||||||
|
|
||||||
|
// Fragment
|
||||||
|
implementation "androidx.fragment:fragment-ktx:1.3.4"
|
||||||
|
|
||||||
|
// Lifecycle
|
||||||
|
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
|
||||||
|
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
|
||||||
|
|
||||||
// Room
|
// Room
|
||||||
def room_version = "2.2.6"
|
def room_version = "2.3.0"
|
||||||
implementation "androidx.room:room-runtime:$room_version"
|
implementation "androidx.room:room-runtime:$room_version"
|
||||||
implementation "androidx.room:room-guava:$room_version"
|
implementation "androidx.room:room-guava:$room_version"
|
||||||
|
implementation "androidx.room:room-ktx:$room_version"
|
||||||
|
kapt "androidx.room:room-compiler:$room_version"
|
||||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
// CameraX
|
// CameraX
|
||||||
def camerax_version = "1.1.0-alpha03"
|
def camerax_version = "1.1.0-alpha04"
|
||||||
implementation "androidx.camera:camera-camera2:$camerax_version"
|
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||||
implementation "androidx.camera:camera-view:1.0.0-alpha22"
|
implementation "androidx.camera:camera-view:1.0.0-alpha24"
|
||||||
|
|
||||||
// EmojiCompat
|
// EmojiCompat
|
||||||
def emoji_compat_version = "1.1.0"
|
def emoji_compat_version = "1.1.0"
|
||||||
implementation "androidx.emoji:emoji:$emoji_compat_version"
|
implementation "androidx.emoji:emoji:$emoji_compat_version"
|
||||||
implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
|
implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
|
||||||
|
|
||||||
implementation 'me.austinhuang:AutoLinkTextViewV2:-SNAPSHOT'
|
// Work
|
||||||
|
def work_version = '2.5.0'
|
||||||
|
implementation "androidx.work:work-runtime:$work_version"
|
||||||
|
implementation "androidx.work:work-runtime-ktx:$work_version"
|
||||||
|
|
||||||
|
implementation "ru.gildor.coroutines:kotlin-coroutines-okhttp:1.0"
|
||||||
|
|
||||||
implementation 'com.facebook.fresco:fresco:2.3.0'
|
implementation 'com.facebook.fresco:fresco:2.3.0'
|
||||||
implementation 'com.facebook.fresco:animated-webp:2.3.0'
|
implementation 'com.facebook.fresco:animated-webp:2.3.0'
|
||||||
@ -157,12 +228,27 @@ dependencies {
|
|||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
|
|
||||||
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
|
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
|
||||||
implementation 'com.github.ammargitham:uCrop:2.3-beta'
|
|
||||||
|
implementation 'com.github.skydoves:balloon:1.3.4'
|
||||||
|
|
||||||
|
implementation 'com.github.ammargitham:AutoLinkTextViewV2:3.2.0'
|
||||||
|
implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2'
|
||||||
implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4'
|
implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4'
|
||||||
|
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||||
|
|
||||||
githubImplementation 'io.sentry:sentry-android:4.3.0'
|
githubImplementation 'io.sentry:sentry-android:4.3.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
|
||||||
|
testImplementation "androidx.test.ext:junit-ktx:1.1.2"
|
||||||
|
testImplementation "androidx.test:core-ktx:1.3.0"
|
||||||
|
testImplementation "androidx.arch.core:core-testing:2.1.0"
|
||||||
|
testImplementation "org.robolectric:robolectric:4.5.1"
|
||||||
|
|
||||||
|
androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.2'
|
||||||
|
androidTestImplementation 'androidx.test:core:1.3.0'
|
||||||
|
androidTestImplementation 'com.android.support:support-annotations:28.0.0'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation "androidx.room:room-testing:2.3.0"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
227
app/schemas/awais.instagrabber.db.AppDatabase/6.json
Normal file
227
app/schemas/awais.instagrabber.db.AppDatabase/6.json
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "232e618b3bfcb4661336b359d036c455",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "accounts",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "cookie",
|
||||||
|
"columnName": "cookie",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "fullName",
|
||||||
|
"columnName": "full_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "profilePic",
|
||||||
|
"columnName": "profile_pic",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorites",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "query",
|
||||||
|
"columnName": "query_text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "displayName",
|
||||||
|
"columnName": "display_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "picUrl",
|
||||||
|
"columnName": "pic_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dm_last_notified",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "threadId",
|
||||||
|
"columnName": "thread_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastNotifiedMsgTs",
|
||||||
|
"columnName": "last_notified_msg_ts",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastNotifiedAt",
|
||||||
|
"columnName": "last_notified_at",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_dm_last_notified_thread_id",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"thread_id"
|
||||||
|
],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_searches",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ig_id` TEXT NOT NULL, `name` TEXT NOT NULL, `username` TEXT, `pic_url` TEXT, `type` TEXT NOT NULL, `last_searched_on` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "igId",
|
||||||
|
"columnName": "ig_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "picUrl",
|
||||||
|
"columnName": "pic_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSearchedOn",
|
||||||
|
"columnName": "last_searched_on",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_recent_searches_ig_id_type",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"ig_id",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `${TABLE_NAME}` (`ig_id`, `type`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '232e618b3bfcb4661336b359d036c455')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package awais.instagrabber.db;
|
||||||
|
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.room.migration.Migration;
|
||||||
|
import androidx.room.testing.MigrationTestHelper;
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static awais.instagrabber.db.AppDatabase.MIGRATION_4_5;
|
||||||
|
import static awais.instagrabber.db.AppDatabase.MIGRATION_5_6;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class MigrationTest {
|
||||||
|
private static final String TEST_DB = "migration-test";
|
||||||
|
private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_4_5, MIGRATION_5_6};
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public MigrationTestHelper helper;
|
||||||
|
|
||||||
|
public MigrationTest() {
|
||||||
|
final String canonicalName = AppDatabase.class.getCanonicalName();
|
||||||
|
assert canonicalName != null;
|
||||||
|
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
|
canonicalName,
|
||||||
|
new FrameworkSQLiteOpenHelperFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void migrateAll() throws IOException {
|
||||||
|
// Create earliest version of the database. Have to start with 4 since that is the version we migrated to Room.
|
||||||
|
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 4);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
// Open latest version of the database. Room will validate the schema
|
||||||
|
// once all migrations execute.
|
||||||
|
AppDatabase appDb = Room.databaseBuilder(InstrumentationRegistry.getInstrumentation().getTargetContext(),
|
||||||
|
AppDatabase.class,
|
||||||
|
TEST_DB)
|
||||||
|
.addMigrations(ALL_MIGRATIONS).build();
|
||||||
|
appDb.getOpenHelper().getWritableDatabase();
|
||||||
|
appDb.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package awais.instagrabber.db.dao;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.db.AppDatabase;
|
||||||
|
import awais.instagrabber.db.entities.RecentSearch;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class RecentSearchDaoTest {
|
||||||
|
private static final String TAG = RecentSearchDaoTest.class.getSimpleName();
|
||||||
|
|
||||||
|
private RecentSearchDao dao;
|
||||||
|
private AppDatabase db;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createDb() {
|
||||||
|
final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
|
||||||
|
dao = db.recentSearchDao();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void closeDb() {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeQueryDelete() {
|
||||||
|
final RecentSearch recentSearch = insertRecentSearch("1", "test1", FavoriteType.HASHTAG);
|
||||||
|
final RecentSearch byIgIdAndType = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG);
|
||||||
|
Assertions.assertEquals(recentSearch, byIgIdAndType);
|
||||||
|
dao.deleteRecentSearch(byIgIdAndType);
|
||||||
|
final RecentSearch deleted = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG);
|
||||||
|
Assertions.assertNull(deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void queryAllOrdered() {
|
||||||
|
final List<RecentSearch> insertListReversed = ImmutableList
|
||||||
|
.<RecentSearch>builder()
|
||||||
|
.add(insertRecentSearch("1", "test1", FavoriteType.HASHTAG))
|
||||||
|
.add(insertRecentSearch("2", "test2", FavoriteType.LOCATION))
|
||||||
|
.add(insertRecentSearch("3", "test3", FavoriteType.USER))
|
||||||
|
.add(insertRecentSearch("4", "test4", FavoriteType.USER))
|
||||||
|
.add(insertRecentSearch("5", "test5", FavoriteType.USER))
|
||||||
|
.build()
|
||||||
|
.reverse(); // important
|
||||||
|
final List<RecentSearch> fromDb = dao.getAllRecentSearches();
|
||||||
|
Assertions.assertIterableEquals(insertListReversed, fromDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private RecentSearch insertRecentSearch(final String igId, final String name, final FavoriteType type) {
|
||||||
|
final RecentSearch recentSearch = new RecentSearch(
|
||||||
|
igId,
|
||||||
|
name,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
type,
|
||||||
|
LocalDateTime.now()
|
||||||
|
);
|
||||||
|
dao.insertRecentSearch(recentSearch);
|
||||||
|
return recentSearch;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Enable Sentry</string>
|
<string name="enable_sentry">Povolit Sentry</string>
|
||||||
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
<string name="sentry_summary">Sentry je listener/handler, který zaznamenává chyby a asynchronně je posílá na Sentry.io</string>
|
||||||
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
<string name="sentry_start_next_launch">Sentry se spustí při příštím spuštění</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Enable Sentry</string>
|
<string name="enable_sentry">Aktiviere Sentry</string>
|
||||||
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
<string name="sentry_summary">Sentry ist ein Listener/Handler für Fehler, der den Fehler/das Ereignis asynchron an Sentry.io sendet</string>
|
||||||
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
<string name="sentry_start_next_launch">Sentry startet beim nächsten Start</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
6
app/src/github/res/values-ko/strings.xml
Normal file
6
app/src/github/res/values-ko/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Sentry 활성화</string>
|
||||||
|
<string name="sentry_summary">Sentry는 Sentry.io에 오류를 비동기적으로 보내는 오류 처리기입니다</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry는 다음 출시에 시작됩니다</string>
|
||||||
|
</resources>
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Enable Sentry</string>
|
<string name="enable_sentry">Sentry inschakelen</string>
|
||||||
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
<string name="sentry_summary">Sentry is een luister/handler voor fouten die asynchroon de fout/gebeurtenis versturen naar Sentry.io</string>
|
||||||
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
<string name="sentry_start_next_launch">Sentry zal starten bij de volgende lancering</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Włącz Sentry</string>
|
<string name="enable_sentry">Włącz Sentry</string>
|
||||||
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
<string name="sentry_summary">Sentry jest słuchaczem/obsługą błędów, które asynchronicznie wysyłają błąd/zdarzenie do Sentry.io</string>
|
||||||
<string name="sentry_start_next_launch">Sentry rozpocznie się przy następnym uruchomieniu</string>
|
<string name="sentry_start_next_launch">Sentry rozpocznie się przy następnym uruchomieniu</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Включить режим \"часового\"</string>
|
<string name="enable_sentry">Включить Sentry</string>
|
||||||
<string name="sentry_summary">\"Часовой\" - это слушатель/обработчик ошибок, который асинхронно отправляет ошибку/событие на Sentry.io</string>
|
<string name="sentry_summary">Sentry - это обработчик событий, который асинхронно отправляет сообщения об ошибках/поломках на Sentry.io</string>
|
||||||
<string name="sentry_start_next_launch">\"Часовой\" будет запущен при следующем запуске</string>
|
<string name="sentry_start_next_launch">Sentry включится при следующем запуске приложения</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
6
app/src/github/res/values-sv/strings.xml
Normal file
6
app/src/github/res/values-sv/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Enable Sentry</string>
|
<string name="enable_sentry">Bật Sentry</string>
|
||||||
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
<string name="sentry_summary">Sentry là một thiết bị nghe/giải quyết cho những lỗi mà gửi những lỗi/sự kiện đến Sentry.io một cách tách biệt</string>
|
||||||
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
<string name="sentry_start_next_launch">Sentry sẽ được bật vào lần khởi động kế tiếp</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="enable_sentry">Enable Sentry</string>
|
<string name="enable_sentry">啟用 Sentry</string>
|
||||||
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
<string name="sentry_summary">Sentry 將會錯誤報告發送至 Sentry.io</string>
|
||||||
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
<string name="sentry_start_next_launch">下次啟用應用程式時將會開啟 Sentry</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -26,8 +26,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".activities.MainActivity"
|
android:name=".activities.MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=".Main"
|
android:taskAffinity=".Main">
|
||||||
android:windowSoftInputMode="adjustResize">
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
@ -51,13 +50,14 @@
|
|||||||
|
|
||||||
<data android:scheme="http" />
|
<data android:scheme="http" />
|
||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:host="ig.me" />
|
<data android:host="instagr.am" />
|
||||||
<data android:host="www.ig.me" />
|
<data android:host="www.instagr.am" />
|
||||||
<data android:host="instagram.com" />
|
<data android:host="instagram.com" />
|
||||||
<data android:host="www.instagram.com" />
|
<data android:host="www.instagram.com" />
|
||||||
<data android:pathPrefix="/" />
|
<data android:pathPrefix="/" />
|
||||||
<data android:pathPrefix="/p" />
|
<data android:pathPrefix="/p" />
|
||||||
<data android:pathPrefix="/explore/tags" />
|
<data android:pathPrefix="/explore/tags" />
|
||||||
|
<data android:pathPrefix="/explore/locations" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
@ -76,41 +76,6 @@
|
|||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:taskAffinity="awais.instagrabber.errorreport"
|
android:taskAffinity="awais.instagrabber.errorreport"
|
||||||
android:theme="@android:style/Theme.DeviceDefault.Dialog" />
|
android:theme="@android:style/Theme.DeviceDefault.Dialog" />
|
||||||
<activity
|
|
||||||
android:name=".activities.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/Theme.AppCompat.Translucent">
|
|
||||||
<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
|
<activity
|
||||||
android:name=".activities.Login"
|
android:name=".activities.Login"
|
||||||
android:label="@string/login"
|
android:label="@string/login"
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
package awais.instagrabber;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
|
||||||
import com.facebook.imagepipeline.core.ImagePipelineConfig;
|
|
||||||
|
|
||||||
import java.net.CookieHandler;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.LocaleUtils;
|
|
||||||
import awais.instagrabber.utils.SettingsHelper;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
import awaisomereport.CrashReporter;
|
|
||||||
|
|
||||||
import static awais.instagrabber.utils.CookieUtils.NET_COOKIE_MANAGER;
|
|
||||||
import static awais.instagrabber.utils.Utils.applicationHandler;
|
|
||||||
import static awais.instagrabber.utils.Utils.cacheDir;
|
|
||||||
import static awais.instagrabber.utils.Utils.clipboardManager;
|
|
||||||
import static awais.instagrabber.utils.Utils.datetimeParser;
|
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
|
||||||
|
|
||||||
|
|
||||||
public final class InstaGrabberApplication extends Application {
|
|
||||||
private static final String TAG = "InstaGrabberApplication";
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
CookieHandler.setDefault(NET_COOKIE_MANAGER);
|
|
||||||
|
|
||||||
if (settingsHelper == null) {
|
|
||||||
settingsHelper = new SettingsHelper(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BuildConfig.DEBUG) {
|
|
||||||
CrashReporter.get(this).start();
|
|
||||||
}
|
|
||||||
// logCollector = new LogCollector(this);
|
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
|
||||||
try {
|
|
||||||
Class.forName("dalvik.system.CloseGuard")
|
|
||||||
.getMethod("setEnabled", boolean.class)
|
|
||||||
.invoke(null, true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// final Set<RequestListener> requestListeners = new HashSet<>();
|
|
||||||
// requestListeners.add(new RequestLoggingListener());
|
|
||||||
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig
|
|
||||||
.newBuilder(this)
|
|
||||||
// .setMainDiskCacheConfig(diskCacheConfig)
|
|
||||||
// .setRequestListeners(requestListeners)
|
|
||||||
.setDownsampleEnabled(true)
|
|
||||||
.build();
|
|
||||||
Fresco.initialize(this, imagePipelineConfig);
|
|
||||||
// FLog.setMinimumLoggingLevel(FLog.VERBOSE);
|
|
||||||
|
|
||||||
if (applicationHandler == null) {
|
|
||||||
applicationHandler = new Handler(getApplicationContext().getMainLooper());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cacheDir == null) {
|
|
||||||
cacheDir = getCacheDir().getAbsolutePath();
|
|
||||||
}
|
|
||||||
|
|
||||||
LocaleUtils.setLocale(getBaseContext());
|
|
||||||
|
|
||||||
if (clipboardManager == null)
|
|
||||||
clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
|
||||||
|
|
||||||
if (datetimeParser == null)
|
|
||||||
datetimeParser = new SimpleDateFormat(
|
|
||||||
settingsHelper.getBoolean(Constants.CUSTOM_DATE_TIME_FORMAT_ENABLED) ?
|
|
||||||
settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT) :
|
|
||||||
settingsHelper.getString(Constants.DATE_TIME_FORMAT), LocaleUtils.getCurrentLocale());
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) {
|
|
||||||
settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,71 @@
|
|||||||
|
package awais.instagrabber
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.util.Log
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys.CUSTOM_DATE_TIME_FORMAT
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys.CUSTOM_DATE_TIME_FORMAT_ENABLED
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys.DATE_TIME_FORMAT
|
||||||
|
import awais.instagrabber.utils.*
|
||||||
|
import awais.instagrabber.utils.LocaleUtils.currentLocale
|
||||||
|
import awais.instagrabber.utils.Utils.settingsHelper
|
||||||
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
|
import awaisomereport.CrashReporter
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
|
import com.facebook.imagepipeline.core.ImagePipelineConfig
|
||||||
|
import java.net.CookieHandler
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
class InstaGrabberApplication : Application() {
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
CookieHandler.setDefault(NET_COOKIE_MANAGER)
|
||||||
|
settingsHelper = SettingsHelper(this)
|
||||||
|
setupCrashReporter()
|
||||||
|
setupCloseGuard()
|
||||||
|
setupFresco()
|
||||||
|
Utils.cacheDir = cacheDir.absolutePath
|
||||||
|
Utils.clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
LocaleUtils.setLocale(baseContext)
|
||||||
|
val pattern = if (settingsHelper.getBoolean(CUSTOM_DATE_TIME_FORMAT_ENABLED)) {
|
||||||
|
settingsHelper.getString(CUSTOM_DATE_TIME_FORMAT)
|
||||||
|
} else {
|
||||||
|
settingsHelper.getString(DATE_TIME_FORMAT)
|
||||||
|
}
|
||||||
|
TextUtils.setFormatter(DateTimeFormatter.ofPattern(pattern, currentLocale))
|
||||||
|
if (TextUtils.isEmpty(settingsHelper.getString(Constants.DEVICE_UUID))) {
|
||||||
|
settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCrashReporter() {
|
||||||
|
if (BuildConfig.DEBUG) return
|
||||||
|
CrashReporter.get(this).start()
|
||||||
|
// logCollector = new LogCollector(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCloseGuard() {
|
||||||
|
if (!BuildConfig.DEBUG) return
|
||||||
|
try {
|
||||||
|
Class.forName("dalvik.system.CloseGuard")
|
||||||
|
.getMethod("setEnabled", Boolean::class.javaPrimitiveType)
|
||||||
|
.invoke(null, true)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupFresco() {
|
||||||
|
// final Set<RequestListener> requestListeners = new HashSet<>();
|
||||||
|
// requestListeners.add(new RequestLoggingListener());
|
||||||
|
val imagePipelineConfig = ImagePipelineConfig
|
||||||
|
.newBuilder(this) // .setMainDiskCacheConfig(diskCacheConfig)
|
||||||
|
// .setRequestListeners(requestListeners)
|
||||||
|
.setDownsampleEnabled(true)
|
||||||
|
.build()
|
||||||
|
Fresco.initialize(this, imagePipelineConfig)
|
||||||
|
// FLog.setMinimumLoggingLevel(FLog.VERBOSE);
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
package awais.instagrabber.activities;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
|
|
||||||
import awais.instagrabber.utils.LocaleUtils;
|
|
||||||
import awais.instagrabber.utils.ThemeUtils;
|
|
||||||
|
|
||||||
public abstract class BaseLanguageActivity extends AppCompatActivity {
|
|
||||||
protected BaseLanguageActivity() {
|
|
||||||
LocaleUtils.updateConfig(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
ThemeUtils.changeTheme(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
}
|
|
||||||
}
|
|
18
app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.kt
Executable file
18
app/src/main/java/awais/instagrabber/activities/BaseLanguageActivity.kt
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
package awais.instagrabber.activities
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import awais.instagrabber.utils.LocaleUtils
|
||||||
|
import awais.instagrabber.utils.ThemeUtils
|
||||||
|
|
||||||
|
abstract class BaseLanguageActivity protected constructor() : AppCompatActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
ThemeUtils.changeTheme(this)
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
@Suppress("LeakingThis")
|
||||||
|
LocaleUtils.updateConfig(this)
|
||||||
|
}
|
||||||
|
}
|
@ -1,281 +0,0 @@
|
|||||||
package awais.instagrabber.activities;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.res.Configuration;
|
|
||||||
import android.hardware.display.DisplayManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.camera.core.CameraInfoUnavailableException;
|
|
||||||
import androidx.camera.core.CameraSelector;
|
|
||||||
import androidx.camera.core.ImageCapture;
|
|
||||||
import androidx.camera.core.ImageCaptureException;
|
|
||||||
import androidx.camera.core.Preview;
|
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.documentfile.provider.DocumentFile;
|
|
||||||
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
import awais.instagrabber.databinding.ActivityCameraBinding;
|
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
|
||||||
import awais.instagrabber.utils.PermissionUtils;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public class CameraActivity extends BaseLanguageActivity {
|
|
||||||
private static final String TAG = CameraActivity.class.getSimpleName();
|
|
||||||
private static final int CAMERA_REQUEST_CODE = 100;
|
|
||||||
private static final String FILE_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS";
|
|
||||||
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat(FILE_FORMAT, Locale.US);
|
|
||||||
|
|
||||||
private ActivityCameraBinding binding;
|
|
||||||
private ImageCapture imageCapture;
|
|
||||||
private DocumentFile outputDirectory;
|
|
||||||
private ExecutorService cameraExecutor;
|
|
||||||
private int displayId = -1;
|
|
||||||
|
|
||||||
private final DisplayManager.DisplayListener displayListener = new DisplayManager.DisplayListener() {
|
|
||||||
@Override
|
|
||||||
public void onDisplayAdded(final int displayId) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisplayRemoved(final int displayId) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisplayChanged(final int displayId) {
|
|
||||||
if (displayId == CameraActivity.this.displayId) {
|
|
||||||
imageCapture.setTargetRotation(binding.getRoot().getDisplay().getRotation());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private DisplayManager displayManager;
|
|
||||||
private ProcessCameraProvider cameraProvider;
|
|
||||||
private int lensFacing;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
binding = ActivityCameraBinding.inflate(LayoutInflater.from(getBaseContext()));
|
|
||||||
setContentView(binding.getRoot());
|
|
||||||
Utils.transparentStatusBar(this, true, false);
|
|
||||||
displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
|
|
||||||
outputDirectory = DownloadUtils.getCameraDir();
|
|
||||||
cameraExecutor = Executors.newSingleThreadExecutor();
|
|
||||||
displayManager.registerDisplayListener(displayListener, null);
|
|
||||||
binding.viewFinder.post(() -> {
|
|
||||||
displayId = binding.viewFinder.getDisplay().getDisplayId();
|
|
||||||
updateUi();
|
|
||||||
checkPermissionsAndSetupCamera();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
// Make sure that all permissions are still present, since the
|
|
||||||
// user could have removed them while the app was in paused state.
|
|
||||||
if (!PermissionUtils.hasCameraPerms(this)) {
|
|
||||||
PermissionUtils.requestCameraPerms(this, CAMERA_REQUEST_CODE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConfigurationChanged(@NonNull final Configuration newConfig) {
|
|
||||||
super.onConfigurationChanged(newConfig);
|
|
||||||
// Redraw the camera UI controls
|
|
||||||
updateUi();
|
|
||||||
|
|
||||||
// Enable or disable switching between cameras
|
|
||||||
updateCameraSwitchButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
super.onDestroy();
|
|
||||||
Utils.transparentStatusBar(this, false, false);
|
|
||||||
cameraExecutor.shutdown();
|
|
||||||
displayManager.unregisterDisplayListener(displayListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUi() {
|
|
||||||
binding.cameraCaptureButton.setOnClickListener(v -> {
|
|
||||||
try {
|
|
||||||
takePhoto();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "updateUi: ", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Disable the button until the camera is set up
|
|
||||||
binding.switchCamera.setEnabled(false);
|
|
||||||
// Listener for button used to switch cameras. Only called if the button is enabled
|
|
||||||
binding.switchCamera.setOnClickListener(v -> {
|
|
||||||
lensFacing = CameraSelector.LENS_FACING_FRONT == lensFacing ? CameraSelector.LENS_FACING_BACK
|
|
||||||
: CameraSelector.LENS_FACING_FRONT;
|
|
||||||
// Re-bind use cases to update selected camera
|
|
||||||
bindCameraUseCases();
|
|
||||||
});
|
|
||||||
binding.close.setOnClickListener(v -> {
|
|
||||||
setResult(Activity.RESULT_CANCELED);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkPermissionsAndSetupCamera() {
|
|
||||||
if (PermissionUtils.hasCameraPerms(this)) {
|
|
||||||
setupCamera();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PermissionUtils.requestCameraPerms(this, CAMERA_REQUEST_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
|
||||||
if (requestCode == CAMERA_REQUEST_CODE) {
|
|
||||||
if (PermissionUtils.hasCameraPerms(this)) {
|
|
||||||
setupCamera();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCamera() {
|
|
||||||
final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this);
|
|
||||||
cameraProviderFuture.addListener(() -> {
|
|
||||||
try {
|
|
||||||
cameraProvider = cameraProviderFuture.get();
|
|
||||||
// Select lensFacing depending on the available cameras
|
|
||||||
lensFacing = -1;
|
|
||||||
if (hasBackCamera()) {
|
|
||||||
lensFacing = CameraSelector.LENS_FACING_BACK;
|
|
||||||
} else if (hasFrontCamera()) {
|
|
||||||
lensFacing = CameraSelector.LENS_FACING_FRONT;
|
|
||||||
}
|
|
||||||
if (lensFacing == -1) {
|
|
||||||
throw new IllegalStateException("Back and front camera are unavailable");
|
|
||||||
}
|
|
||||||
// Enable or disable switching between cameras
|
|
||||||
updateCameraSwitchButton();
|
|
||||||
// Build and bind the camera use cases
|
|
||||||
bindCameraUseCases();
|
|
||||||
} catch (ExecutionException | InterruptedException | CameraInfoUnavailableException e) {
|
|
||||||
Log.e(TAG, "setupCamera: ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}, ContextCompat.getMainExecutor(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindCameraUseCases() {
|
|
||||||
final int rotation = binding.viewFinder.getDisplay().getRotation();
|
|
||||||
|
|
||||||
// CameraSelector
|
|
||||||
final CameraSelector cameraSelector = new CameraSelector.Builder()
|
|
||||||
.requireLensFacing(lensFacing)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// Preview
|
|
||||||
final Preview preview = new Preview.Builder()
|
|
||||||
// Set initial target rotation
|
|
||||||
.setTargetRotation(rotation)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
// ImageCapture
|
|
||||||
imageCapture = new ImageCapture.Builder()
|
|
||||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
|
||||||
// Set initial target rotation, we will have to call this again if rotation changes
|
|
||||||
// during the lifecycle of this use case
|
|
||||||
.setTargetRotation(rotation)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
cameraProvider.unbindAll();
|
|
||||||
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture);
|
|
||||||
|
|
||||||
preview.setSurfaceProvider(binding.viewFinder.getSurfaceProvider());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void takePhoto() throws IOException {
|
|
||||||
if (imageCapture == null) return;
|
|
||||||
final String extension = "jpg";
|
|
||||||
final String fileName = SIMPLE_DATE_FORMAT.format(System.currentTimeMillis()) + "." + extension;
|
|
||||||
// final File photoFile = new File(outputDirectory, fileName);
|
|
||||||
final String mimeType = "image/jpg";
|
|
||||||
final DocumentFile photoFile = outputDirectory.createFile(mimeType, fileName);
|
|
||||||
if (photoFile == null) {
|
|
||||||
Log.e(TAG, "takePhoto: photoFile is null!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final OutputStream outputStream = getContentResolver().openOutputStream(photoFile.getUri());
|
|
||||||
if (outputStream == null) return;
|
|
||||||
final ImageCapture.OutputFileOptions outputFileOptions = new ImageCapture.OutputFileOptions.Builder(outputStream).build();
|
|
||||||
imageCapture.takePicture(
|
|
||||||
outputFileOptions,
|
|
||||||
cameraExecutor,
|
|
||||||
new ImageCapture.OnImageSavedCallback() {
|
|
||||||
@Override
|
|
||||||
public void onImageSaved(@NonNull final ImageCapture.OutputFileResults outputFileResults) {
|
|
||||||
try { outputStream.close(); } catch (IOException ignored) {}
|
|
||||||
final Intent intent = new Intent();
|
|
||||||
intent.setData(photoFile.getUri());
|
|
||||||
setResult(Activity.RESULT_OK, intent);
|
|
||||||
finish();
|
|
||||||
Log.d(TAG, "onImageSaved: " + photoFile.getUri());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(@NonNull final ImageCaptureException exception) {
|
|
||||||
Log.e(TAG, "onError: ", exception);
|
|
||||||
try { outputStream.close(); } catch (IOException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// We can only change the foreground Drawable using API level 23+ API
|
|
||||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
// // Display flash animation to indicate that photo was captured
|
|
||||||
// final ConstraintLayout container = binding.getRoot();
|
|
||||||
// container.postDelayed(() -> {
|
|
||||||
// container.setForeground(new ColorDrawable(Color.WHITE));
|
|
||||||
// container.postDelayed(() -> container.setForeground(null), 50);
|
|
||||||
// }, 100);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enabled or disabled a button to switch cameras depending on the available cameras
|
|
||||||
*/
|
|
||||||
private void updateCameraSwitchButton() {
|
|
||||||
try {
|
|
||||||
binding.switchCamera.setEnabled(hasBackCamera() && hasFrontCamera());
|
|
||||||
} catch (CameraInfoUnavailableException e) {
|
|
||||||
binding.switchCamera.setEnabled(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the device has an available back camera. False otherwise
|
|
||||||
*/
|
|
||||||
private boolean hasBackCamera() throws CameraInfoUnavailableException {
|
|
||||||
if (cameraProvider == null) return false;
|
|
||||||
return cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the device has an available front camera. False otherwise
|
|
||||||
*/
|
|
||||||
private boolean hasFrontCamera() throws CameraInfoUnavailableException {
|
|
||||||
if (cameraProvider == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,248 @@
|
|||||||
|
package awais.instagrabber.activities
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.hardware.display.DisplayManager
|
||||||
|
import android.hardware.display.DisplayManager.DisplayListener
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.camera.core.*
|
||||||
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import awais.instagrabber.databinding.ActivityCameraBinding
|
||||||
|
import awais.instagrabber.utils.DirectoryUtils
|
||||||
|
import awais.instagrabber.utils.PermissionUtils
|
||||||
|
import awais.instagrabber.utils.Utils
|
||||||
|
import awais.instagrabber.utils.extensions.TAG
|
||||||
|
import com.google.common.io.Files
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.ExecutionException
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class CameraActivity : BaseLanguageActivity() {
|
||||||
|
private lateinit var binding: ActivityCameraBinding
|
||||||
|
private lateinit var outputDirectory: File
|
||||||
|
private lateinit var displayManager: DisplayManager
|
||||||
|
private lateinit var cameraExecutor: ExecutorService
|
||||||
|
|
||||||
|
private var imageCapture: ImageCapture? = null
|
||||||
|
private var displayId = -1
|
||||||
|
private var cameraProvider: ProcessCameraProvider? = null
|
||||||
|
private var lensFacing = 0
|
||||||
|
|
||||||
|
private val cameraRequestCode = 100
|
||||||
|
private val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
|
||||||
|
private val displayListener: DisplayListener = object : DisplayListener {
|
||||||
|
override fun onDisplayAdded(displayId: Int) {}
|
||||||
|
override fun onDisplayRemoved(displayId: Int) {}
|
||||||
|
override fun onDisplayChanged(displayId: Int) {
|
||||||
|
if (displayId == this@CameraActivity.displayId) {
|
||||||
|
imageCapture?.targetRotation = binding.root.display.rotation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityCameraBinding.inflate(LayoutInflater.from(baseContext))
|
||||||
|
setContentView(binding.root)
|
||||||
|
Utils.transparentStatusBar(this, true, false)
|
||||||
|
displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
|
||||||
|
outputDirectory = DirectoryUtils.getOutputMediaDirectory(this, "Camera")
|
||||||
|
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
displayManager.registerDisplayListener(displayListener, null)
|
||||||
|
binding.viewFinder.post {
|
||||||
|
displayId = binding.viewFinder.display.displayId
|
||||||
|
updateUi()
|
||||||
|
checkPermissionsAndSetupCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// Make sure that all permissions are still present, since the
|
||||||
|
// user could have removed them while the app was in paused state.
|
||||||
|
if (!PermissionUtils.hasCameraPerms(this)) {
|
||||||
|
PermissionUtils.requestCameraPerms(this, cameraRequestCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
// Redraw the camera UI controls
|
||||||
|
updateUi()
|
||||||
|
|
||||||
|
// Enable or disable switching between cameras
|
||||||
|
updateCameraSwitchButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
Utils.transparentStatusBar(this, false, false)
|
||||||
|
cameraExecutor.shutdown()
|
||||||
|
displayManager.unregisterDisplayListener(displayListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUi() {
|
||||||
|
binding.cameraCaptureButton.setOnClickListener { takePhoto() }
|
||||||
|
// Disable the button until the camera is set up
|
||||||
|
binding.switchCamera.isEnabled = false
|
||||||
|
// Listener for button used to switch cameras. Only called if the button is enabled
|
||||||
|
binding.switchCamera.setOnClickListener {
|
||||||
|
lensFacing = if (CameraSelector.LENS_FACING_FRONT == lensFacing) CameraSelector.LENS_FACING_BACK else CameraSelector.LENS_FACING_FRONT
|
||||||
|
// Re-bind use cases to update selected camera
|
||||||
|
bindCameraUseCases()
|
||||||
|
}
|
||||||
|
binding.close.setOnClickListener {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPermissionsAndSetupCamera() {
|
||||||
|
if (PermissionUtils.hasCameraPerms(this)) {
|
||||||
|
setupCamera()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
PermissionUtils.requestCameraPerms(this, cameraRequestCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == cameraRequestCode) {
|
||||||
|
if (PermissionUtils.hasCameraPerms(this)) {
|
||||||
|
setupCamera()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCamera() {
|
||||||
|
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
|
||||||
|
cameraProviderFuture.addListener({
|
||||||
|
try {
|
||||||
|
cameraProvider = cameraProviderFuture.get()
|
||||||
|
// Select lensFacing depending on the available cameras
|
||||||
|
lensFacing = -1
|
||||||
|
if (hasBackCamera()) {
|
||||||
|
lensFacing = CameraSelector.LENS_FACING_BACK
|
||||||
|
} else if (hasFrontCamera()) {
|
||||||
|
lensFacing = CameraSelector.LENS_FACING_FRONT
|
||||||
|
}
|
||||||
|
check(lensFacing != -1) { "Back and front camera are unavailable" }
|
||||||
|
// Enable or disable switching between cameras
|
||||||
|
updateCameraSwitchButton()
|
||||||
|
// Build and bind the camera use cases
|
||||||
|
bindCameraUseCases()
|
||||||
|
} catch (e: ExecutionException) {
|
||||||
|
Log.e(TAG, "setupCamera: ", e)
|
||||||
|
} catch (e: InterruptedException) {
|
||||||
|
Log.e(TAG, "setupCamera: ", e)
|
||||||
|
} catch (e: CameraInfoUnavailableException) {
|
||||||
|
Log.e(TAG, "setupCamera: ", e)
|
||||||
|
}
|
||||||
|
}, ContextCompat.getMainExecutor(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindCameraUseCases() {
|
||||||
|
val rotation = binding.viewFinder.display.rotation
|
||||||
|
|
||||||
|
// CameraSelector
|
||||||
|
val cameraSelector = CameraSelector.Builder()
|
||||||
|
.requireLensFacing(lensFacing)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// Preview
|
||||||
|
val preview = Preview.Builder() // Set initial target rotation
|
||||||
|
.setTargetRotation(rotation)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// ImageCapture
|
||||||
|
imageCapture = ImageCapture.Builder()
|
||||||
|
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) // Set initial target rotation, we will have to call this again if rotation changes
|
||||||
|
// during the lifecycle of this use case
|
||||||
|
.setTargetRotation(rotation)
|
||||||
|
.build()
|
||||||
|
cameraProvider?.unbindAll()
|
||||||
|
cameraProvider?.bindToLifecycle(this, cameraSelector, preview, imageCapture)
|
||||||
|
preview.setSurfaceProvider(binding.viewFinder.surfaceProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun takePhoto() {
|
||||||
|
if (imageCapture == null) return
|
||||||
|
val photoFile = File(outputDirectory, simpleDateFormat.format(System.currentTimeMillis()) + ".jpg")
|
||||||
|
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
|
||||||
|
imageCapture?.takePicture(
|
||||||
|
outputFileOptions,
|
||||||
|
cameraExecutor,
|
||||||
|
object : ImageCapture.OnImageSavedCallback {
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
|
||||||
|
val uri = Uri.fromFile(photoFile)
|
||||||
|
val mimeType = MimeTypeMap.getSingleton()
|
||||||
|
.getMimeTypeFromExtension(Files.getFileExtension(photoFile.name))
|
||||||
|
MediaScannerConnection.scanFile(
|
||||||
|
this@CameraActivity,
|
||||||
|
arrayOf(photoFile.absolutePath),
|
||||||
|
arrayOf(mimeType)
|
||||||
|
) { _: String?, uri1: Uri? ->
|
||||||
|
Log.d(TAG, "onImageSaved: scan complete")
|
||||||
|
val intent = Intent()
|
||||||
|
intent.data = uri1
|
||||||
|
setResult(RESULT_OK, intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
Log.d(TAG, "onImageSaved: $uri")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(exception: ImageCaptureException) {
|
||||||
|
Log.e(TAG, "onError: ", exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// We can only change the foreground Drawable using API level 23+ API
|
||||||
|
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
// // Display flash animation to indicate that photo was captured
|
||||||
|
// final ConstraintLayout container = binding.getRoot();
|
||||||
|
// container.postDelayed(() -> {
|
||||||
|
// container.setForeground(new ColorDrawable(Color.WHITE));
|
||||||
|
// container.postDelayed(() -> container.setForeground(null), 50);
|
||||||
|
// }, 100);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enabled or disabled a button to switch cameras depending on the available cameras
|
||||||
|
*/
|
||||||
|
private fun updateCameraSwitchButton() {
|
||||||
|
try {
|
||||||
|
binding.switchCamera.isEnabled = hasBackCamera() && hasFrontCamera()
|
||||||
|
} catch (e: CameraInfoUnavailableException) {
|
||||||
|
binding.switchCamera.isEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the device has an available back camera. False otherwise
|
||||||
|
*/
|
||||||
|
@Throws(CameraInfoUnavailableException::class)
|
||||||
|
private fun hasBackCamera(): Boolean {
|
||||||
|
return if (cameraProvider == null) false else cameraProvider?.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the device has an available front camera. False otherwise
|
||||||
|
*/
|
||||||
|
@Throws(CameraInfoUnavailableException::class)
|
||||||
|
private fun hasFrontCamera(): Boolean {
|
||||||
|
return if (cameraProvider == null) {
|
||||||
|
false
|
||||||
|
} else cameraProvider?.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) ?: false
|
||||||
|
}
|
||||||
|
}
|
@ -1,149 +0,0 @@
|
|||||||
package awais.instagrabber.activities;
|
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
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.Bundle;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.asyncs.PostFetcher;
|
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
|
||||||
import awais.instagrabber.models.IntentModel;
|
|
||||||
import awais.instagrabber.models.enums.IntentModelType;
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
|
||||||
import awais.instagrabber.utils.IntentUtils;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public final class DirectDownload extends AppCompatActivity {
|
|
||||||
private static final int NOTIFICATION_ID = 1900000000;
|
|
||||||
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
|
|
||||||
|
|
||||||
private boolean contextFound = false;
|
|
||||||
private Intent intent;
|
|
||||||
private Context context;
|
|
||||||
private NotificationManagerCompat notificationManager;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_direct);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowAttributesChanged(final WindowManager.LayoutParams params) {
|
|
||||||
super.onWindowAttributesChanged(params);
|
|
||||||
if (!contextFound) {
|
|
||||||
intent = getIntent();
|
|
||||||
context = getApplicationContext();
|
|
||||||
if (intent != null && context != null) {
|
|
||||||
contextFound = true;
|
|
||||||
checkPermissions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Resources getResources() {
|
|
||||||
if (!contextFound) {
|
|
||||||
intent = getIntent();
|
|
||||||
context = getApplicationContext();
|
|
||||||
if (intent != null && context != null) {
|
|
||||||
contextFound = true;
|
|
||||||
checkPermissions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.getResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void checkPermissions() {
|
|
||||||
// if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
doDownload();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// ActivityCompat.requestPermissions(this, DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
|
|
||||||
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
|
|
||||||
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
|
|
||||||
doDownload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void doDownload() {
|
|
||||||
CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE));
|
|
||||||
notificationManager = NotificationManagerCompat.from(getApplicationContext());
|
|
||||||
final Intent intent = getIntent();
|
|
||||||
final String action = intent.getAction();
|
|
||||||
if (TextUtils.isEmpty(action) || Intent.ACTION_MAIN.equals(action)) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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 || TextUtils.isEmpty(data)) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final IntentModel model = IntentUtils.parseUrl(data);
|
|
||||||
if (model == null || model.getType() != IntentModelType.POST) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String text = model.getText();
|
|
||||||
new PostFetcher(text, new FetchListener<Media>() {
|
|
||||||
@Override
|
|
||||||
public void doBefore() {
|
|
||||||
if (notificationManager == null) return;
|
|
||||||
final Notification fetchingPostNotification = new NotificationCompat.Builder(getApplicationContext(), Constants.DOWNLOAD_CHANNEL_ID)
|
|
||||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
|
||||||
.setSmallIcon(R.drawable.ic_download)
|
|
||||||
.setAutoCancel(false)
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_MIN)
|
|
||||||
.setContentText(getString(R.string.direct_download_loading))
|
|
||||||
.build();
|
|
||||||
notificationManager.notify(NOTIFICATION_ID, fetchingPostNotification);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResult(final Media result) {
|
|
||||||
if (notificationManager != null) notificationManager.cancel(NOTIFICATION_ID);
|
|
||||||
if (result == null) {
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DownloadUtils.download(getApplicationContext(), result);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
package awais.instagrabber.activities;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.webkit.CookieManager;
|
|
||||||
import android.webkit.CookieSyncManager;
|
|
||||||
import android.webkit.WebChromeClient;
|
|
||||||
import android.webkit.WebSettings;
|
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.webkit.WebViewClient;
|
|
||||||
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.CookieUtils;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
|
|
||||||
public final class Login extends BaseLanguageActivity implements View.OnClickListener {
|
|
||||||
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;
|
|
||||||
final String mainCookie = CookieUtils.getCookie(url);
|
|
||||||
if (TextUtils.isEmpty(mainCookie) || !mainCookie.contains("; ds_user_id=")) {
|
|
||||||
ready = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (mainCookie.contains("; ds_user_id=") && ready) {
|
|
||||||
returnCookieResult(mainCookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void returnCookieResult(final String mainCookie) {
|
|
||||||
final Intent intent = new Intent();
|
|
||||||
intent.putExtra("cookie", mainCookie);
|
|
||||||
setResult(Constants.LOGIN_RESULT_CODE, intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final WebChromeClient webChromeClient = new WebChromeClient();
|
|
||||||
private String webViewUrl;
|
|
||||||
private boolean ready = false;
|
|
||||||
private ActivityLoginBinding loginBinding;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
loginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(getApplicationContext()));
|
|
||||||
setContentView(loginBinding.getRoot());
|
|
||||||
|
|
||||||
initWebView();
|
|
||||||
|
|
||||||
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/");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (v == loginBinding.cookies) {
|
|
||||||
final String mainCookie = CookieUtils.getCookie(webViewUrl);
|
|
||||||
if (TextUtils.isEmpty(mainCookie) || !mainCookie.contains("; ds_user_id=")) {
|
|
||||||
Toast.makeText(this, R.string.login_error_loading_cookies, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returnCookieResult(mainCookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
webSettings.setUserAgentString("Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36");
|
|
||||||
webSettings.setJavaScriptEnabled(true);
|
|
||||||
webSettings.setDomStorageEnabled(true);
|
|
||||||
webSettings.setSupportZoom(true);
|
|
||||||
webSettings.setBuiltInZoomControls(true);
|
|
||||||
webSettings.setDisplayZoomControls(false);
|
|
||||||
webSettings.setLoadWithOverviewMode(true);
|
|
||||||
webSettings.setUseWideViewPort(true);
|
|
||||||
webSettings.setAllowFileAccessFromFileURLs(true);
|
|
||||||
webSettings.setAllowUniversalAccessFromFileURLs(true);
|
|
||||||
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
|
||||||
CookieManager.getInstance().removeAllCookies(null);
|
|
||||||
CookieManager.getInstance().flush();
|
|
||||||
} else {
|
|
||||||
CookieSyncManager cookieSyncMngr = CookieSyncManager.createInstance(getApplicationContext());
|
|
||||||
cookieSyncMngr.startSync();
|
|
||||||
CookieManager cookieManager = CookieManager.getInstance();
|
|
||||||
cookieManager.removeAllCookie();
|
|
||||||
cookieManager.removeSessionCookie();
|
|
||||||
cookieSyncMngr.stopSync();
|
|
||||||
cookieSyncMngr.sync();
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
119
app/src/main/java/awais/instagrabber/activities/Login.kt
Executable file
119
app/src/main/java/awais/instagrabber/activities/Login.kt
Executable file
@ -0,0 +1,119 @@
|
|||||||
|
package awais.instagrabber.activities
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.webkit.*
|
||||||
|
import android.widget.Toast
|
||||||
|
import awais.instagrabber.R
|
||||||
|
import awais.instagrabber.databinding.ActivityLoginBinding
|
||||||
|
import awais.instagrabber.utils.Constants
|
||||||
|
import awais.instagrabber.utils.getCookie
|
||||||
|
|
||||||
|
class Login : BaseLanguageActivity(), View.OnClickListener {
|
||||||
|
private var webViewUrl: String? = null
|
||||||
|
private var ready = false
|
||||||
|
private lateinit var loginBinding: ActivityLoginBinding
|
||||||
|
|
||||||
|
private val webChromeClient = WebChromeClient()
|
||||||
|
private val webViewClient: WebViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
||||||
|
webViewUrl = url
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
|
webViewUrl = url
|
||||||
|
val mainCookie = getCookie(url)
|
||||||
|
if (mainCookie.isNullOrBlank() || !mainCookie.contains("; ds_user_id=")) {
|
||||||
|
ready = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (mainCookie.contains("; ds_user_id=") && ready) {
|
||||||
|
returnCookieResult(mainCookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun returnCookieResult(mainCookie: String?) {
|
||||||
|
val intent = Intent()
|
||||||
|
intent.putExtra("cookie", mainCookie)
|
||||||
|
setResult(Constants.LOGIN_RESULT_CODE, intent)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
loginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(applicationContext))
|
||||||
|
setContentView(loginBinding.root)
|
||||||
|
initWebView()
|
||||||
|
loginBinding.cookies.setOnClickListener(this)
|
||||||
|
loginBinding.refresh.setOnClickListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View) {
|
||||||
|
if (v === loginBinding.refresh) {
|
||||||
|
loginBinding.webView.loadUrl("https://instagram.com/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (v === loginBinding.cookies) {
|
||||||
|
val mainCookie = getCookie(webViewUrl)
|
||||||
|
if (mainCookie.isNullOrBlank() || !mainCookie.contains("; ds_user_id=")) {
|
||||||
|
Toast.makeText(this, R.string.login_error_loading_cookies, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
returnCookieResult(mainCookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
|
private fun initWebView() {
|
||||||
|
loginBinding.webView.webChromeClient = webChromeClient
|
||||||
|
loginBinding.webView.webViewClient = webViewClient
|
||||||
|
val webSettings = loginBinding.webView.settings
|
||||||
|
webSettings.userAgentString =
|
||||||
|
"Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36"
|
||||||
|
webSettings.javaScriptEnabled = true
|
||||||
|
webSettings.domStorageEnabled = true
|
||||||
|
webSettings.setSupportZoom(true)
|
||||||
|
webSettings.builtInZoomControls = true
|
||||||
|
webSettings.displayZoomControls = false
|
||||||
|
webSettings.loadWithOverviewMode = true
|
||||||
|
webSettings.useWideViewPort = true
|
||||||
|
webSettings.allowFileAccessFromFileURLs = true
|
||||||
|
webSettings.allowUniversalAccessFromFileURLs = true
|
||||||
|
webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
|
||||||
|
CookieManager.getInstance().removeAllCookies(null)
|
||||||
|
CookieManager.getInstance().flush()
|
||||||
|
} else {
|
||||||
|
val cookieSyncMngr = CookieSyncManager.createInstance(applicationContext)
|
||||||
|
cookieSyncMngr.startSync()
|
||||||
|
val cookieManager = CookieManager.getInstance()
|
||||||
|
cookieManager.removeAllCookie()
|
||||||
|
cookieManager.removeSessionCookie()
|
||||||
|
cookieSyncMngr.stopSync()
|
||||||
|
cookieSyncMngr.sync()
|
||||||
|
}
|
||||||
|
loginBinding.webView.loadUrl("https://instagram.com/")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
loginBinding.webView.onPause()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
loginBinding.webView.onResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
loginBinding.webView.destroy()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
}
|
@ -1,973 +0,0 @@
|
|||||||
package awais.instagrabber.activities;
|
|
||||||
|
|
||||||
import android.animation.LayoutTransition;
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.database.MatrixCursor;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import android.provider.BaseColumns;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.widget.AutoCompleteTextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
|
||||||
import androidx.core.app.NotificationManagerCompat;
|
|
||||||
import androidx.core.provider.FontRequest;
|
|
||||||
import androidx.emoji.text.EmojiCompat;
|
|
||||||
import androidx.emoji.text.FontRequestEmojiCompatConfig;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.navigation.NavBackStackEntry;
|
|
||||||
import androidx.navigation.NavController;
|
|
||||||
import androidx.navigation.NavDestination;
|
|
||||||
import androidx.navigation.ui.NavigationUI;
|
|
||||||
|
|
||||||
import com.google.android.material.appbar.AppBarLayout;
|
|
||||||
import com.google.android.material.appbar.CollapsingToolbarLayout;
|
|
||||||
import com.google.android.material.badge.BadgeDrawable;
|
|
||||||
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.Iterators;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import awais.instagrabber.BuildConfig;
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.adapters.SuggestionsAdapter;
|
|
||||||
import awais.instagrabber.asyncs.PostFetcher;
|
|
||||||
import awais.instagrabber.customviews.emoji.EmojiVariantManager;
|
|
||||||
import awais.instagrabber.databinding.ActivityMainBinding;
|
|
||||||
import awais.instagrabber.fragments.PostViewV2Fragment;
|
|
||||||
import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
|
|
||||||
import awais.instagrabber.fragments.main.FeedFragment;
|
|
||||||
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
|
||||||
import awais.instagrabber.models.IntentModel;
|
|
||||||
import awais.instagrabber.models.Tab;
|
|
||||||
import awais.instagrabber.models.enums.SuggestionType;
|
|
||||||
import awais.instagrabber.repositories.responses.search.SearchItem;
|
|
||||||
import awais.instagrabber.repositories.responses.search.SearchResponse;
|
|
||||||
import awais.instagrabber.services.ActivityCheckerService;
|
|
||||||
import awais.instagrabber.services.DMSyncAlarmReceiver;
|
|
||||||
import awais.instagrabber.utils.AppExecutors;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
|
||||||
import awais.instagrabber.utils.FlavorTown;
|
|
||||||
import awais.instagrabber.utils.IntentUtils;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
import awais.instagrabber.utils.emoji.EmojiParser;
|
|
||||||
import awais.instagrabber.viewmodels.AppStateViewModel;
|
|
||||||
import awais.instagrabber.viewmodels.DirectInboxViewModel;
|
|
||||||
import awais.instagrabber.webservices.RetrofitFactory;
|
|
||||||
import awais.instagrabber.webservices.SearchService;
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
import static awais.instagrabber.utils.Constants.EXTRA_INITIAL_URI;
|
|
||||||
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
|
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
|
||||||
|
|
||||||
public class MainActivity extends BaseLanguageActivity implements FragmentManager.OnBackStackChangedListener {
|
|
||||||
private static final String TAG = "MainActivity";
|
|
||||||
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
|
|
||||||
private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId";
|
|
||||||
|
|
||||||
private ActivityMainBinding binding;
|
|
||||||
private LiveData<NavController> currentNavControllerLiveData;
|
|
||||||
private MenuItem searchMenuItem;
|
|
||||||
private SuggestionsAdapter suggestionAdapter;
|
|
||||||
private AutoCompleteTextView searchAutoComplete;
|
|
||||||
private SearchView searchView;
|
|
||||||
private SearchService searchService;
|
|
||||||
private boolean showSearch = true;
|
|
||||||
private Handler suggestionsFetchHandler;
|
|
||||||
private int firstFragmentGraphIndex;
|
|
||||||
private int lastSelectedNavMenuId;
|
|
||||||
private boolean isActivityCheckerServiceBound = false;
|
|
||||||
private boolean isBackStackEmpty = false;
|
|
||||||
private boolean isLoggedIn;
|
|
||||||
private HideBottomViewOnScrollBehavior<BottomNavigationView> behavior;
|
|
||||||
private List<Tab> currentTabs;
|
|
||||||
private List<Integer> showBottomViewDestinations = Collections.emptyList();
|
|
||||||
|
|
||||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(final ComponentName name, final IBinder service) {
|
|
||||||
// final ActivityCheckerService.LocalBinder binder = (ActivityCheckerService.LocalBinder) service;
|
|
||||||
// final ActivityCheckerService activityCheckerService = binder.getService();
|
|
||||||
isActivityCheckerServiceBound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(final ComponentName name) {
|
|
||||||
isActivityCheckerServiceBound = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
try {
|
|
||||||
DownloadUtils.init(this);
|
|
||||||
} catch (DownloadUtils.ReselectDocumentTreeException e) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
final Intent intent = new Intent(this, DirectorySelectActivity.class);
|
|
||||||
intent.putExtra(EXTRA_INITIAL_URI, e.getInitialUri());
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RetrofitFactory.setup(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
|
||||||
setupCookie();
|
|
||||||
if (settingsHelper.getBoolean(Constants.FLAG_SECURE))
|
|
||||||
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
|
|
||||||
setContentView(binding.getRoot());
|
|
||||||
final Toolbar toolbar = binding.toolbar;
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
createNotificationChannels();
|
|
||||||
try {
|
|
||||||
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();
|
|
||||||
//noinspection unchecked
|
|
||||||
behavior = (HideBottomViewOnScrollBehavior<BottomNavigationView>) layoutParams.getBehavior();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "onCreate: ", e);
|
|
||||||
}
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
setupBottomNavigationBar(true);
|
|
||||||
}
|
|
||||||
setupSuggestions();
|
|
||||||
if (!BuildConfig.isPre) {
|
|
||||||
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
|
|
||||||
if (checkUpdates) FlavorTown.updateCheck(this);
|
|
||||||
}
|
|
||||||
FlavorTown.changelogCheck(this);
|
|
||||||
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
|
|
||||||
final Intent intent = getIntent();
|
|
||||||
handleIntent(intent);
|
|
||||||
if (isLoggedIn && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
|
|
||||||
bindActivityCheckerService();
|
|
||||||
}
|
|
||||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
|
||||||
// Initialise the internal map
|
|
||||||
AppExecutors.getInstance().tasksThread().execute(() -> {
|
|
||||||
EmojiParser.setup(this);
|
|
||||||
EmojiVariantManager.getInstance();
|
|
||||||
});
|
|
||||||
initEmojiCompat();
|
|
||||||
searchService = SearchService.getInstance();
|
|
||||||
// initDmService();
|
|
||||||
initDmUnreadCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCookie() {
|
|
||||||
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
|
||||||
long userId = 0;
|
|
||||||
String csrfToken = null;
|
|
||||||
if (!TextUtils.isEmpty(cookie)) {
|
|
||||||
userId = CookieUtils.getUserIdFromCookie(cookie);
|
|
||||||
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
|
||||||
}
|
|
||||||
if (TextUtils.isEmpty(cookie) || userId == 0 || TextUtils.isEmpty(csrfToken)) {
|
|
||||||
isLoggedIn = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
|
|
||||||
if (TextUtils.isEmpty(deviceUuid)) {
|
|
||||||
settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
|
|
||||||
}
|
|
||||||
CookieUtils.setupCookies(cookie);
|
|
||||||
isLoggedIn = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDmService() {
|
|
||||||
if (!isLoggedIn) return;
|
|
||||||
final boolean enabled = settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH);
|
|
||||||
if (!enabled) return;
|
|
||||||
DMSyncAlarmReceiver.setAlarm(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initDmUnreadCount() {
|
|
||||||
if (!isLoggedIn) return;
|
|
||||||
final DirectInboxViewModel directInboxViewModel = new ViewModelProvider(this).get(DirectInboxViewModel.class);
|
|
||||||
directInboxViewModel.getUnseenCount().observe(this, unseenCountResource -> {
|
|
||||||
if (unseenCountResource == null) return;
|
|
||||||
final Integer unseenCount = unseenCountResource.data;
|
|
||||||
setNavBarDMUnreadCountBadge(unseenCount == null ? 0 : unseenCount);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
|
||||||
getMenuInflater().inflate(R.menu.main_menu, menu);
|
|
||||||
searchMenuItem = menu.findItem(R.id.search);
|
|
||||||
if (showSearch && currentNavControllerLiveData != null) {
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController != null) {
|
|
||||||
final NavDestination currentDestination = navController.getCurrentDestination();
|
|
||||||
if (currentDestination != null) {
|
|
||||||
final int destinationId = currentDestination.getId();
|
|
||||||
showSearch = destinationId == R.id.profileFragment;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!showSearch) {
|
|
||||||
searchMenuItem.setVisible(false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return setupSearchView();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
|
||||||
outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, String.valueOf(firstFragmentGraphIndex));
|
|
||||||
if (binding != null) {
|
|
||||||
outState.putString(LAST_SELECT_NAV_MENU_ID, String.valueOf(binding.bottomNavView.getSelectedItemId()));
|
|
||||||
}
|
|
||||||
super.onSaveInstanceState(outState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
|
||||||
super.onRestoreInstanceState(savedInstanceState);
|
|
||||||
final String key = (String) savedInstanceState.get(FIRST_FRAGMENT_GRAPH_INDEX_KEY);
|
|
||||||
if (key != null) {
|
|
||||||
try {
|
|
||||||
firstFragmentGraphIndex = Integer.parseInt(key);
|
|
||||||
} catch (NumberFormatException ignored) { }
|
|
||||||
}
|
|
||||||
final String lastSelected = (String) savedInstanceState.get(LAST_SELECT_NAV_MENU_ID);
|
|
||||||
if (lastSelected != null) {
|
|
||||||
try {
|
|
||||||
lastSelectedNavMenuId = Integer.parseInt(lastSelected);
|
|
||||||
} catch (NumberFormatException ignored) { }
|
|
||||||
}
|
|
||||||
setupBottomNavigationBar(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onSupportNavigateUp() {
|
|
||||||
if (currentNavControllerLiveData == null) return false;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return false;
|
|
||||||
return navController.navigateUp();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onNewIntent(final Intent intent) {
|
|
||||||
super.onNewIntent(intent);
|
|
||||||
handleIntent(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
try {
|
|
||||||
super.onDestroy();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "onDestroy: ", e);
|
|
||||||
}
|
|
||||||
unbindActivityCheckerService();
|
|
||||||
try {
|
|
||||||
RetrofitFactory.getInstance().destroy();
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
DownloadUtils.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
int currentNavControllerBackStack = 2;
|
|
||||||
if (currentNavControllerLiveData != null) {
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController != null) {
|
|
||||||
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
|
|
||||||
currentNavControllerBackStack = backStack.size();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isTaskRoot() && isBackStackEmpty && currentNavControllerBackStack == 2) {
|
|
||||||
finishAfterTransition();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!isFinishing()) {
|
|
||||||
try {
|
|
||||||
super.onBackPressed();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "onBackPressed: ", e);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackStackChanged() {
|
|
||||||
final int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
|
|
||||||
isBackStackEmpty = backStackEntryCount == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createNotificationChannels() {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;
|
|
||||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
|
|
||||||
notificationManager.createNotificationChannel(new NotificationChannel(Constants.DOWNLOAD_CHANNEL_ID,
|
|
||||||
Constants.DOWNLOAD_CHANNEL_NAME,
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT));
|
|
||||||
notificationManager.createNotificationChannel(new NotificationChannel(Constants.ACTIVITY_CHANNEL_ID,
|
|
||||||
Constants.ACTIVITY_CHANNEL_NAME,
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT));
|
|
||||||
notificationManager.createNotificationChannel(new NotificationChannel(Constants.DM_UNREAD_CHANNEL_ID,
|
|
||||||
Constants.DM_UNREAD_CHANNEL_NAME,
|
|
||||||
NotificationManager.IMPORTANCE_DEFAULT));
|
|
||||||
final NotificationChannel silentNotificationChannel = new NotificationChannel(Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
|
|
||||||
Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
|
|
||||||
NotificationManager.IMPORTANCE_LOW);
|
|
||||||
silentNotificationChannel.setSound(null, null);
|
|
||||||
notificationManager.createNotificationChannel(silentNotificationChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupSuggestions() {
|
|
||||||
suggestionsFetchHandler = new Handler();
|
|
||||||
suggestionAdapter = new SuggestionsAdapter(this, (type, query) -> {
|
|
||||||
if (searchMenuItem != null) searchMenuItem.collapseActionView();
|
|
||||||
if (searchView != null && !searchView.isIconified()) searchView.setIconified(true);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return;
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
switch (type) {
|
|
||||||
case TYPE_LOCATION:
|
|
||||||
bundle.putLong("locationId", Long.parseLong(query));
|
|
||||||
navController.navigate(R.id.action_global_locationFragment, bundle);
|
|
||||||
break;
|
|
||||||
case TYPE_HASHTAG:
|
|
||||||
bundle.putString("hashtag", query);
|
|
||||||
navController.navigate(R.id.action_global_hashTagFragment, bundle);
|
|
||||||
break;
|
|
||||||
case TYPE_USER:
|
|
||||||
bundle.putString("username", query);
|
|
||||||
navController.navigate(R.id.action_global_profileFragment, bundle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setupSearchView() {
|
|
||||||
final View actionView = searchMenuItem.getActionView();
|
|
||||||
if (!(actionView instanceof SearchView)) return false;
|
|
||||||
searchView = (SearchView) actionView;
|
|
||||||
searchView.setSuggestionsAdapter(suggestionAdapter);
|
|
||||||
searchView.setMaxWidth(Integer.MAX_VALUE);
|
|
||||||
final View searchText = searchView.findViewById(R.id.search_src_text);
|
|
||||||
if (searchText instanceof AutoCompleteTextView) {
|
|
||||||
searchAutoComplete = (AutoCompleteTextView) searchText;
|
|
||||||
}
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
||||||
private boolean searchUser;
|
|
||||||
private boolean searchHash;
|
|
||||||
private Call<SearchResponse> prevSuggestionAsync;
|
|
||||||
private final String[] COLUMNS = {
|
|
||||||
BaseColumns._ID,
|
|
||||||
Constants.EXTRAS_USERNAME,
|
|
||||||
Constants.EXTRAS_NAME,
|
|
||||||
Constants.EXTRAS_TYPE,
|
|
||||||
"query",
|
|
||||||
"pfp",
|
|
||||||
"verified"
|
|
||||||
};
|
|
||||||
private String currentSearchQuery;
|
|
||||||
|
|
||||||
private final Callback<SearchResponse> cb = new Callback<SearchResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull final Call<SearchResponse> call,
|
|
||||||
@NonNull final Response<SearchResponse> response) {
|
|
||||||
final MatrixCursor cursor;
|
|
||||||
final SearchResponse body = response.body();
|
|
||||||
if (body == null) {
|
|
||||||
cursor = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<SearchItem> result = new ArrayList<>();
|
|
||||||
if (isLoggedIn) {
|
|
||||||
if (body.getList() != null) {
|
|
||||||
result.addAll(searchHash ? body.getList()
|
|
||||||
.stream()
|
|
||||||
.filter(i -> i.getUser() == null)
|
|
||||||
.collect(Collectors.toList())
|
|
||||||
: body.getList());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers());
|
|
||||||
if (body.getHashtags() != null) result.addAll(body.getHashtags());
|
|
||||||
if (body.getPlaces() != null) result.addAll(body.getPlaces());
|
|
||||||
}
|
|
||||||
cursor = new MatrixCursor(COLUMNS, 0);
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
final SearchItem suggestionModel = result.get(i);
|
|
||||||
if (suggestionModel != null) {
|
|
||||||
Object[] objects = null;
|
|
||||||
if (suggestionModel.getUser() != null)
|
|
||||||
objects = new Object[]{
|
|
||||||
suggestionModel.getPosition(),
|
|
||||||
suggestionModel.getUser().getUsername(),
|
|
||||||
suggestionModel.getUser().getFullName(),
|
|
||||||
SuggestionType.TYPE_USER,
|
|
||||||
suggestionModel.getUser().getUsername(),
|
|
||||||
suggestionModel.getUser().getProfilePicUrl(),
|
|
||||||
suggestionModel.getUser().isVerified()};
|
|
||||||
else if (suggestionModel.getHashtag() != null)
|
|
||||||
objects = new Object[]{
|
|
||||||
suggestionModel.getPosition(),
|
|
||||||
suggestionModel.getHashtag().getName(),
|
|
||||||
suggestionModel.getHashtag().getSubtitle(),
|
|
||||||
SuggestionType.TYPE_HASHTAG,
|
|
||||||
suggestionModel.getHashtag().getName(),
|
|
||||||
"res:/" + R.drawable.ic_hashtag,
|
|
||||||
false};
|
|
||||||
else if (suggestionModel.getPlace() != null)
|
|
||||||
objects = new Object[]{
|
|
||||||
suggestionModel.getPosition(),
|
|
||||||
suggestionModel.getPlace().getTitle(),
|
|
||||||
suggestionModel.getPlace().getSubtitle(),
|
|
||||||
SuggestionType.TYPE_LOCATION,
|
|
||||||
suggestionModel.getPlace().getLocation().getPk(),
|
|
||||||
"res:/" + R.drawable.ic_location,
|
|
||||||
false};
|
|
||||||
cursor.addRow(objects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggestionAdapter.changeCursor(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull final Call<SearchResponse> call,
|
|
||||||
@NonNull Throwable t) {
|
|
||||||
if (!call.isCanceled()) {
|
|
||||||
Log.e(TAG, "Exception on search:", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Runnable runnable = () -> {
|
|
||||||
cancelSuggestionsAsync();
|
|
||||||
if (TextUtils.isEmpty(currentSearchQuery)) {
|
|
||||||
suggestionAdapter.changeCursor(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
searchUser = currentSearchQuery.charAt(0) == '@';
|
|
||||||
searchHash = currentSearchQuery.charAt(0) == '#';
|
|
||||||
if (currentSearchQuery.length() == 1 && (searchHash || searchUser)) {
|
|
||||||
if (searchAutoComplete != null) {
|
|
||||||
searchAutoComplete.setThreshold(2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (searchAutoComplete != null) {
|
|
||||||
searchAutoComplete.setThreshold(1);
|
|
||||||
}
|
|
||||||
prevSuggestionAsync = searchService.search(isLoggedIn,
|
|
||||||
searchUser || searchHash ? currentSearchQuery.substring(1)
|
|
||||||
: currentSearchQuery,
|
|
||||||
searchUser ? "user" : (searchHash ? "hashtag" : "blended"));
|
|
||||||
suggestionAdapter.changeCursor(null);
|
|
||||||
prevSuggestionAsync.enqueue(cb);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void cancelSuggestionsAsync() {
|
|
||||||
if (prevSuggestionAsync != null)
|
|
||||||
try {
|
|
||||||
prevSuggestionAsync.cancel();
|
|
||||||
} catch (final Exception ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(final String query) {
|
|
||||||
return onQueryTextChange(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(final String query) {
|
|
||||||
suggestionsFetchHandler.removeCallbacks(runnable);
|
|
||||||
currentSearchQuery = query;
|
|
||||||
suggestionsFetchHandler.postDelayed(runnable, 800);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) {
|
|
||||||
currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav();
|
|
||||||
final List<Integer> mainNavList = currentTabs.stream()
|
|
||||||
.map(Tab::getNavigationResId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
showBottomViewDestinations = currentTabs.stream()
|
|
||||||
.map(Tab::getStartDestinationFragmentId)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (setDefaultTabFromSettings) {
|
|
||||||
setSelectedTab(currentTabs);
|
|
||||||
} else {
|
|
||||||
binding.bottomNavView.setSelectedItemId(lastSelectedNavMenuId);
|
|
||||||
}
|
|
||||||
final LiveData<NavController> navControllerLiveData = setupWithNavController(
|
|
||||||
binding.bottomNavView,
|
|
||||||
mainNavList,
|
|
||||||
getSupportFragmentManager(),
|
|
||||||
R.id.main_nav_host,
|
|
||||||
getIntent(),
|
|
||||||
firstFragmentGraphIndex);
|
|
||||||
navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
|
|
||||||
currentNavControllerLiveData = navControllerLiveData;
|
|
||||||
binding.bottomNavView.setOnNavigationItemReselectedListener(item -> {
|
|
||||||
// Log.d(TAG, "setupBottomNavigationBar: item: " + item);
|
|
||||||
final Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.main_nav_host);
|
|
||||||
if (navHostFragment != null) {
|
|
||||||
final Fragment fragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
|
|
||||||
if (fragment instanceof FeedFragment) {
|
|
||||||
((FeedFragment) fragment).scrollToTop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSelectedTab(final List<Tab> tabs) {
|
|
||||||
final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
|
|
||||||
try {
|
|
||||||
int navId = 0;
|
|
||||||
if (!TextUtils.isEmpty(defaultTabResNameString)) {
|
|
||||||
navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
|
|
||||||
}
|
|
||||||
final int navGraph = isLoggedIn ? R.navigation.feed_nav_graph
|
|
||||||
: R.navigation.profile_nav_graph;
|
|
||||||
final int defaultNavId = navId <= 0 ? navGraph : navId;
|
|
||||||
int index = Iterators.indexOf(tabs.iterator(), tab -> {
|
|
||||||
if (tab == null) return false;
|
|
||||||
return tab.getNavigationResId() == defaultNavId;
|
|
||||||
});
|
|
||||||
if (index < 0 || index >= tabs.size()) index = 0;
|
|
||||||
firstFragmentGraphIndex = index;
|
|
||||||
setBottomNavSelectedTab(tabs.get(index));
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(TAG, "Error parsing id", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Tab> setupAnonBottomNav() {
|
|
||||||
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
|
|
||||||
final Tab profileTab = new Tab(R.drawable.ic_person_24,
|
|
||||||
getString(R.string.profile),
|
|
||||||
false,
|
|
||||||
"profile_nav_graph",
|
|
||||||
R.navigation.profile_nav_graph,
|
|
||||||
R.id.profile_nav_graph,
|
|
||||||
R.id.profileFragment);
|
|
||||||
final Tab moreTab = new Tab(R.drawable.ic_more_horiz_24,
|
|
||||||
getString(R.string.more),
|
|
||||||
false,
|
|
||||||
"more_nav_graph",
|
|
||||||
R.navigation.more_nav_graph,
|
|
||||||
R.id.more_nav_graph,
|
|
||||||
R.id.morePreferencesFragment);
|
|
||||||
final Menu menu = binding.bottomNavView.getMenu();
|
|
||||||
menu.clear();
|
|
||||||
menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId());
|
|
||||||
menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId());
|
|
||||||
if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) {
|
|
||||||
setBottomNavSelectedTab(profileTab);
|
|
||||||
}
|
|
||||||
return ImmutableList.of(profileTab, moreTab);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Tab> setupMainBottomNav() {
|
|
||||||
final Menu menu = binding.bottomNavView.getMenu();
|
|
||||||
menu.clear();
|
|
||||||
final List<Tab> navTabList = Utils.getNavTabList(this).first;
|
|
||||||
for (final Tab tab : navTabList) {
|
|
||||||
menu.add(0, tab.getNavigationRootId(), 0, tab.getTitle()).setIcon(tab.getIconResId());
|
|
||||||
}
|
|
||||||
return navTabList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBottomNavSelectedTab(@NonNull final Tab tab) {
|
|
||||||
binding.bottomNavView.setSelectedItemId(tab.getNavigationRootId());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setBottomNavSelectedTab(@SuppressWarnings("SameParameterValue") @IdRes final int navGraphRootId) {
|
|
||||||
binding.bottomNavView.setSelectedItemId(navGraphRootId);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @NonNull
|
|
||||||
// private List<Integer> getMainNavList(final int main_nav_ids) {
|
|
||||||
// final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
|
|
||||||
// final List<Integer> mainNavList = new ArrayList<>(navIds.length());
|
|
||||||
// final int length = navIds.length();
|
|
||||||
// for (int i = 0; i < length; i++) {
|
|
||||||
// final int resourceId = navIds.getResourceId(i, -1);
|
|
||||||
// if (resourceId < 0) continue;
|
|
||||||
// mainNavList.add(resourceId);
|
|
||||||
// }
|
|
||||||
// navIds.recycle();
|
|
||||||
// return mainNavList;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
|
|
||||||
if (navController == null) return;
|
|
||||||
NavigationUI.setupWithNavController(toolbar, navController);
|
|
||||||
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
|
|
||||||
if (destination.getId() == R.id.directMessagesThreadFragment && arguments != null) {
|
|
||||||
// Set the thread title earlier for better ux
|
|
||||||
final String title = arguments.getString("title");
|
|
||||||
final ActionBar actionBar = getSupportActionBar();
|
|
||||||
if (actionBar != null && !TextUtils.isEmpty(title)) {
|
|
||||||
actionBar.setTitle(title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// below is a hack to check if we are at the end of the current stack, to setup the search view
|
|
||||||
binding.appBarLayout.setExpanded(true, true);
|
|
||||||
final int destinationId = destination.getId();
|
|
||||||
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
|
|
||||||
setupMenu(backStack.size(), destinationId);
|
|
||||||
final boolean contains = showBottomViewDestinations.contains(destinationId);
|
|
||||||
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
|
|
||||||
if (contains && behavior != null) {
|
|
||||||
behavior.slideUp(binding.bottomNavView);
|
|
||||||
}
|
|
||||||
|
|
||||||
// explicitly hide keyboard when we navigate
|
|
||||||
final View view = getCurrentFocus();
|
|
||||||
Utils.hideKeyboard(view);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupMenu(final int backStackSize, final int destinationId) {
|
|
||||||
if (searchMenuItem == null) return;
|
|
||||||
if (backStackSize >= 2 && destinationId == R.id.profileFragment) {
|
|
||||||
showSearch = true;
|
|
||||||
searchMenuItem.setVisible(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showSearch = false;
|
|
||||||
searchMenuItem.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setScrollingBehaviour() {
|
|
||||||
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams();
|
|
||||||
layoutParams.setBehavior(new AppBarLayout.ScrollingViewBehavior());
|
|
||||||
binding.mainNavHost.requestLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeScrollingBehaviour() {
|
|
||||||
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.mainNavHost.getLayoutParams();
|
|
||||||
layoutParams.setBehavior(null);
|
|
||||||
binding.mainNavHost.requestLayout();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleIntent(final Intent intent) {
|
|
||||||
if (intent == null) return;
|
|
||||||
final String action = intent.getAction();
|
|
||||||
final String type = intent.getType();
|
|
||||||
// Log.d(TAG, action + " " + type);
|
|
||||||
if (Intent.ACTION_MAIN.equals(action)) return;
|
|
||||||
if (Constants.ACTION_SHOW_ACTIVITY.equals(action)) {
|
|
||||||
showActivityView();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Constants.ACTION_SHOW_DM_THREAD.equals(action)) {
|
|
||||||
showThread(intent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Intent.ACTION_SEND.equals(action) && type != null) {
|
|
||||||
if (type.equals("text/plain")) {
|
|
||||||
handleUrl(intent.getStringExtra(Intent.EXTRA_TEXT));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Intent.ACTION_VIEW.equals(action)) {
|
|
||||||
final Uri data = intent.getData();
|
|
||||||
if (data == null) return;
|
|
||||||
handleUrl(data.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showThread(@NonNull final Intent intent) {
|
|
||||||
final String threadId = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID);
|
|
||||||
final String threadTitle = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE);
|
|
||||||
navigateToThread(threadId, threadTitle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void navigateToThread(final String threadId, final String threadTitle) {
|
|
||||||
if (threadId == null || threadTitle == null) return;
|
|
||||||
currentNavControllerLiveData.observe(this, new Observer<NavController>() {
|
|
||||||
@Override
|
|
||||||
public void onChanged(final NavController navController) {
|
|
||||||
if (navController == null) return;
|
|
||||||
if (navController.getGraph().getId() != R.id.direct_messages_nav_graph) return;
|
|
||||||
try {
|
|
||||||
final NavDestination currentDestination = navController.getCurrentDestination();
|
|
||||||
if (currentDestination != null && currentDestination.getId() == R.id.directMessagesInboxFragment) {
|
|
||||||
// if we are already on the inbox page, navigate to the thread
|
|
||||||
// need handler.post() to wait for the fragment manager to be ready to navigate
|
|
||||||
new Handler().post(() -> {
|
|
||||||
final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
|
|
||||||
.actionInboxToThread(threadId, threadTitle);
|
|
||||||
navController.navigate(action);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// add a destination change listener to navigate to thread once we are on the inbox page
|
|
||||||
navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
|
|
||||||
@Override
|
|
||||||
public void onDestinationChanged(@NonNull final NavController controller,
|
|
||||||
@NonNull final NavDestination destination,
|
|
||||||
@Nullable final Bundle arguments) {
|
|
||||||
if (destination.getId() == R.id.directMessagesInboxFragment) {
|
|
||||||
final DirectMessageInboxFragmentDirections.ActionInboxToThread action = DirectMessageInboxFragmentDirections
|
|
||||||
.actionInboxToThread(threadId, threadTitle);
|
|
||||||
controller.navigate(action);
|
|
||||||
controller.removeOnDestinationChangedListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// pop back stack until we reach the inbox page
|
|
||||||
navController.popBackStack(R.id.directMessagesInboxFragment, false);
|
|
||||||
} finally {
|
|
||||||
currentNavControllerLiveData.removeObserver(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
|
|
||||||
if (selectedItemId != R.navigation.direct_messages_nav_graph) {
|
|
||||||
setBottomNavSelectedTab(R.id.direct_messages_nav_graph);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUrl(final String url) {
|
|
||||||
if (url == null) return;
|
|
||||||
// Log.d(TAG, url);
|
|
||||||
final IntentModel intentModel = IntentUtils.parseUrl(url);
|
|
||||||
if (intentModel == null) return;
|
|
||||||
showView(intentModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showView(final IntentModel intentModel) {
|
|
||||||
switch (intentModel.getType()) {
|
|
||||||
case USERNAME:
|
|
||||||
showProfileView(intentModel);
|
|
||||||
break;
|
|
||||||
case POST:
|
|
||||||
showPostView(intentModel);
|
|
||||||
break;
|
|
||||||
case LOCATION:
|
|
||||||
showLocationView(intentModel);
|
|
||||||
break;
|
|
||||||
case HASHTAG:
|
|
||||||
showHashtagView(intentModel);
|
|
||||||
break;
|
|
||||||
case UNKNOWN:
|
|
||||||
default:
|
|
||||||
Log.w(TAG, "Unknown model type received!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showProfileView(@NonNull final IntentModel intentModel) {
|
|
||||||
final String username = intentModel.getText();
|
|
||||||
// Log.d(TAG, "username: " + username);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return;
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
bundle.putString("username", "@" + username);
|
|
||||||
navController.navigate(R.id.action_global_profileFragment, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showPostView(@NonNull final IntentModel intentModel) {
|
|
||||||
final String shortCode = intentModel.getText();
|
|
||||||
// Log.d(TAG, "shortCode: " + shortCode);
|
|
||||||
final AlertDialog alertDialog = new AlertDialog.Builder(this)
|
|
||||||
.setCancelable(false)
|
|
||||||
.setView(R.layout.dialog_opening_post)
|
|
||||||
.create();
|
|
||||||
alertDialog.show();
|
|
||||||
new PostFetcher(shortCode, feedModel -> {
|
|
||||||
if (feedModel != null) {
|
|
||||||
final PostViewV2Fragment fragment = PostViewV2Fragment
|
|
||||||
.builder(feedModel)
|
|
||||||
.build();
|
|
||||||
fragment.setOnShowListener(dialog -> alertDialog.dismiss());
|
|
||||||
fragment.show(getSupportFragmentManager(), "post_view");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Toast.makeText(getApplicationContext(), R.string.post_not_found, Toast.LENGTH_SHORT).show();
|
|
||||||
alertDialog.dismiss();
|
|
||||||
}).execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showLocationView(@NonNull final IntentModel intentModel) {
|
|
||||||
final String locationId = intentModel.getText();
|
|
||||||
// Log.d(TAG, "locationId: " + locationId);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return;
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
bundle.putLong("locationId", Long.parseLong(locationId));
|
|
||||||
navController.navigate(R.id.action_global_locationFragment, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showHashtagView(@NonNull final IntentModel intentModel) {
|
|
||||||
final String hashtag = intentModel.getText();
|
|
||||||
// Log.d(TAG, "hashtag: " + hashtag);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return;
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
bundle.putString("hashtag", hashtag);
|
|
||||||
navController.navigate(R.id.action_global_hashTagFragment, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showActivityView() {
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return;
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
bundle.putString("type", "notif");
|
|
||||||
navController.navigate(R.id.action_global_notificationsViewerFragment, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindActivityCheckerService() {
|
|
||||||
bindService(new Intent(this, ActivityCheckerService.class), serviceConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
isActivityCheckerServiceBound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unbindActivityCheckerService() {
|
|
||||||
if (!isActivityCheckerServiceBound) return;
|
|
||||||
unbindService(serviceConnection);
|
|
||||||
isActivityCheckerServiceBound = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public BottomNavigationView getBottomNavView() {
|
|
||||||
return binding.bottomNavView;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCollapsingView(@NonNull final View view) {
|
|
||||||
binding.collapsingToolbarLayout.addView(view, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeCollapsingView(@NonNull final View view) {
|
|
||||||
binding.collapsingToolbarLayout.removeView(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setToolbar(final Toolbar toolbar) {
|
|
||||||
binding.appBarLayout.setVisibility(View.GONE);
|
|
||||||
removeScrollingBehaviour();
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
setupNavigation(toolbar, currentNavControllerLiveData.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetToolbar() {
|
|
||||||
binding.appBarLayout.setVisibility(View.VISIBLE);
|
|
||||||
setScrollingBehaviour();
|
|
||||||
setSupportActionBar(binding.toolbar);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
setupNavigation(binding.toolbar, currentNavControllerLiveData.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public CollapsingToolbarLayout getCollapsingToolbarView() {
|
|
||||||
return binding.collapsingToolbarLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AppBarLayout getAppbarLayout() {
|
|
||||||
return binding.appBarLayout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeLayoutTransition() {
|
|
||||||
binding.getRoot().setLayoutTransition(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLayoutTransition() {
|
|
||||||
binding.getRoot().setLayoutTransition(new LayoutTransition());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initEmojiCompat() {
|
|
||||||
// Use a downloadable font for EmojiCompat
|
|
||||||
final FontRequest fontRequest = new FontRequest(
|
|
||||||
"com.google.android.gms.fonts",
|
|
||||||
"com.google.android.gms",
|
|
||||||
"Noto Color Emoji Compat",
|
|
||||||
R.array.com_google_android_gms_fonts_certs);
|
|
||||||
final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(getApplicationContext(), fontRequest);
|
|
||||||
config.setReplaceAll(true)
|
|
||||||
// .setUseEmojiAsDefaultStyle(true)
|
|
||||||
.registerInitCallback(new EmojiCompat.InitCallback() {
|
|
||||||
@Override
|
|
||||||
public void onInitialized() {
|
|
||||||
Log.i(TAG, "EmojiCompat initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailed(@Nullable Throwable throwable) {
|
|
||||||
Log.e(TAG, "EmojiCompat initialization failed", throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
EmojiCompat.init(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Toolbar getToolbar() {
|
|
||||||
return binding.toolbar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public View getRootView() {
|
|
||||||
return binding.getRoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Tab> getCurrentTabs() {
|
|
||||||
return currentTabs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) {
|
|
||||||
// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void setNavBarDMUnreadCountBadge(final int unseenCount) {
|
|
||||||
final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
|
|
||||||
if (badge == null) return;
|
|
||||||
if (unseenCount == 0) {
|
|
||||||
badge.setVisible(false);
|
|
||||||
badge.clearNumber();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (badge.getVerticalOffset() != 10) {
|
|
||||||
badge.setVerticalOffset(10);
|
|
||||||
}
|
|
||||||
badge.setNumber(unseenCount);
|
|
||||||
badge.setVisible(true);
|
|
||||||
}
|
|
||||||
}
|
|
821
app/src/main/java/awais/instagrabber/activities/MainActivity.kt
Normal file
821
app/src/main/java/awais/instagrabber/activities/MainActivity.kt
Normal file
@ -0,0 +1,821 @@
|
|||||||
|
package awais.instagrabber.activities
|
||||||
|
|
||||||
|
import android.animation.LayoutTransition
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.os.*
|
||||||
|
import android.text.Editable
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.IdRes
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.Toolbar
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.provider.FontRequest
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.emoji.text.EmojiCompat
|
||||||
|
import androidx.emoji.text.EmojiCompat.InitCallback
|
||||||
|
import androidx.emoji.text.FontRequestEmojiCompatConfig
|
||||||
|
import androidx.fragment.app.FragmentManager
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavController.OnDestinationChangedListener
|
||||||
|
import androidx.navigation.NavDestination
|
||||||
|
import androidx.navigation.ui.NavigationUI
|
||||||
|
import awais.instagrabber.BuildConfig
|
||||||
|
import awais.instagrabber.R
|
||||||
|
import awais.instagrabber.customviews.emoji.EmojiVariantManager
|
||||||
|
import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback
|
||||||
|
import awais.instagrabber.customviews.helpers.TextWatcherAdapter
|
||||||
|
import awais.instagrabber.databinding.ActivityMainBinding
|
||||||
|
import awais.instagrabber.fragments.PostViewV2Fragment
|
||||||
|
import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys
|
||||||
|
import awais.instagrabber.models.IntentModel
|
||||||
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.models.Tab
|
||||||
|
import awais.instagrabber.models.enums.IntentModelType
|
||||||
|
import awais.instagrabber.services.ActivityCheckerService
|
||||||
|
import awais.instagrabber.services.DMSyncAlarmReceiver
|
||||||
|
import awais.instagrabber.utils.*
|
||||||
|
import awais.instagrabber.utils.AppExecutors.tasksThread
|
||||||
|
import awais.instagrabber.utils.TextUtils.isEmpty
|
||||||
|
import awais.instagrabber.utils.TextUtils.shortcodeToId
|
||||||
|
import awais.instagrabber.utils.emoji.EmojiParser
|
||||||
|
import awais.instagrabber.viewmodels.AppStateViewModel
|
||||||
|
import awais.instagrabber.viewmodels.DirectInboxViewModel
|
||||||
|
import awais.instagrabber.webservices.GraphQLRepository
|
||||||
|
import awais.instagrabber.webservices.MediaRepository
|
||||||
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
|
||||||
|
import com.google.android.material.appbar.CollapsingToolbarLayout
|
||||||
|
import com.google.android.material.bottomnavigation.BottomNavigationView
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.Iterators
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.util.*
|
||||||
|
import java.util.stream.Collectors
|
||||||
|
|
||||||
|
class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener {
|
||||||
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
|
private var currentNavControllerLiveData: LiveData<NavController>? = null
|
||||||
|
private var searchMenuItem: MenuItem? = null
|
||||||
|
private var firstFragmentGraphIndex = 0
|
||||||
|
private var lastSelectedNavMenuId = 0
|
||||||
|
private var isActivityCheckerServiceBound = false
|
||||||
|
private var isBackStackEmpty = false
|
||||||
|
private var isLoggedIn = false
|
||||||
|
private var deviceUuid: String? = null
|
||||||
|
private var csrfToken: String? = null
|
||||||
|
private var userId: Long = 0
|
||||||
|
|
||||||
|
// private var behavior: HideBottomViewOnScrollBehavior<BottomNavigationView>? = null
|
||||||
|
var currentTabs: List<Tab> = emptyList()
|
||||||
|
private set
|
||||||
|
private var showBottomViewDestinations: List<Int> = emptyList()
|
||||||
|
|
||||||
|
private val serviceConnection: ServiceConnection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
|
// final ActivityCheckerService.LocalBinder binder = (ActivityCheckerService.LocalBinder) service;
|
||||||
|
// final ActivityCheckerService activityCheckerService = binder.getService();
|
||||||
|
isActivityCheckerServiceBound = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
isActivityCheckerServiceBound = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
|
||||||
|
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
instance = this
|
||||||
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||||
|
setupCookie()
|
||||||
|
if (Utils.settingsHelper.getBoolean(PreferenceKeys.FLAG_SECURE)) {
|
||||||
|
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
}
|
||||||
|
setContentView(binding.root)
|
||||||
|
setSupportActionBar(binding.toolbar)
|
||||||
|
setupInsetsCallback()
|
||||||
|
createNotificationChannels()
|
||||||
|
// try {
|
||||||
|
// val layoutParams = binding.bottomNavView.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
// @Suppress("UNCHECKED_CAST")
|
||||||
|
// behavior = layoutParams.behavior as HideBottomViewOnScrollBehavior<BottomNavigationView>
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// Log.e(TAG, "onCreate: ", e)
|
||||||
|
// }
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
setupBottomNavigationBar(true)
|
||||||
|
}
|
||||||
|
if (!BuildConfig.isPre) {
|
||||||
|
val checkUpdates = Utils.settingsHelper.getBoolean(PreferenceKeys.CHECK_UPDATES)
|
||||||
|
if (checkUpdates) FlavorTown.updateCheck(this)
|
||||||
|
}
|
||||||
|
FlavorTown.changelogCheck(this)
|
||||||
|
ViewModelProvider(this).get(AppStateViewModel::class.java) // Just initiate the App state here
|
||||||
|
handleIntent(intent)
|
||||||
|
if (isLoggedIn && Utils.settingsHelper.getBoolean(PreferenceKeys.CHECK_ACTIVITY)) {
|
||||||
|
bindActivityCheckerService()
|
||||||
|
}
|
||||||
|
supportFragmentManager.addOnBackStackChangedListener(this)
|
||||||
|
// Initialise the internal map
|
||||||
|
tasksThread.execute {
|
||||||
|
EmojiParser.getInstance(this)
|
||||||
|
EmojiVariantManager.getInstance()
|
||||||
|
}
|
||||||
|
initEmojiCompat()
|
||||||
|
// initDmService();
|
||||||
|
initDmUnreadCount()
|
||||||
|
initSearchInput()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupInsetsCallback() {
|
||||||
|
val deferringInsetsCallback = RootViewDeferringInsetsCallback(
|
||||||
|
WindowInsetsCompat.Type.systemBars(),
|
||||||
|
WindowInsetsCompat.Type.ime()
|
||||||
|
)
|
||||||
|
ViewCompat.setWindowInsetsAnimationCallback(binding.root, deferringInsetsCallback)
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root, deferringInsetsCallback)
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupCookie() {
|
||||||
|
val cookie = Utils.settingsHelper.getString(Constants.COOKIE)
|
||||||
|
userId = 0
|
||||||
|
csrfToken = null
|
||||||
|
if (cookie.isNotBlank()) {
|
||||||
|
userId = getUserIdFromCookie(cookie)
|
||||||
|
csrfToken = getCsrfTokenFromCookie(cookie)
|
||||||
|
}
|
||||||
|
if (cookie.isBlank() || userId == 0L || csrfToken.isNullOrBlank()) {
|
||||||
|
isLoggedIn = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID)
|
||||||
|
if (isEmpty(deviceUuid)) {
|
||||||
|
Utils.settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString())
|
||||||
|
}
|
||||||
|
setupCookies(cookie)
|
||||||
|
isLoggedIn = true
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
private fun initDmService() {
|
||||||
|
if (!isLoggedIn) return
|
||||||
|
val enabled = Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH)
|
||||||
|
if (!enabled) return
|
||||||
|
DMSyncAlarmReceiver.setAlarm(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initDmUnreadCount() {
|
||||||
|
if (!isLoggedIn) return
|
||||||
|
val directInboxViewModel = ViewModelProvider(this).get(DirectInboxViewModel::class.java)
|
||||||
|
directInboxViewModel.unseenCount.observe(this, { unseenCountResource: Resource<Int?>? ->
|
||||||
|
if (unseenCountResource == null) return@observe
|
||||||
|
val unseenCount = unseenCountResource.data
|
||||||
|
setNavBarDMUnreadCountBadge(unseenCount ?: 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initSearchInput() {
|
||||||
|
binding.searchInputLayout.setEndIconOnClickListener {
|
||||||
|
val editText = binding.searchInputLayout.editText ?: return@setEndIconOnClickListener
|
||||||
|
editText.setText("")
|
||||||
|
}
|
||||||
|
binding.searchInputLayout.addOnEditTextAttachedListener { textInputLayout: TextInputLayout ->
|
||||||
|
textInputLayout.isEndIconVisible = false
|
||||||
|
val editText = textInputLayout.editText ?: return@addOnEditTextAttachedListener
|
||||||
|
editText.addTextChangedListener(object : TextWatcherAdapter() {
|
||||||
|
override fun afterTextChanged(s: Editable) {
|
||||||
|
binding.searchInputLayout.isEndIconVisible = !isEmpty(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
|
menuInflater.inflate(R.menu.main_menu, menu)
|
||||||
|
searchMenuItem = menu.findItem(R.id.search)
|
||||||
|
val navController = currentNavControllerLiveData?.value
|
||||||
|
if (navController != null) {
|
||||||
|
val currentDestination = navController.currentDestination
|
||||||
|
if (currentDestination != null) {
|
||||||
|
@SuppressLint("RestrictedApi") val backStack = navController.backStack
|
||||||
|
setupMenu(backStack.size, currentDestination.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if (binding.searchInputLayout.getVisibility() == View.VISIBLE) {
|
||||||
|
// searchMenuItem.setVisible(false).setEnabled(false);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// searchMenuItem.setVisible(true).setEnabled(true);
|
||||||
|
// if (showSearch && currentNavControllerLiveData != null) {
|
||||||
|
// final NavController navController = currentNavControllerLiveData.getValue();
|
||||||
|
// if (navController != null) {
|
||||||
|
// final NavDestination currentDestination = navController.getCurrentDestination();
|
||||||
|
// if (currentDestination != null) {
|
||||||
|
// final int destinationId = currentDestination.getId();
|
||||||
|
// showSearch = destinationId == R.id.profileFragment;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (!showSearch) {
|
||||||
|
// searchMenuItem.setVisible(false);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return setupSearchView();
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.search) {
|
||||||
|
val navController = currentNavControllerLiveData?.value ?: return false
|
||||||
|
try {
|
||||||
|
navController.navigate(R.id.action_global_search)
|
||||||
|
return true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onOptionsItemSelected: ", e)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, firstFragmentGraphIndex.toString())
|
||||||
|
outState.putString(LAST_SELECT_NAV_MENU_ID, binding.bottomNavView.selectedItemId.toString())
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState)
|
||||||
|
val key = savedInstanceState[FIRST_FRAGMENT_GRAPH_INDEX_KEY] as String?
|
||||||
|
if (key != null) {
|
||||||
|
try {
|
||||||
|
firstFragmentGraphIndex = key.toInt()
|
||||||
|
} catch (ignored: NumberFormatException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val lastSelected = savedInstanceState[LAST_SELECT_NAV_MENU_ID] as String?
|
||||||
|
if (lastSelected != null) {
|
||||||
|
try {
|
||||||
|
lastSelectedNavMenuId = lastSelected.toInt()
|
||||||
|
} catch (ignored: NumberFormatException) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setupBottomNavigationBar(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSupportNavigateUp(): Boolean {
|
||||||
|
if (currentNavControllerLiveData == null) return false
|
||||||
|
val navController = currentNavControllerLiveData?.value ?: return false
|
||||||
|
return navController.navigateUp()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
handleIntent(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
try {
|
||||||
|
super.onDestroy()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onDestroy: ", e)
|
||||||
|
}
|
||||||
|
unbindActivityCheckerService()
|
||||||
|
// try {
|
||||||
|
// RetrofitFactory.getInstance().destroy()
|
||||||
|
// } catch (e: Exception) {
|
||||||
|
// Log.e(TAG, "onDestroy: ", e)
|
||||||
|
// }
|
||||||
|
instance = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
var currentNavControllerBackStack = 2
|
||||||
|
currentNavControllerLiveData?.let {
|
||||||
|
val navController = it.value
|
||||||
|
if (navController != null) {
|
||||||
|
@SuppressLint("RestrictedApi") val backStack = navController.backStack
|
||||||
|
currentNavControllerBackStack = backStack.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isTaskRoot && isBackStackEmpty && currentNavControllerBackStack == 2) {
|
||||||
|
finishAfterTransition()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isFinishing) {
|
||||||
|
try {
|
||||||
|
super.onBackPressed()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "onBackPressed: ", e)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackStackChanged() {
|
||||||
|
val backStackEntryCount = supportFragmentManager.backStackEntryCount
|
||||||
|
isBackStackEmpty = backStackEntryCount == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNotificationChannels() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||||
|
val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||||
|
notificationManager.createNotificationChannel(NotificationChannel(
|
||||||
|
Constants.DOWNLOAD_CHANNEL_ID,
|
||||||
|
Constants.DOWNLOAD_CHANNEL_NAME,
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
))
|
||||||
|
notificationManager.createNotificationChannel(NotificationChannel(
|
||||||
|
Constants.ACTIVITY_CHANNEL_ID,
|
||||||
|
Constants.ACTIVITY_CHANNEL_NAME,
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
))
|
||||||
|
notificationManager.createNotificationChannel(NotificationChannel(
|
||||||
|
Constants.DM_UNREAD_CHANNEL_ID,
|
||||||
|
Constants.DM_UNREAD_CHANNEL_NAME,
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
))
|
||||||
|
val silentNotificationChannel = NotificationChannel(
|
||||||
|
Constants.SILENT_NOTIFICATIONS_CHANNEL_ID,
|
||||||
|
Constants.SILENT_NOTIFICATIONS_CHANNEL_NAME,
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
)
|
||||||
|
silentNotificationChannel.setSound(null, null)
|
||||||
|
notificationManager.createNotificationChannel(silentNotificationChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupBottomNavigationBar(setDefaultTabFromSettings: Boolean) {
|
||||||
|
currentTabs = if (!isLoggedIn) setupAnonBottomNav() else setupMainBottomNav()
|
||||||
|
val mainNavList = currentTabs.stream()
|
||||||
|
.map(Tab::navigationResId)
|
||||||
|
.collect(Collectors.toList())
|
||||||
|
showBottomViewDestinations = currentTabs.asSequence().map {
|
||||||
|
it.startDestinationFragmentId
|
||||||
|
}.toMutableList().apply {
|
||||||
|
add(R.id.postViewFragment)
|
||||||
|
add(R.id.favoritesFragment)
|
||||||
|
}
|
||||||
|
if (setDefaultTabFromSettings) {
|
||||||
|
setSelectedTab(currentTabs)
|
||||||
|
} else {
|
||||||
|
binding.bottomNavView.selectedItemId = lastSelectedNavMenuId
|
||||||
|
}
|
||||||
|
val navControllerLiveData = NavigationExtensions.setupWithNavController(
|
||||||
|
binding.bottomNavView,
|
||||||
|
mainNavList,
|
||||||
|
supportFragmentManager,
|
||||||
|
R.id.main_nav_host,
|
||||||
|
intent,
|
||||||
|
firstFragmentGraphIndex)
|
||||||
|
navControllerLiveData.observe(this, { navController: NavController? -> setupNavigation(binding.toolbar, navController) })
|
||||||
|
currentNavControllerLiveData = navControllerLiveData
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setSelectedTab(tabs: List<Tab>) {
|
||||||
|
val defaultTabResNameString = Utils.settingsHelper.getString(Constants.DEFAULT_TAB)
|
||||||
|
try {
|
||||||
|
var navId = 0
|
||||||
|
if (!isEmpty(defaultTabResNameString)) {
|
||||||
|
navId = resources.getIdentifier(defaultTabResNameString, "navigation", packageName)
|
||||||
|
}
|
||||||
|
val navGraph = if (isLoggedIn) R.navigation.feed_nav_graph else R.navigation.profile_nav_graph
|
||||||
|
val defaultNavId = if (navId <= 0) navGraph else navId
|
||||||
|
var index = Iterators.indexOf(tabs.iterator()) { tab: Tab? ->
|
||||||
|
if (tab == null) return@indexOf false
|
||||||
|
tab.navigationResId == defaultNavId
|
||||||
|
}
|
||||||
|
if (index < 0 || index >= tabs.size) index = 0
|
||||||
|
firstFragmentGraphIndex = index
|
||||||
|
setBottomNavSelectedTab(tabs[index])
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error parsing id", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAnonBottomNav(): List<Tab> {
|
||||||
|
val selectedItemId = binding.bottomNavView.selectedItemId
|
||||||
|
val favoriteTab = Tab(R.drawable.ic_star_24,
|
||||||
|
getString(R.string.title_favorites),
|
||||||
|
false,
|
||||||
|
"favorites_nav_graph",
|
||||||
|
R.navigation.favorites_nav_graph,
|
||||||
|
R.id.favorites_nav_graph,
|
||||||
|
R.id.favoritesFragment)
|
||||||
|
val profileTab = Tab(R.drawable.ic_person_24,
|
||||||
|
getString(R.string.profile),
|
||||||
|
false,
|
||||||
|
"profile_nav_graph",
|
||||||
|
R.navigation.profile_nav_graph,
|
||||||
|
R.id.profile_nav_graph,
|
||||||
|
R.id.profileFragment)
|
||||||
|
val moreTab = Tab(R.drawable.ic_more_horiz_24,
|
||||||
|
getString(R.string.more),
|
||||||
|
false,
|
||||||
|
"more_nav_graph",
|
||||||
|
R.navigation.more_nav_graph,
|
||||||
|
R.id.more_nav_graph,
|
||||||
|
R.id.morePreferencesFragment)
|
||||||
|
val menu = binding.bottomNavView.menu
|
||||||
|
menu.clear()
|
||||||
|
menu.add(0, favoriteTab.navigationRootId, 0, favoriteTab.title).setIcon(favoriteTab.iconResId)
|
||||||
|
menu.add(0, profileTab.navigationRootId, 0, profileTab.title).setIcon(profileTab.iconResId)
|
||||||
|
menu.add(0, moreTab.navigationRootId, 0, moreTab.title).setIcon(moreTab.iconResId)
|
||||||
|
if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph && selectedItemId != R.id.favorites_nav_graph) {
|
||||||
|
setBottomNavSelectedTab(profileTab)
|
||||||
|
}
|
||||||
|
return ImmutableList.of(favoriteTab, profileTab, moreTab)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupMainBottomNav(): List<Tab> {
|
||||||
|
val menu = binding.bottomNavView.menu
|
||||||
|
menu.clear()
|
||||||
|
val navTabList = Utils.getNavTabList(this).first
|
||||||
|
for ((iconResId, title, _, _, _, navigationRootId) in navTabList) {
|
||||||
|
menu.add(0, navigationRootId, 0, title).setIcon(iconResId)
|
||||||
|
}
|
||||||
|
return navTabList
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setBottomNavSelectedTab(tab: Tab) {
|
||||||
|
binding.bottomNavView.selectedItemId = tab.navigationRootId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setBottomNavSelectedTab(@IdRes navGraphRootId: Int) {
|
||||||
|
binding.bottomNavView.selectedItemId = navGraphRootId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupNavigation(toolbar: Toolbar, navController: NavController?) {
|
||||||
|
if (navController == null) return
|
||||||
|
NavigationUI.setupWithNavController(toolbar, navController)
|
||||||
|
navController.addOnDestinationChangedListener(OnDestinationChangedListener { _: NavController?, destination: NavDestination, arguments: Bundle? ->
|
||||||
|
if (destination.id == R.id.directMessagesThreadFragment && arguments != null) {
|
||||||
|
// Set the thread title earlier for better ux
|
||||||
|
val title = arguments.getString("title")
|
||||||
|
val actionBar = supportActionBar
|
||||||
|
if (actionBar != null && !isEmpty(title)) {
|
||||||
|
actionBar.title = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// below is a hack to check if we are at the end of the current stack, to setup the search view
|
||||||
|
binding.appBarLayout.setExpanded(true, true)
|
||||||
|
val destinationId = destination.id
|
||||||
|
@SuppressLint("RestrictedApi") val backStack = navController.backStack
|
||||||
|
setupMenu(backStack.size, destinationId)
|
||||||
|
val contains = showBottomViewDestinations.contains(destinationId)
|
||||||
|
binding.root.post {
|
||||||
|
binding.bottomNavView.visibility = if (contains) View.VISIBLE else View.GONE
|
||||||
|
// if (contains) {
|
||||||
|
// behavior?.slideUp(binding.bottomNavView)
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// explicitly hide keyboard when we navigate
|
||||||
|
val view = currentFocus
|
||||||
|
Utils.hideKeyboard(view)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupMenu(backStackSize: Int, destinationId: Int) {
|
||||||
|
val searchMenuItem = searchMenuItem ?: return
|
||||||
|
if (backStackSize >= 2 && SEARCH_VISIBLE_DESTINATIONS.contains(destinationId)) {
|
||||||
|
searchMenuItem.isVisible = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
searchMenuItem.isVisible = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setScrollingBehaviour() {
|
||||||
|
val layoutParams = binding.mainNavHost.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
layoutParams.behavior = ScrollingViewBehavior()
|
||||||
|
binding.mainNavHost.requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeScrollingBehaviour() {
|
||||||
|
val layoutParams = binding.mainNavHost.layoutParams as CoordinatorLayout.LayoutParams
|
||||||
|
layoutParams.behavior = null
|
||||||
|
binding.mainNavHost.requestLayout()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleIntent(intent: Intent?) {
|
||||||
|
if (intent == null) return
|
||||||
|
val action = intent.action
|
||||||
|
val type = intent.type
|
||||||
|
// Log.d(TAG, action + " " + type);
|
||||||
|
if (Intent.ACTION_MAIN == action) return
|
||||||
|
if (Constants.ACTION_SHOW_ACTIVITY == action) {
|
||||||
|
showActivityView()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Constants.ACTION_SHOW_DM_THREAD == action) {
|
||||||
|
showThread(intent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Intent.ACTION_SEND == action && type != null) {
|
||||||
|
if (type == "text/plain") {
|
||||||
|
handleUrl(intent.getStringExtra(Intent.EXTRA_TEXT))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Intent.ACTION_VIEW == action) {
|
||||||
|
val data = intent.data ?: return
|
||||||
|
handleUrl(data.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showThread(intent: Intent) {
|
||||||
|
val threadId = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_ID)
|
||||||
|
val threadTitle = intent.getStringExtra(Constants.DM_THREAD_ACTION_EXTRA_THREAD_TITLE)
|
||||||
|
navigateToThread(threadId, threadTitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigateToThread(threadId: String?, threadTitle: String?) {
|
||||||
|
if (threadId == null || threadTitle == null) return
|
||||||
|
currentNavControllerLiveData?.observe(this, object : Observer<NavController?> {
|
||||||
|
override fun onChanged(navController: NavController?) {
|
||||||
|
if (navController == null) return
|
||||||
|
if (navController.graph.id != R.id.direct_messages_nav_graph) return
|
||||||
|
try {
|
||||||
|
val currentDestination = navController.currentDestination
|
||||||
|
if (currentDestination != null && currentDestination.id == R.id.directMessagesInboxFragment) {
|
||||||
|
// if we are already on the inbox page, navigate to the thread
|
||||||
|
// need handler.post() to wait for the fragment manager to be ready to navigate
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
val action = DirectMessageInboxFragmentDirections
|
||||||
|
.actionInboxToThread(threadId, threadTitle)
|
||||||
|
navController.navigate(action)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// add a destination change listener to navigate to thread once we are on the inbox page
|
||||||
|
navController.addOnDestinationChangedListener(object : OnDestinationChangedListener {
|
||||||
|
override fun onDestinationChanged(
|
||||||
|
controller: NavController,
|
||||||
|
destination: NavDestination,
|
||||||
|
arguments: Bundle?,
|
||||||
|
) {
|
||||||
|
if (destination.id == R.id.directMessagesInboxFragment) {
|
||||||
|
val action = DirectMessageInboxFragmentDirections
|
||||||
|
.actionInboxToThread(threadId, threadTitle)
|
||||||
|
controller.navigate(action)
|
||||||
|
controller.removeOnDestinationChangedListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// pop back stack until we reach the inbox page
|
||||||
|
navController.popBackStack(R.id.directMessagesInboxFragment, false)
|
||||||
|
} finally {
|
||||||
|
currentNavControllerLiveData?.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
val selectedItemId = binding.bottomNavView.selectedItemId
|
||||||
|
if (selectedItemId != R.navigation.direct_messages_nav_graph) {
|
||||||
|
setBottomNavSelectedTab(R.id.direct_messages_nav_graph)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleUrl(url: String?) {
|
||||||
|
if (url == null) return
|
||||||
|
// Log.d(TAG, url);
|
||||||
|
val intentModel = IntentUtils.parseUrl(url) ?: return
|
||||||
|
showView(intentModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showView(intentModel: IntentModel) {
|
||||||
|
when (intentModel.type) {
|
||||||
|
IntentModelType.USERNAME -> showProfileView(intentModel)
|
||||||
|
IntentModelType.POST -> showPostView(intentModel)
|
||||||
|
IntentModelType.LOCATION -> showLocationView(intentModel)
|
||||||
|
IntentModelType.HASHTAG -> showHashtagView(intentModel)
|
||||||
|
IntentModelType.UNKNOWN -> Log.w(TAG, "Unknown model type received!")
|
||||||
|
// else -> Log.w(TAG, "Unknown model type received!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showProfileView(intentModel: IntentModel) {
|
||||||
|
val username = intentModel.text
|
||||||
|
// Log.d(TAG, "username: " + username);
|
||||||
|
val currentNavControllerLiveData = currentNavControllerLiveData ?: return
|
||||||
|
val navController = currentNavControllerLiveData.value
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("username", "@$username")
|
||||||
|
try {
|
||||||
|
navController?.navigate(R.id.action_global_profileFragment, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "showProfileView: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showPostView(intentModel: IntentModel) {
|
||||||
|
val shortCode = intentModel.text
|
||||||
|
// Log.d(TAG, "shortCode: " + shortCode);
|
||||||
|
val alertDialog = AlertDialog.Builder(this)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setView(R.layout.dialog_opening_post)
|
||||||
|
.create()
|
||||||
|
alertDialog.show()
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
val media = if (isLoggedIn) mediaRepository.fetch(shortcodeToId(shortCode)) else graphQLRepository.fetchPost(shortCode)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
if (media == null) {
|
||||||
|
Toast.makeText(applicationContext, R.string.post_not_found, Toast.LENGTH_SHORT).show()
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
val currentNavControllerLiveData = currentNavControllerLiveData ?: return@withContext
|
||||||
|
val navController = currentNavControllerLiveData.value
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media)
|
||||||
|
try {
|
||||||
|
navController?.navigate(R.id.action_global_post_view, bundle)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "showPostView: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "showPostView: ", e)
|
||||||
|
} finally {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
alertDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLocationView(intentModel: IntentModel) {
|
||||||
|
val locationId = intentModel.text
|
||||||
|
// Log.d(TAG, "locationId: " + locationId);
|
||||||
|
val currentNavControllerLiveData = currentNavControllerLiveData ?: return
|
||||||
|
val navController = currentNavControllerLiveData.value
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putLong("locationId", locationId.toLong())
|
||||||
|
navController?.navigate(R.id.action_global_locationFragment, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showHashtagView(intentModel: IntentModel) {
|
||||||
|
val hashtag = intentModel.text
|
||||||
|
// Log.d(TAG, "hashtag: " + hashtag);
|
||||||
|
val currentNavControllerLiveData = currentNavControllerLiveData ?: return
|
||||||
|
val navController = currentNavControllerLiveData.value
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("hashtag", hashtag)
|
||||||
|
navController?.navigate(R.id.action_global_hashTagFragment, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showActivityView() {
|
||||||
|
val currentNavControllerLiveData = currentNavControllerLiveData ?: return
|
||||||
|
val navController = currentNavControllerLiveData.value
|
||||||
|
val bundle = Bundle()
|
||||||
|
bundle.putString("type", "notif")
|
||||||
|
navController?.navigate(R.id.action_global_notificationsViewerFragment, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindActivityCheckerService() {
|
||||||
|
bindService(Intent(this, ActivityCheckerService::class.java), serviceConnection, BIND_AUTO_CREATE)
|
||||||
|
isActivityCheckerServiceBound = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unbindActivityCheckerService() {
|
||||||
|
if (!isActivityCheckerServiceBound) return
|
||||||
|
unbindService(serviceConnection)
|
||||||
|
isActivityCheckerServiceBound = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val bottomNavView: BottomNavigationView
|
||||||
|
get() = binding.bottomNavView
|
||||||
|
|
||||||
|
fun setCollapsingView(view: View) {
|
||||||
|
try {
|
||||||
|
binding.collapsingToolbarLayout.addView(view, 0)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "setCollapsingView: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCollapsingView(view: View) {
|
||||||
|
try {
|
||||||
|
binding.collapsingToolbarLayout.removeView(view)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "removeCollapsingView: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetToolbar() {
|
||||||
|
binding.appBarLayout.visibility = View.VISIBLE
|
||||||
|
setScrollingBehaviour()
|
||||||
|
setSupportActionBar(binding.toolbar)
|
||||||
|
val currentNavControllerLiveData = currentNavControllerLiveData ?: return
|
||||||
|
setupNavigation(binding.toolbar, currentNavControllerLiveData.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
val collapsingToolbarView: CollapsingToolbarLayout
|
||||||
|
get() = binding.collapsingToolbarLayout
|
||||||
|
val appbarLayout: AppBarLayout
|
||||||
|
get() = binding.appBarLayout
|
||||||
|
|
||||||
|
fun removeLayoutTransition() {
|
||||||
|
binding.root.layoutTransition = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setLayoutTransition() {
|
||||||
|
binding.root.layoutTransition = LayoutTransition()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEmojiCompat() {
|
||||||
|
// Use a downloadable font for EmojiCompat
|
||||||
|
val fontRequest = FontRequest(
|
||||||
|
"com.google.android.gms.fonts",
|
||||||
|
"com.google.android.gms",
|
||||||
|
"Noto Color Emoji Compat",
|
||||||
|
R.array.com_google_android_gms_fonts_certs)
|
||||||
|
val config: EmojiCompat.Config = FontRequestEmojiCompatConfig(applicationContext, fontRequest)
|
||||||
|
config.setReplaceAll(true) // .setUseEmojiAsDefaultStyle(true)
|
||||||
|
.registerInitCallback(object : InitCallback() {
|
||||||
|
override fun onInitialized() {
|
||||||
|
Log.i(TAG, "EmojiCompat initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailed(throwable: Throwable?) {
|
||||||
|
Log.e(TAG, "EmojiCompat initialization failed", throwable)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
EmojiCompat.init(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
var toolbar: Toolbar
|
||||||
|
get() = binding.toolbar
|
||||||
|
set(toolbar) {
|
||||||
|
binding.appBarLayout.visibility = View.GONE
|
||||||
|
removeScrollingBehaviour()
|
||||||
|
setSupportActionBar(toolbar)
|
||||||
|
if (currentNavControllerLiveData == null) return
|
||||||
|
setupNavigation(toolbar, currentNavControllerLiveData?.value)
|
||||||
|
}
|
||||||
|
val rootView: View
|
||||||
|
get() = binding.root
|
||||||
|
|
||||||
|
private fun setNavBarDMUnreadCountBadge(unseenCount: Int) {
|
||||||
|
val badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph)
|
||||||
|
if (unseenCount == 0) {
|
||||||
|
badge.isVisible = false
|
||||||
|
badge.clearNumber()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (badge.verticalOffset != 10) {
|
||||||
|
badge.verticalOffset = 10
|
||||||
|
}
|
||||||
|
badge.number = unseenCount
|
||||||
|
badge.isVisible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSearchView(): TextInputLayout {
|
||||||
|
binding.searchInputLayout.visibility = View.VISIBLE
|
||||||
|
return binding.searchInputLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideSearchView() {
|
||||||
|
binding.searchInputLayout.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "MainActivity"
|
||||||
|
private const val FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"
|
||||||
|
private const val LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId"
|
||||||
|
private val SEARCH_VISIBLE_DESTINATIONS: List<Int> = ImmutableList.of(
|
||||||
|
R.id.feedFragment,
|
||||||
|
R.id.profileFragment,
|
||||||
|
R.id.directMessagesInboxFragment,
|
||||||
|
R.id.discoverFragment,
|
||||||
|
R.id.favoritesFragment,
|
||||||
|
R.id.hashTagFragment,
|
||||||
|
R.id.locationFragment
|
||||||
|
)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
var instance: MainActivity? = null
|
||||||
|
private set
|
||||||
|
}
|
||||||
|
}
|
@ -1,195 +1,60 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Objects;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.comments.ChildCommentViewHolder;
|
import awais.instagrabber.adapters.viewholder.CommentViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.comments.ParentCommentViewHolder;
|
|
||||||
import awais.instagrabber.databinding.ItemCommentBinding;
|
import awais.instagrabber.databinding.ItemCommentBinding;
|
||||||
import awais.instagrabber.databinding.ItemCommentSmallBinding;
|
import awais.instagrabber.models.Comment;
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
|
|
||||||
public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerView.ViewHolder> {
|
public final class CommentsAdapter extends ListAdapter<Comment, CommentViewHolder> {
|
||||||
private static final int TYPE_PARENT = 1;
|
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() {
|
||||||
private static final int TYPE_CHILD = 2;
|
|
||||||
|
|
||||||
private final Map<Integer, Integer> positionTypeMap = new HashMap<>();
|
|
||||||
|
|
||||||
// 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.size();
|
|
||||||
// if (commentModels != null && commentsLen > 0 && !TextUtils.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 List<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 List) {
|
|
||||||
// //noinspection unchecked
|
|
||||||
// filteredCommentModels = (List<CommentModel>) results.values;
|
|
||||||
// notifyDataSetChanged();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
private static final DiffUtil.ItemCallback<CommentModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<CommentModel>() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
|
public boolean areItemsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
|
||||||
return oldItem.getId().equals(newItem.getId());
|
return Objects.equals(oldItem.getPk(), newItem.getPk());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
|
public boolean areContentsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
|
||||||
return oldItem.getId().equals(newItem.getId());
|
return Objects.equals(oldItem, newItem);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final CommentCallback commentCallback;
|
|
||||||
private CommentModel selected, toChangeLike;
|
|
||||||
private int selectedIndex, likedIndex;
|
|
||||||
|
|
||||||
public CommentsAdapter(final CommentCallback commentCallback) {
|
private final boolean showingReplies;
|
||||||
|
private final CommentCallback commentCallback;
|
||||||
|
private final long currentUserId;
|
||||||
|
|
||||||
|
public CommentsAdapter(final long currentUserId,
|
||||||
|
final boolean showingReplies,
|
||||||
|
final CommentCallback commentCallback) {
|
||||||
super(DIFF_CALLBACK);
|
super(DIFF_CALLBACK);
|
||||||
|
this.showingReplies = showingReplies;
|
||||||
|
this.currentUserId = currentUserId;
|
||||||
this.commentCallback = commentCallback;
|
this.commentCallback = commentCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
||||||
final Context context = parent.getContext();
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(context);
|
|
||||||
if (type == TYPE_PARENT) {
|
|
||||||
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
|
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
|
||||||
return new ParentCommentViewHolder(binding);
|
return new CommentViewHolder(binding, currentUserId, commentCallback);
|
||||||
}
|
|
||||||
final ItemCommentSmallBinding binding = ItemCommentSmallBinding.inflate(layoutInflater, parent, false);
|
|
||||||
return new ChildCommentViewHolder(binding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) {
|
||||||
CommentModel commentModel = getItem(position);
|
final Comment comment = getItem(position);
|
||||||
if (commentModel == null) return;
|
holder.bind(comment, showingReplies && position == 0, showingReplies && position != 0);
|
||||||
final int type = getItemViewType(position);
|
|
||||||
final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId());
|
|
||||||
final boolean toLike = this.toChangeLike != null && this.toChangeLike.getId().equals(commentModel.getId());
|
|
||||||
if (toLike) commentModel = this.toChangeLike;
|
|
||||||
if (type == TYPE_PARENT) {
|
|
||||||
final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder;
|
|
||||||
viewHolder.bind(commentModel, selected, commentCallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final ChildCommentViewHolder viewHolder = (ChildCommentViewHolder) holder;
|
|
||||||
viewHolder.bind(commentModel, selected, commentCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void submitList(@Nullable final List<CommentModel> list) {
|
|
||||||
final List<CommentModel> flatList = flattenList(list);
|
|
||||||
super.submitList(flatList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void submitList(@Nullable final List<CommentModel> list, @Nullable final Runnable commitCallback) {
|
|
||||||
final List<CommentModel> flatList = flattenList(list);
|
|
||||||
super.submitList(flatList, commitCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CommentModel> flattenList(final List<CommentModel> list) {
|
|
||||||
if (list == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
final List<CommentModel> flatList = new ArrayList<>();
|
|
||||||
int lastCommentIndex = -1;
|
|
||||||
for (final CommentModel parent : list) {
|
|
||||||
lastCommentIndex++;
|
|
||||||
flatList.add(parent);
|
|
||||||
positionTypeMap.put(lastCommentIndex, TYPE_PARENT);
|
|
||||||
final List<CommentModel> children = parent.getChildCommentModels();
|
|
||||||
if (children != null) {
|
|
||||||
for (final CommentModel child : children) {
|
|
||||||
lastCommentIndex++;
|
|
||||||
flatList.add(child);
|
|
||||||
positionTypeMap.put(lastCommentIndex, TYPE_CHILD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flatList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(final int position) {
|
|
||||||
final Integer type = positionTypeMap.get(position);
|
|
||||||
if (type == null) {
|
|
||||||
return TYPE_PARENT;
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelected(final CommentModel commentModel) {
|
|
||||||
this.selected = commentModel;
|
|
||||||
selectedIndex = getCurrentList().indexOf(commentModel);
|
|
||||||
notifyItemChanged(selectedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearSelection() {
|
|
||||||
this.selected = null;
|
|
||||||
notifyItemChanged(selectedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLiked(final CommentModel commentModel, final boolean liked) {
|
|
||||||
likedIndex = getCurrentList().indexOf(commentModel);
|
|
||||||
CommentModel newCommentModel = commentModel;
|
|
||||||
newCommentModel.setLiked(liked);
|
|
||||||
this.toChangeLike = newCommentModel;
|
|
||||||
notifyItemChanged(likedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommentModel getSelected() {
|
|
||||||
return selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface CommentCallback {
|
public interface CommentCallback {
|
||||||
void onClick(final CommentModel comment);
|
void onClick(final Comment comment);
|
||||||
|
|
||||||
void onHashtagClick(final String hashtag);
|
void onHashtagClick(final String hashtag);
|
||||||
|
|
||||||
@ -198,5 +63,15 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
|
|||||||
void onURLClick(final String url);
|
void onURLClick(final String url);
|
||||||
|
|
||||||
void onEmailClick(final String emailAddress);
|
void onEmailClick(final String emailAddress);
|
||||||
|
|
||||||
|
void onLikeClick(final Comment comment, boolean liked, final boolean isReply);
|
||||||
|
|
||||||
|
void onRepliesClick(final Comment comment);
|
||||||
|
|
||||||
|
void onViewLikes(Comment comment);
|
||||||
|
|
||||||
|
void onTranslate(Comment comment);
|
||||||
|
|
||||||
|
void onDelete(Comment comment, boolean isReply);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,5 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
@ -13,8 +12,10 @@ import androidx.recyclerview.widget.AsyncListDiffer;
|
|||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.FormatStyle;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -57,7 +58,6 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
import awais.instagrabber.utils.DateUtils;
|
|
||||||
|
|
||||||
public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
private static final String TAG = DirectItemsAdapter.class.getSimpleName();
|
private static final String TAG = DirectItemsAdapter.class.getSimpleName();
|
||||||
@ -139,7 +139,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
return new HeaderViewHolder(LayoutDmHeaderBinding.inflate(layoutInflater, parent, false));
|
return new HeaderViewHolder(LayoutDmHeaderBinding.inflate(layoutInflater, parent, false));
|
||||||
}
|
}
|
||||||
final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
|
final LayoutDmBaseBinding baseBinding = LayoutDmBaseBinding.inflate(layoutInflater, parent, false);
|
||||||
final DirectItemType directItemType = DirectItemType.valueOf(type);
|
final DirectItemType directItemType = DirectItemType.Companion.getId(type);
|
||||||
final DirectItemViewHolder itemViewHolder = getItemViewHolder(layoutInflater, baseBinding, directItemType);
|
final DirectItemViewHolder itemViewHolder = getItemViewHolder(layoutInflater, baseBinding, directItemType);
|
||||||
itemViewHolder.setLongClickListener(longClickListener);
|
itemViewHolder.setLongClickListener(longClickListener);
|
||||||
return itemViewHolder;
|
return itemViewHolder;
|
||||||
@ -292,12 +292,15 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
|
|
||||||
private List<DirectItemOrHeader> sectionAndSort(final List<DirectItem> list) {
|
private List<DirectItemOrHeader> sectionAndSort(final List<DirectItem> list) {
|
||||||
final List<DirectItemOrHeader> itemOrHeaders = new ArrayList<>();
|
final List<DirectItemOrHeader> itemOrHeaders = new ArrayList<>();
|
||||||
Date prevSectionDate = null;
|
LocalDate prevSectionDate = null;
|
||||||
for (int i = 0; i < list.size(); i++) {
|
for (int i = 0; i < list.size(); i++) {
|
||||||
final DirectItem item = list.get(i);
|
final DirectItem item = list.get(i);
|
||||||
if (item == null) continue;
|
if (item == null || item.getDate() == null) continue;
|
||||||
final DirectItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1);
|
final DirectItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1);
|
||||||
if (prev != null && prev.item != null && DateUtils.isSameDay(prev.item.getDate(), item.getDate())) {
|
if (prev != null
|
||||||
|
&& prev.item != null
|
||||||
|
&& prev.item.getDate() != null
|
||||||
|
&& prev.item.getDate().toLocalDate().isEqual(item.getDate().toLocalDate())) {
|
||||||
// just add item
|
// just add item
|
||||||
final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
|
final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
|
||||||
itemOrHeader.item = item;
|
itemOrHeader.item = item;
|
||||||
@ -320,7 +323,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
|
final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
|
||||||
itemOrHeader.item = item;
|
itemOrHeader.item = item;
|
||||||
itemOrHeaders.add(itemOrHeader);
|
itemOrHeaders.add(itemOrHeader);
|
||||||
prevSectionDate = DateUtils.dateAtZeroHours(item.getDate());
|
prevSectionDate = item.getDate().toLocalDate();
|
||||||
}
|
}
|
||||||
return itemOrHeaders;
|
return itemOrHeaders;
|
||||||
}
|
}
|
||||||
@ -352,7 +355,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class DirectItemOrHeader {
|
public static class DirectItemOrHeader {
|
||||||
Date date;
|
LocalDate date;
|
||||||
public DirectItem item;
|
public DirectItem item;
|
||||||
|
|
||||||
public boolean isHeader() {
|
public boolean isHeader() {
|
||||||
@ -377,12 +380,13 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final Date date) {
|
public void bind(final LocalDate date) {
|
||||||
if (date == null) {
|
if (date == null) {
|
||||||
binding.header.setText("");
|
binding.header.setText("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
binding.header.setText(DateFormat.getDateFormat(itemView.getContext()).format(date));
|
final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
|
||||||
|
binding.header.setText(dateFormatter.format(date));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,6 +410,8 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
|||||||
void onReactionClick(DirectItem item, int position);
|
void onReactionClick(DirectItem item, int position);
|
||||||
|
|
||||||
void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
|
void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
|
||||||
|
|
||||||
|
void onAddReactionListener(DirectItem item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DirectItemInternalLongClickListener {
|
public interface DirectItemInternalLongClickListener {
|
||||||
|
@ -11,6 +11,7 @@ import androidx.recyclerview.widget.ListAdapter;
|
|||||||
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
|
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
|
||||||
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
|
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
|
||||||
import awais.instagrabber.repositories.responses.discover.TopicCluster;
|
import awais.instagrabber.repositories.responses.discover.TopicCluster;
|
||||||
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
|
|
||||||
public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicClusterViewHolder> {
|
public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicClusterViewHolder> {
|
||||||
@ -50,6 +51,8 @@ public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicCluste
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface OnTopicClickListener {
|
public interface OnTopicClickListener {
|
||||||
void onTopicClick(TopicCluster topicCluster, View root, View cover, View title, int titleColor, int backgroundColor);
|
void onTopicClick(TopicCluster topicCluster, View cover, int titleColor, int backgroundColor);
|
||||||
|
|
||||||
|
void onTopicLongClick(Media coverMedia);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ import java.util.List;
|
|||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
|
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
|
||||||
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
||||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
import awais.instagrabber.db.entities.Favorite;
|
import awais.instagrabber.db.entities.Favorite;
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||||||
// header
|
// header
|
||||||
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false));
|
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false));
|
||||||
}
|
}
|
||||||
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false);
|
final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(inflater, parent, false);
|
||||||
return new FavoriteViewHolder(binding);
|
return new FavoriteViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,19 +4,12 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder;
|
import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder;
|
||||||
import awais.instagrabber.databinding.ItemHighlightBinding;
|
import awais.instagrabber.databinding.ItemHighlightBinding;
|
||||||
import awais.instagrabber.models.FeedStoryModel;
|
import awais.instagrabber.models.FeedStoryModel;
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> {
|
public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> {
|
||||||
private final OnFeedStoryClickListener listener;
|
private final OnFeedStoryClickListener listener;
|
||||||
@ -29,7 +22,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
|
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
|
||||||
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead());
|
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead() == newItem.isFullyRead();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,23 +23,29 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
|
|||||||
private List<FeedStoryModel> list;
|
private List<FeedStoryModel> list;
|
||||||
|
|
||||||
private final Filter filter = new Filter() {
|
private final Filter filter = new Filter() {
|
||||||
@Nullable
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected FilterResults performFiltering(final CharSequence filter) {
|
protected FilterResults performFiltering(final CharSequence filter) {
|
||||||
final boolean isFilterEmpty = TextUtils.isEmpty(filter);
|
final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase();
|
||||||
final String query = isFilterEmpty ? null : filter.toString().toLowerCase();
|
List<FeedStoryModel> filteredList = list;
|
||||||
|
if (list != null && query != null) {
|
||||||
for (FeedStoryModel item : list) {
|
filteredList = list.stream()
|
||||||
if (isFilterEmpty) item.setShown(true);
|
.filter(feedStoryModel -> feedStoryModel.getProfileModel()
|
||||||
else item.setShown(item.getProfileModel().getUsername().toLowerCase().contains(query));
|
.getUsername()
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(query))
|
||||||
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
return null;
|
final FilterResults filterResults = new FilterResults();
|
||||||
|
filterResults.count = filteredList != null ? filteredList.size() : 0;
|
||||||
|
filterResults.values = filteredList;
|
||||||
|
return filterResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
||||||
submitList(list);
|
//noinspection unchecked
|
||||||
notifyDataSetChanged();
|
submitList((List<FeedStoryModel>) results.values, true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -51,7 +57,7 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
|
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
|
||||||
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead());
|
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead() == newItem.isFullyRead();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -65,10 +71,16 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void submitList(@Nullable final List<FeedStoryModel> list, final boolean isFiltered) {
|
||||||
|
if (!isFiltered) {
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
super.submitList(list);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void submitList(final List<FeedStoryModel> list) {
|
public void submitList(final List<FeedStoryModel> list) {
|
||||||
super.submitList(list.stream().filter(i -> i.isShown()).collect(Collectors.toList()));
|
submitList(list, false);
|
||||||
this.list = list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -82,11 +94,11 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
|
||||||
final FeedStoryModel model = getItem(position);
|
final FeedStoryModel model = getItem(position);
|
||||||
holder.bind(model, position, listener);
|
holder.bind(model, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnFeedStoryClickListener {
|
public interface OnFeedStoryClickListener {
|
||||||
void onFeedStoryClick(final FeedStoryModel model, final int position);
|
void onFeedStoryClick(final FeedStoryModel model);
|
||||||
|
|
||||||
void onProfileClick(final String username);
|
void onProfileClick(final String username);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ public final class LikesAdapter extends RecyclerView.Adapter<FollowsViewHolder>
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
|
||||||
final User model = profileModels.get(position);
|
final User model = profileModels.get(position);
|
||||||
holder.bind(model, null, onClickListener);
|
holder.bind(model, onClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,8 +23,8 @@ public final class NotificationsAdapter extends ListAdapter<Notification, Notifi
|
|||||||
|
|
||||||
private static final DiffUtil.ItemCallback<Notification> DIFF_CALLBACK = new DiffUtil.ItemCallback<Notification>() {
|
private static final DiffUtil.ItemCallback<Notification> DIFF_CALLBACK = new DiffUtil.ItemCallback<Notification>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull final Notification oldItem, @NonNull final Notification newItem) {
|
public boolean areItemsTheSame(final Notification oldItem, final Notification newItem) {
|
||||||
return oldItem.getPk().equals(newItem.getPk());
|
return oldItem != null && newItem != null && oldItem.getPk().equals(newItem.getPk());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -11,20 +11,19 @@ import androidx.recyclerview.widget.ListAdapter;
|
|||||||
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
|
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
|
||||||
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
|
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
|
||||||
import awais.instagrabber.repositories.responses.saved.SavedCollection;
|
import awais.instagrabber.repositories.responses.saved.SavedCollection;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
|
||||||
|
|
||||||
public class SavedCollectionsAdapter extends ListAdapter<SavedCollection, TopicClusterViewHolder> {
|
public class SavedCollectionsAdapter extends ListAdapter<SavedCollection, TopicClusterViewHolder> {
|
||||||
private static final DiffUtil.ItemCallback<SavedCollection> DIFF_CALLBACK = new DiffUtil.ItemCallback<SavedCollection>() {
|
private static final DiffUtil.ItemCallback<SavedCollection> DIFF_CALLBACK = new DiffUtil.ItemCallback<SavedCollection>() {
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
public boolean areItemsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
||||||
return oldItem.getId().equals(newItem.getId());
|
return oldItem.getCollectionId().equals(newItem.getCollectionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
||||||
if (oldItem.getCoverMedias() != null && newItem.getCoverMedias() != null
|
if (oldItem.getCoverMediaList() != null && newItem.getCoverMediaList() != null
|
||||||
&& oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) {
|
&& oldItem.getCoverMediaList().size() == newItem.getCoverMediaList().size()) {
|
||||||
return oldItem.getCoverMedias().get(0).getId().equals(newItem.getCoverMedias().get(0).getId());
|
return oldItem.getCoverMediaList().get(0).getId().equals(newItem.getCoverMediaList().get(0).getId());
|
||||||
}
|
}
|
||||||
else if (oldItem.getCoverMedia() != null && newItem.getCoverMedia() != null) {
|
else if (oldItem.getCoverMedia() != null && newItem.getCoverMedia() != null) {
|
||||||
return oldItem.getCoverMedia().getId().equals(newItem.getCoverMedia().getId());
|
return oldItem.getCoverMedia().getId().equals(newItem.getCoverMedia().getId());
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.fragments.search.SearchCategoryFragment;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
public class SearchCategoryAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
|
private final List<FavoriteType> categories;
|
||||||
|
|
||||||
|
public SearchCategoryAdapter(@NonNull final Fragment fragment,
|
||||||
|
@NonNull final List<FavoriteType> categories) {
|
||||||
|
super(fragment);
|
||||||
|
this.categories = categories;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(final int position) {
|
||||||
|
return SearchCategoryFragment.newInstance(categories.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return categories.size();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.AdapterListUpdateCallback;
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig;
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.viewholder.SearchItemViewHolder;
|
||||||
|
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
||||||
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
|
import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
import awais.instagrabber.repositories.responses.search.SearchItem;
|
||||||
|
|
||||||
|
public final class SearchItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
private static final String TAG = SearchItemsAdapter.class.getSimpleName();
|
||||||
|
private static final DiffUtil.ItemCallback<SearchItemOrHeader> DIFF_CALLBACK = new DiffUtil.ItemCallback<SearchItemOrHeader>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) {
|
||||||
|
return Objects.equals(oldItem, newItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) {
|
||||||
|
return Objects.equals(oldItem, newItem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final String RECENT = "recent";
|
||||||
|
private static final String FAVORITE = "favorite";
|
||||||
|
private static final int VIEW_TYPE_HEADER = 0;
|
||||||
|
private static final int VIEW_TYPE_ITEM = 1;
|
||||||
|
|
||||||
|
private final OnSearchItemClickListener onSearchItemClickListener;
|
||||||
|
private final AsyncListDiffer<SearchItemOrHeader> differ;
|
||||||
|
|
||||||
|
public SearchItemsAdapter(final OnSearchItemClickListener onSearchItemClickListener) {
|
||||||
|
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
|
||||||
|
new AsyncDifferConfig.Builder<>(DIFF_CALLBACK).build());
|
||||||
|
this.onSearchItemClickListener = onSearchItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
|
if (viewType == VIEW_TYPE_HEADER) {
|
||||||
|
return new HeaderViewHolder(ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false));
|
||||||
|
}
|
||||||
|
final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new SearchItemViewHolder(binding, onSearchItemClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
||||||
|
if (getItemViewType(position) == VIEW_TYPE_HEADER) {
|
||||||
|
final SearchItemOrHeader searchItemOrHeader = getItem(position);
|
||||||
|
if (!searchItemOrHeader.isHeader()) return;
|
||||||
|
((HeaderViewHolder) holder).bind(searchItemOrHeader.header);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
((SearchItemViewHolder) holder).bind(getItem(position).searchItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SearchItemOrHeader getItem(int position) {
|
||||||
|
return differ.getCurrentList().get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return differ.getCurrentList().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(final int position) {
|
||||||
|
return getItem(position).isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitList(@Nullable final List<SearchItem> list) {
|
||||||
|
if (list == null) {
|
||||||
|
differ.submitList(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
differ.submitList(sectionAndSort(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitList(@Nullable final List<SearchItem> list, @Nullable final Runnable commitCallback) {
|
||||||
|
if (list == null) {
|
||||||
|
differ.submitList(null, commitCallback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
differ.submitList(sectionAndSort(list), commitCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<SearchItemOrHeader> sectionAndSort(@NonNull final List<SearchItem> list) {
|
||||||
|
final boolean containsRecentOrFavorite = list.stream().anyMatch(searchItem -> searchItem.isRecent() || searchItem.isFavorite());
|
||||||
|
// Don't do anything if not showing recent results
|
||||||
|
if (!containsRecentOrFavorite) {
|
||||||
|
return list.stream()
|
||||||
|
.map(SearchItemOrHeader::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
final List<SearchItem> listCopy = new ArrayList<>(list);
|
||||||
|
Collections.sort(listCopy, (o1, o2) -> {
|
||||||
|
final boolean bothRecent = o1.isRecent() && o2.isRecent();
|
||||||
|
if (bothRecent) {
|
||||||
|
// Don't sort
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final boolean bothFavorite = o1.isFavorite() && o2.isFavorite();
|
||||||
|
if (bothFavorite) {
|
||||||
|
if (o1.getType() == o2.getType()) return 0;
|
||||||
|
// keep users at top
|
||||||
|
if (o1.getType() == FavoriteType.USER) return -1;
|
||||||
|
if (o2.getType() == FavoriteType.USER) return 1;
|
||||||
|
// keep locations at bottom
|
||||||
|
if (o1.getType() == FavoriteType.LOCATION) return 1;
|
||||||
|
if (o2.getType() == FavoriteType.LOCATION) return -1;
|
||||||
|
}
|
||||||
|
// keep recents at top
|
||||||
|
if (o1.isRecent()) return -1;
|
||||||
|
if (o2.isRecent()) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
final List<SearchItemOrHeader> itemOrHeaders = new ArrayList<>();
|
||||||
|
for (int i = 0; i < listCopy.size(); i++) {
|
||||||
|
final SearchItem searchItem = listCopy.get(i);
|
||||||
|
final SearchItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1);
|
||||||
|
boolean prevWasSameType = prev != null && ((prev.searchItem.isRecent() && searchItem.isRecent())
|
||||||
|
|| (prev.searchItem.isFavorite() && searchItem.isFavorite()));
|
||||||
|
if (prevWasSameType) {
|
||||||
|
// just add the item
|
||||||
|
itemOrHeaders.add(new SearchItemOrHeader(searchItem));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// add header and item
|
||||||
|
// add header only if search item is recent or favorite
|
||||||
|
if (searchItem.isRecent() || searchItem.isFavorite()) {
|
||||||
|
itemOrHeaders.add(new SearchItemOrHeader(searchItem.isRecent() ? RECENT : FAVORITE));
|
||||||
|
}
|
||||||
|
itemOrHeaders.add(new SearchItemOrHeader(searchItem));
|
||||||
|
}
|
||||||
|
return itemOrHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SearchItemOrHeader {
|
||||||
|
String header;
|
||||||
|
SearchItem searchItem;
|
||||||
|
|
||||||
|
public SearchItemOrHeader(final SearchItem searchItem) {
|
||||||
|
this.searchItem = searchItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchItemOrHeader(final String header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isHeader() {
|
||||||
|
return header != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final SearchItemOrHeader that = (SearchItemOrHeader) o;
|
||||||
|
return Objects.equals(header, that.header) &&
|
||||||
|
Objects.equals(searchItem, that.searchItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(header, searchItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ItemFavSectionHeaderBinding binding;
|
||||||
|
|
||||||
|
public HeaderViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final String header) {
|
||||||
|
if (header == null) return;
|
||||||
|
final int headerText;
|
||||||
|
switch (header) {
|
||||||
|
case RECENT:
|
||||||
|
headerText = R.string.recent;
|
||||||
|
break;
|
||||||
|
case FAVORITE:
|
||||||
|
headerText = R.string.title_favorites;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
headerText = R.string.unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
binding.getRoot().setText(headerText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,17 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
|
||||||
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onThumbnailLoaded(final int position) {}
|
public void onThumbnailLoaded(final int position) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClicked(final int position) {}
|
public void onItemClicked(final int position, final Media media, final View view) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerPlay(final int position) {}
|
public void onPlayerPlay(final int position) {}
|
||||||
@ -15,4 +21,12 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerRelease(final int position) {}
|
public void onPlayerRelease(final int position) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInFullScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
|
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
|
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
|
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
|
||||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
|
||||||
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
|
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
|
||||||
|
|
||||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
|
||||||
private final boolean loadVideoOnItemClick;
|
private final boolean loadVideoOnItemClick;
|
||||||
private final SliderCallback sliderCallback;
|
private final SliderCallback sliderCallback;
|
||||||
private final LayoutExoCustomControlsBinding controlsBinding;
|
|
||||||
|
|
||||||
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
|
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
|
||||||
@Override
|
@Override
|
||||||
@ -36,15 +35,11 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
public SliderItemsAdapter(final boolean loadVideoOnItemClick,
|
||||||
final LayoutExoCustomControlsBinding controlsBinding,
|
|
||||||
final boolean loadVideoOnItemClick,
|
|
||||||
final SliderCallback sliderCallback) {
|
final SliderCallback sliderCallback) {
|
||||||
super(DIFF_CALLBACK);
|
super(DIFF_CALLBACK);
|
||||||
this.onVerticalDragListener = onVerticalDragListener;
|
|
||||||
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
||||||
this.sliderCallback = sliderCallback;
|
this.sliderCallback = sliderCallback;
|
||||||
this.controlsBinding = controlsBinding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -55,12 +50,12 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
|||||||
switch (mediaItemType) {
|
switch (mediaItemType) {
|
||||||
case MEDIA_TYPE_VIDEO: {
|
case MEDIA_TYPE_VIDEO: {
|
||||||
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
|
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
|
||||||
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding, loadVideoOnItemClick);
|
return new SliderVideoViewHolder(binding, loadVideoOnItemClick);
|
||||||
}
|
}
|
||||||
case MEDIA_TYPE_IMAGE:
|
case MEDIA_TYPE_IMAGE:
|
||||||
default:
|
default:
|
||||||
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
|
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
|
||||||
return new SliderPhotoViewHolder(binding, onVerticalDragListener);
|
return new SliderPhotoViewHolder(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,12 +138,16 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
|||||||
public interface SliderCallback {
|
public interface SliderCallback {
|
||||||
void onThumbnailLoaded(int position);
|
void onThumbnailLoaded(int position);
|
||||||
|
|
||||||
void onItemClicked(int position);
|
void onItemClicked(int position, final Media media, final View view);
|
||||||
|
|
||||||
void onPlayerPlay(int position);
|
void onPlayerPlay(int position);
|
||||||
|
|
||||||
void onPlayerPause(int position);
|
void onPlayerPause(int position);
|
||||||
|
|
||||||
void onPlayerRelease(int position);
|
void onPlayerRelease(int position);
|
||||||
|
|
||||||
|
void onFullScreenModeChanged(boolean isFullScreen, final StyledPlayerView playerView);
|
||||||
|
|
||||||
|
boolean isInFullScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package awais.instagrabber.adapters;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.cursoradapter.widget.CursorAdapter;
|
|
||||||
|
|
||||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
|
||||||
import awais.instagrabber.models.enums.SuggestionType;
|
|
||||||
|
|
||||||
public final class SuggestionsAdapter extends CursorAdapter {
|
|
||||||
private static final String TAG = "SuggestionsAdapter";
|
|
||||||
|
|
||||||
private final OnSuggestionClickListener onSuggestionClickListener;
|
|
||||||
|
|
||||||
public SuggestionsAdapter(final Context context,
|
|
||||||
final OnSuggestionClickListener onSuggestionClickListener) {
|
|
||||||
super(context, null, FLAG_REGISTER_CONTENT_OBSERVER);
|
|
||||||
this.onSuggestionClickListener = onSuggestionClickListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
|
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(context);
|
|
||||||
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(layoutInflater, parent, false);
|
|
||||||
return binding.getRoot();
|
|
||||||
// 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, query, picUrl, verified
|
|
||||||
// 0, 1 , 2 , 3 , 4 , 5 , 6
|
|
||||||
final String fullName = cursor.getString(2);
|
|
||||||
String username = cursor.getString(1);
|
|
||||||
String picUrl = cursor.getString(5);
|
|
||||||
final boolean verified = cursor.getString(6).charAt(0) == 't';
|
|
||||||
|
|
||||||
final String type = cursor.getString(3);
|
|
||||||
SuggestionType suggestionType = null;
|
|
||||||
try {
|
|
||||||
suggestionType = SuggestionType.valueOf(type);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.e(TAG, "Unknown suggestion type: " + type, e);
|
|
||||||
}
|
|
||||||
if (suggestionType == null) return;
|
|
||||||
String query = cursor.getString(4);
|
|
||||||
switch (suggestionType) {
|
|
||||||
case TYPE_USER:
|
|
||||||
username = '@' + username;
|
|
||||||
break;
|
|
||||||
case TYPE_HASHTAG:
|
|
||||||
username = '#' + username;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onSuggestionClickListener != null) {
|
|
||||||
final SuggestionType finalSuggestionType = suggestionType;
|
|
||||||
view.setOnClickListener(v -> onSuggestionClickListener.onSuggestionClick(finalSuggestionType, query));
|
|
||||||
}
|
|
||||||
final ItemSuggestionBinding binding = ItemSuggestionBinding.bind(view);
|
|
||||||
binding.isVerified.setVisibility(verified ? View.VISIBLE : View.GONE);
|
|
||||||
binding.tvUsername.setText(username);
|
|
||||||
binding.tvFullName.setVisibility(View.VISIBLE);
|
|
||||||
binding.tvFullName.setText(fullName);
|
|
||||||
binding.ivProfilePic.setImageURI(picUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnSuggestionClickListener {
|
|
||||||
void onSuggestionClick(final SuggestionType type, final String query);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,209 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper;
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
|
||||||
|
import awais.instagrabber.customviews.ProfilePicView;
|
||||||
|
import awais.instagrabber.databinding.ItemCommentBinding;
|
||||||
|
import awais.instagrabber.models.Comment;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
|
public final class CommentViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final ItemCommentBinding binding;
|
||||||
|
private final long currentUserId;
|
||||||
|
private final CommentCallback commentCallback;
|
||||||
|
@ColorInt
|
||||||
|
private int parentCommentHighlightColor;
|
||||||
|
private PopupMenu optionsPopup;
|
||||||
|
|
||||||
|
public CommentViewHolder(@NonNull final ItemCommentBinding binding,
|
||||||
|
final long currentUserId,
|
||||||
|
final CommentCallback commentCallback) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.currentUserId = currentUserId;
|
||||||
|
this.commentCallback = commentCallback;
|
||||||
|
final Context context = itemView.getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
final Resources.Theme theme = context.getTheme();
|
||||||
|
if (theme == null) return;
|
||||||
|
final TypedValue typedValue = new TypedValue();
|
||||||
|
final boolean resolved = theme.resolveAttribute(R.attr.parentCommentHighlightColor, typedValue, true);
|
||||||
|
if (resolved) {
|
||||||
|
parentCommentHighlightColor = typedValue.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final Comment comment, final boolean isReplyParent, final boolean isReply) {
|
||||||
|
if (comment == null) return;
|
||||||
|
itemView.setOnClickListener(v -> {
|
||||||
|
if (commentCallback != null) {
|
||||||
|
commentCallback.onClick(comment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (isReplyParent && parentCommentHighlightColor != 0) {
|
||||||
|
itemView.setBackgroundColor(parentCommentHighlightColor);
|
||||||
|
} else {
|
||||||
|
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
|
||||||
|
}
|
||||||
|
setupCommentText(comment, isReply);
|
||||||
|
binding.date.setText(comment.getDateTime());
|
||||||
|
setLikes(comment, isReply);
|
||||||
|
setReplies(comment, isReply);
|
||||||
|
setUser(comment, isReply);
|
||||||
|
setupOptions(comment, isReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCommentText(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
binding.comment.clearOnURLClickListeners();
|
||||||
|
binding.comment.clearOnHashtagClickListeners();
|
||||||
|
binding.comment.clearOnMentionClickListeners();
|
||||||
|
binding.comment.clearOnEmailClickListeners();
|
||||||
|
binding.comment.setText(comment.getText());
|
||||||
|
binding.comment.setTextSize(TypedValue.COMPLEX_UNIT_SP, isReply ? 12 : 14);
|
||||||
|
binding.comment.addOnHashtagListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onHashtagClick(originalText);
|
||||||
|
});
|
||||||
|
binding.comment.addOnMentionClickListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onMentionClick(originalText);
|
||||||
|
|
||||||
|
});
|
||||||
|
binding.comment.addOnEmailClickListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onEmailClick(originalText);
|
||||||
|
});
|
||||||
|
binding.comment.addOnURLClickListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onURLClick(originalText);
|
||||||
|
});
|
||||||
|
binding.comment.setOnLongClickListener(v -> {
|
||||||
|
Utils.copyText(itemView.getContext(), comment.getText());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
binding.comment.setOnClickListener(v -> commentCallback.onClick(comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUser(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
final User user = comment.getUser();
|
||||||
|
if (user == null) return;
|
||||||
|
binding.username.setUsername(user.getUsername(), user.isVerified());
|
||||||
|
binding.username.setTextAppearance(itemView.getContext(), isReply ? R.style.TextAppearance_MaterialComponents_Subtitle2
|
||||||
|
: R.style.TextAppearance_MaterialComponents_Subtitle1);
|
||||||
|
binding.username.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onMentionClick("@" + user.getUsername());
|
||||||
|
});
|
||||||
|
binding.profilePic.setImageURI(user.getProfilePicUrl());
|
||||||
|
binding.profilePic.setSize(isReply ? ProfilePicView.Size.SMALLER : ProfilePicView.Size.SMALL);
|
||||||
|
binding.profilePic.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onMentionClick("@" + user.getUsername());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLikes(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
// final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
|
||||||
|
binding.likes.setText(String.valueOf(comment.getCommentLikeCount()));
|
||||||
|
binding.likes.setOnLongClickListener(v -> {
|
||||||
|
if (commentCallback == null) return false;
|
||||||
|
commentCallback.onViewLikes(comment);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (currentUserId == 0) { // not logged in
|
||||||
|
binding.likes.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onViewLikes(comment);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final boolean liked = comment.getLiked();
|
||||||
|
final int resId = liked ? R.drawable.ic_like : R.drawable.ic_not_liked;
|
||||||
|
binding.likes.setCompoundDrawablesRelativeWithSize(ContextCompat.getDrawable(itemView.getContext(), resId), null, null, null);
|
||||||
|
binding.likes.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
// toggle like
|
||||||
|
commentCallback.onLikeClick(comment, !liked, isReply);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReplies(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
final int replies = comment.getChildCommentCount();
|
||||||
|
binding.replies.setVisibility(View.VISIBLE);
|
||||||
|
final String text = isReply ? "" : String.valueOf(replies);
|
||||||
|
// final String string = itemView.getResources().getQuantityString(R.plurals.replies_count, replies, replies);
|
||||||
|
binding.replies.setText(text);
|
||||||
|
binding.replies.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onRepliesClick(comment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupOptions(final Comment comment, final boolean isReply) {
|
||||||
|
binding.options.setOnClickListener(v -> {
|
||||||
|
if (optionsPopup == null) {
|
||||||
|
createOptionsPopupMenu(comment, isReply);
|
||||||
|
}
|
||||||
|
if (optionsPopup == null) return;
|
||||||
|
optionsPopup.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOptionsPopupMenu(final Comment comment, final boolean isReply) {
|
||||||
|
if (optionsPopup == null) {
|
||||||
|
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(itemView.getContext(), R.style.popupMenuStyle);
|
||||||
|
optionsPopup = new PopupMenu(themeWrapper, binding.options);
|
||||||
|
} else {
|
||||||
|
optionsPopup.getMenu().clear();
|
||||||
|
}
|
||||||
|
optionsPopup.getMenuInflater().inflate(R.menu.comment_options_menu, optionsPopup.getMenu());
|
||||||
|
final User user = comment.getUser();
|
||||||
|
if (currentUserId == 0 || user == null || user.getPk() != currentUserId) {
|
||||||
|
final Menu menu = optionsPopup.getMenu();
|
||||||
|
menu.removeItem(R.id.delete);
|
||||||
|
}
|
||||||
|
optionsPopup.setOnMenuItemClickListener(item -> {
|
||||||
|
if (commentCallback == null) return false;
|
||||||
|
int itemId = item.getItemId();
|
||||||
|
if (itemId == R.id.translate) {
|
||||||
|
commentCallback.onTranslate(comment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (itemId == R.id.delete) {
|
||||||
|
commentCallback.onDelete(comment, isReply);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// private void setupReply(final Comment comment) {
|
||||||
|
// if (!isLoggedIn) {
|
||||||
|
// binding.reply.setVisibility(View.GONE);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// binding.reply.setOnClickListener(v -> {
|
||||||
|
// if (commentCallback == null) return;
|
||||||
|
// // toggle like
|
||||||
|
// commentCallback.onReplyClick(comment);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
@ -1,26 +1,26 @@
|
|||||||
package awais.instagrabber.adapters.viewholder;
|
//package awais.instagrabber.adapters.viewholder;
|
||||||
|
//
|
||||||
import android.view.View;
|
//import android.view.View;
|
||||||
import android.widget.ImageView;
|
//import android.widget.ImageView;
|
||||||
|
//
|
||||||
import androidx.annotation.NonNull;
|
//import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
//import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
//
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
//import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
//
|
||||||
import awais.instagrabber.R;
|
//import awais.instagrabber.R;
|
||||||
|
//
|
||||||
public final class DiscoverViewHolder extends RecyclerView.ViewHolder {
|
//public final class DiscoverViewHolder extends RecyclerView.ViewHolder {
|
||||||
public final SimpleDraweeView postImage;
|
// public final SimpleDraweeView postImage;
|
||||||
public final ImageView typeIcon;
|
// public final ImageView typeIcon;
|
||||||
public final View selectedView;
|
// public final View selectedView;
|
||||||
// public final View progressView;
|
// // public final View progressView;
|
||||||
|
//
|
||||||
public DiscoverViewHolder(@NonNull final View itemView) {
|
// public DiscoverViewHolder(@NonNull final View itemView) {
|
||||||
super(itemView);
|
// super(itemView);
|
||||||
typeIcon = itemView.findViewById(R.id.typeIcon);
|
// typeIcon = itemView.findViewById(R.id.typeIcon);
|
||||||
postImage = itemView.findViewById(R.id.postImage);
|
// postImage = itemView.findViewById(R.id.postImage);
|
||||||
selectedView = itemView.findViewById(R.id.selectedView);
|
// selectedView = itemView.findViewById(R.id.selectedView);
|
||||||
// progressView = itemView.findViewById(R.id.progressView);
|
// // progressView = itemView.findViewById(R.id.progressView);
|
||||||
}
|
// }
|
||||||
}
|
//}
|
@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.FavoritesAdapter;
|
import awais.instagrabber.adapters.FavoritesAdapter;
|
||||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
import awais.instagrabber.db.entities.Favorite;
|
import awais.instagrabber.db.entities.Favorite;
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
@ -14,12 +14,12 @@ import awais.instagrabber.utils.Constants;
|
|||||||
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
||||||
private static final String TAG = "FavoriteViewHolder";
|
private static final String TAG = "FavoriteViewHolder";
|
||||||
|
|
||||||
private final ItemSuggestionBinding binding;
|
private final ItemSearchResultBinding binding;
|
||||||
|
|
||||||
public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) {
|
public FavoriteViewHolder(@NonNull final ItemSearchResultBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
binding.isVerified.setVisibility(View.GONE);
|
binding.verified.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final Favorite model,
|
public void bind(final Favorite model,
|
||||||
@ -36,12 +36,12 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
|||||||
return longClickListener.onLongClick(model);
|
return longClickListener.onLongClick(model);
|
||||||
});
|
});
|
||||||
if (model.getType() == FavoriteType.HASHTAG) {
|
if (model.getType() == FavoriteType.HASHTAG) {
|
||||||
binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
|
binding.profilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
|
||||||
} else {
|
} else {
|
||||||
binding.ivProfilePic.setImageURI(model.getPicUrl());
|
binding.profilePic.setImageURI(model.getPicUrl());
|
||||||
}
|
}
|
||||||
binding.tvFullName.setText(model.getDisplayName());
|
binding.title.setVisibility(View.VISIBLE);
|
||||||
binding.tvUsername.setVisibility(View.VISIBLE);
|
binding.subtitle.setText(model.getDisplayName());
|
||||||
String query = model.getQuery();
|
String query = model.getQuery();
|
||||||
switch (model.getType()) {
|
switch (model.getType()) {
|
||||||
case HASHTAG:
|
case HASHTAG:
|
||||||
@ -51,11 +51,11 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
|||||||
query = "@" + query;
|
query = "@" + query;
|
||||||
break;
|
break;
|
||||||
case LOCATION:
|
case LOCATION:
|
||||||
binding.tvUsername.setVisibility(View.GONE);
|
binding.title.setVisibility(View.GONE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
binding.tvUsername.setText(query);
|
binding.title.setText(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,11 +19,12 @@ import java.util.List;
|
|||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||||
import awais.instagrabber.asyncs.DownloadedCheckerAsyncTask;
|
|
||||||
import awais.instagrabber.databinding.ItemFeedGridBinding;
|
import awais.instagrabber.databinding.ItemFeedGridBinding;
|
||||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||||
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
|
||||||
@ -68,7 +69,9 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
setUserDetails(media, layoutPreferences);
|
setUserDetails(media, layoutPreferences);
|
||||||
String thumbnailUrl = null;
|
String thumbnailUrl = null;
|
||||||
final int typeIconRes;
|
final int typeIconRes;
|
||||||
switch (media.getMediaType()) {
|
final MediaItemType mediaType = media.getMediaType();
|
||||||
|
if (mediaType == null) return;
|
||||||
|
switch (mediaType) {
|
||||||
case MEDIA_TYPE_IMAGE:
|
case MEDIA_TYPE_IMAGE:
|
||||||
typeIconRes = -1;
|
typeIconRes = -1;
|
||||||
thumbnailUrl = ResponseBodyUtils.getThumbUrl(media);
|
thumbnailUrl = ResponseBodyUtils.getThumbUrl(media);
|
||||||
@ -102,9 +105,8 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
binding.typeIcon.setVisibility(View.VISIBLE);
|
binding.typeIcon.setVisibility(View.VISIBLE);
|
||||||
binding.typeIcon.setImageResource(typeIconRes);
|
binding.typeIcon.setImageResource(typeIconRes);
|
||||||
}
|
}
|
||||||
final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> {
|
final List<Boolean> checkList = DownloadUtils.checkDownloaded(media);
|
||||||
final List<Boolean> checkList = result.get(media.getPk());
|
if (checkList.isEmpty()) {
|
||||||
if (checkList == null || checkList.isEmpty()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (media.getMediaType()) {
|
switch (media.getMediaType()) {
|
||||||
@ -125,8 +127,6 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
|
||||||
task.execute(media);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setThumbImage(final String thumbnailUrl) {
|
private void setThumbImage(final String thumbnailUrl) {
|
||||||
|
@ -33,7 +33,7 @@ public class FilterViewHolder extends RecyclerView.ViewHolder {
|
|||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.tuneFilters = tuneFilters;
|
this.tuneFilters = tuneFilters;
|
||||||
this.onFilterClickListener = onFilterClickListener;
|
this.onFilterClickListener = onFilterClickListener;
|
||||||
appExecutors = AppExecutors.getInstance();
|
appExecutors = AppExecutors.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final int position, final String originalKey, final Bitmap originalBitmap, final Filter<?> item, final boolean isSelected) {
|
public void bind(final int position, final String originalKey, final Bitmap originalBitmap, final Filter<?> item, final boolean isSelected) {
|
||||||
@ -55,13 +55,13 @@ public class FilterViewHolder extends RecyclerView.ViewHolder {
|
|||||||
final Bitmap bitmap = BitmapUtils.getBitmapFromMemCache(filterKey);
|
final Bitmap bitmap = BitmapUtils.getBitmapFromMemCache(filterKey);
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
final GPUImageFilter filter = item.getInstance();
|
final GPUImageFilter filter = item.getInstance();
|
||||||
appExecutors.tasksThread().submit(() -> {
|
appExecutors.getTasksThread().submit(() -> {
|
||||||
GPUImage.getBitmapForMultipleFilters(
|
GPUImage.getBitmapForMultipleFilters(
|
||||||
originalBitmap,
|
originalBitmap,
|
||||||
ImmutableList.<GPUImageFilter>builder().add(filter).addAll(tuneFilters).build(),
|
ImmutableList.<GPUImageFilter>builder().add(filter).addAll(tuneFilters).build(),
|
||||||
filteredBitmap -> {
|
filteredBitmap -> {
|
||||||
BitmapUtils.addBitmapToMemoryCache(filterKey, filteredBitmap, true);
|
BitmapUtils.addBitmapToMemoryCache(filterKey, filteredBitmap, true);
|
||||||
appExecutors.mainThread().execute(() -> binding.getRoot().post(() -> binding.preview.setImageBitmap(filteredBitmap)));
|
appExecutors.getMainThread().execute(() -> binding.getRoot().post(() -> binding.preview.setImageBitmap(filteredBitmap)));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -2,10 +2,9 @@ package awais.instagrabber.adapters.viewholder;
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.databinding.ItemFollowBinding;
|
import awais.instagrabber.databinding.ItemFollowBinding;
|
||||||
import awais.instagrabber.models.FollowModel;
|
import awais.instagrabber.models.FollowModel;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
@ -14,23 +13,19 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
private final ItemFollowBinding binding;
|
private final ItemFollowBinding binding;
|
||||||
|
|
||||||
public FollowsViewHolder(final ItemFollowBinding binding) {
|
public FollowsViewHolder(@NonNull final ItemFollowBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final User model,
|
public void bind(final User model,
|
||||||
final List<Long> admins,
|
|
||||||
final View.OnClickListener onClickListener) {
|
final View.OnClickListener onClickListener) {
|
||||||
if (model == null) return;
|
if (model == null) return;
|
||||||
itemView.setTag(model);
|
itemView.setTag(model);
|
||||||
itemView.setOnClickListener(onClickListener);
|
itemView.setOnClickListener(onClickListener);
|
||||||
binding.tvUsername.setText(model.getUsername());
|
binding.username.setUsername("@" + model.getUsername(), model.isVerified());
|
||||||
binding.tvFullName.setText(model.getFullName());
|
binding.fullName.setText(model.getFullName());
|
||||||
if (admins != null && admins.contains(model.getPk())) {
|
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
||||||
binding.isAdmin.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final FollowModel model,
|
public void bind(final FollowModel model,
|
||||||
@ -38,8 +33,8 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (model == null) return;
|
if (model == null) return;
|
||||||
itemView.setTag(model);
|
itemView.setTag(model);
|
||||||
itemView.setOnClickListener(onClickListener);
|
itemView.setOnClickListener(onClickListener);
|
||||||
binding.tvUsername.setText(model.getUsername());
|
binding.username.setUsername("@" + model.getUsername());
|
||||||
binding.tvFullName.setText(model.getFullName());
|
binding.fullName.setText(model.getFullName());
|
||||||
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
|
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
|
import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
import awais.instagrabber.repositories.responses.Hashtag;
|
||||||
|
import awais.instagrabber.repositories.responses.Place;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.repositories.responses.search.SearchItem;
|
||||||
|
|
||||||
|
public class SearchItemViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final ItemSearchResultBinding binding;
|
||||||
|
private final OnSearchItemClickListener onSearchItemClickListener;
|
||||||
|
|
||||||
|
public SearchItemViewHolder(@NonNull final ItemSearchResultBinding binding,
|
||||||
|
final OnSearchItemClickListener onSearchItemClickListener) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.onSearchItemClickListener = onSearchItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final SearchItem searchItem) {
|
||||||
|
if (searchItem == null) return;
|
||||||
|
final FavoriteType type = searchItem.getType();
|
||||||
|
if (type == null) return;
|
||||||
|
String title;
|
||||||
|
String subtitle;
|
||||||
|
String picUrl;
|
||||||
|
boolean isVerified = false;
|
||||||
|
switch (type) {
|
||||||
|
case USER:
|
||||||
|
final User user = searchItem.getUser();
|
||||||
|
title = "@" + user.getUsername();
|
||||||
|
subtitle = user.getFullName();
|
||||||
|
picUrl = user.getProfilePicUrl();
|
||||||
|
isVerified = user.isVerified();
|
||||||
|
break;
|
||||||
|
case HASHTAG:
|
||||||
|
final Hashtag hashtag = searchItem.getHashtag();
|
||||||
|
title = "#" + hashtag.getName();
|
||||||
|
subtitle = hashtag.getSubtitle();
|
||||||
|
picUrl = "res:/" + R.drawable.ic_hashtag;
|
||||||
|
break;
|
||||||
|
case LOCATION:
|
||||||
|
final Place place = searchItem.getPlace();
|
||||||
|
title = place.getTitle();
|
||||||
|
subtitle = place.getSubtitle();
|
||||||
|
picUrl = "res:/" + R.drawable.ic_location;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
itemView.setOnClickListener(v -> {
|
||||||
|
if (onSearchItemClickListener != null) {
|
||||||
|
onSearchItemClickListener.onSearchItemClick(searchItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
binding.delete.setVisibility(searchItem.isRecent() ? View.VISIBLE : View.GONE);
|
||||||
|
if (searchItem.isRecent()) {
|
||||||
|
binding.delete.setEnabled(true);
|
||||||
|
binding.delete.setOnClickListener(v -> {
|
||||||
|
if (onSearchItemClickListener != null) {
|
||||||
|
binding.delete.setEnabled(false);
|
||||||
|
onSearchItemClickListener.onSearchItemDelete(searchItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
binding.title.setText(title);
|
||||||
|
binding.subtitle.setText(subtitle);
|
||||||
|
binding.profilePic.setImageURI(picUrl);
|
||||||
|
binding.verified.setVisibility(isVerified ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package awais.instagrabber.adapters.viewholder;
|
|||||||
|
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -14,8 +13,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
|||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
||||||
|
import awais.instagrabber.customviews.drawee.DoubleTapGestureListener;
|
||||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
@ -24,13 +23,10 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
|||||||
private static final String TAG = "FeedSliderPhotoViewHolder";
|
private static final String TAG = "FeedSliderPhotoViewHolder";
|
||||||
|
|
||||||
private final ItemSliderPhotoBinding binding;
|
private final ItemSliderPhotoBinding binding;
|
||||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
|
||||||
|
|
||||||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
|
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding) {
|
||||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
|
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.onVerticalDragListener = onVerticalDragListener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(@NonNull final Media model,
|
public void bind(@NonNull final Media model,
|
||||||
@ -62,74 +58,19 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
|||||||
})
|
})
|
||||||
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
||||||
.build());
|
.build());
|
||||||
// binding.getRoot().setOnClickListener(v -> {
|
final DoubleTapGestureListener tapListener = new DoubleTapGestureListener(binding.getRoot()) {
|
||||||
// if (sliderCallback != null) {
|
|
||||||
// sliderCallback.onItemClicked(position);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
binding.getRoot().setTapListener(new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapUp(final MotionEvent e) {
|
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||||
if (sliderCallback != null) {
|
if (sliderCallback != null) {
|
||||||
sliderCallback.onItemClicked(position);
|
sliderCallback.onItemClicked(position, model, binding.getRoot());
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return super.onSingleTapConfirmed(e);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
binding.getRoot().setTapListener(tapListener);
|
||||||
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
||||||
zoomableController.setMaxScaleFactor(3f);
|
zoomableController.setMaxScaleFactor(3f);
|
||||||
binding.getRoot().setZoomableController(zoomableController);
|
binding.getRoot().setZoomableController(zoomableController);
|
||||||
if (onVerticalDragListener != null) {
|
binding.getRoot().setZoomingEnabled(true);
|
||||||
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
|
||||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
|
||||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
|
||||||
// final int spanWidth = deviceWidth / spanCount;
|
|
||||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
|
||||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
|
||||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
|
||||||
// if (animate) {
|
|
||||||
// Animation animation = AnimationUtils.expand(
|
|
||||||
// binding.imageViewer,
|
|
||||||
// layoutParams.width,
|
|
||||||
// layoutParams.height,
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// new Animation.AnimationListener() {
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationStart(final Animation animation) {
|
|
||||||
// showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationEnd(final Animation animation) {
|
|
||||||
// // showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationRepeat(final Animation animation) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// binding.imageViewer.startAnimation(animation);
|
|
||||||
// } else {
|
|
||||||
// layoutParams.width = width;
|
|
||||||
// layoutParams.height = height;
|
|
||||||
// binding.imageViewer.requestLayout();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void showOrHideDetails(final int spanCount) {
|
|
||||||
// if (spanCount == 1) {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// } else {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -7,17 +7,17 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
|
||||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.VideoVersion;
|
import awais.instagrabber.repositories.responses.VideoVersion;
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
@ -28,40 +28,23 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
private static final String TAG = "SliderVideoViewHolder";
|
private static final String TAG = "SliderVideoViewHolder";
|
||||||
|
|
||||||
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
||||||
private final LayoutExoCustomControlsBinding controlsBinding;
|
|
||||||
private final boolean loadVideoOnItemClick;
|
private final boolean loadVideoOnItemClick;
|
||||||
private final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
|
private VideoPlayerViewHelper videoPlayerViewHelper;
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
|
||||||
|
final boolean loadVideoOnItemClick) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
||||||
|
final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||||
binding.playerView.performClick();
|
binding.playerView.performClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private VideoPlayerViewHelper videoPlayerViewHelper;
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
|
|
||||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
|
||||||
final LayoutExoCustomControlsBinding controlsBinding,
|
|
||||||
final boolean loadVideoOnItemClick) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
this.binding = binding;
|
|
||||||
this.controlsBinding = controlsBinding;
|
|
||||||
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
|
||||||
// if (onVerticalDragListener != null) {
|
|
||||||
// final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent);
|
|
||||||
// final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView);
|
|
||||||
// thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
// playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
// binding.thumbnailParent.setOnTouchListener((v, event) -> {
|
|
||||||
// final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event);
|
|
||||||
// if (onDragTouch) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// return thumbnailVerticalDragHelper.onGestureTouchEvent(event);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
|
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
|
||||||
binding.playerView.setOnTouchListener((v, event) -> {
|
binding.playerView.setOnTouchListener((v, event) -> {
|
||||||
gestureDetector.onTouchEvent(event);
|
gestureDetector.onTouchEvent(event);
|
||||||
@ -72,13 +55,13 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
public void bind(@NonNull final Media media,
|
public void bind(@NonNull final Media media,
|
||||||
final int position,
|
final int position,
|
||||||
final SliderItemsAdapter.SliderCallback sliderCallback) {
|
final SliderItemsAdapter.SliderCallback sliderCallback) {
|
||||||
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
final float vol = settingsHelper.getBoolean(PreferenceKeys.MUTED_VIDEOS) ? 0f : 1f;
|
||||||
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
|
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onThumbnailClick() {
|
public void onThumbnailClick() {
|
||||||
if (sliderCallback != null) {
|
if (sliderCallback != null) {
|
||||||
sliderCallback.onItemClicked(position);
|
sliderCallback.onItemClicked(position, media, binding.getRoot());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +104,21 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
sliderCallback.onPlayerRelease(position);
|
sliderCallback.onPlayerRelease(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {
|
||||||
|
if (sliderCallback != null) {
|
||||||
|
sliderCallback.onFullScreenModeChanged(isFullScreen, playerView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInFullScreen() {
|
||||||
|
if (sliderCallback != null) {
|
||||||
|
return sliderCallback.isInFullScreen();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight();
|
final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight();
|
||||||
String videoUrl = null;
|
String videoUrl = null;
|
||||||
@ -139,16 +137,10 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
ResponseBodyUtils.getThumbUrl(media),
|
ResponseBodyUtils.getThumbUrl(media),
|
||||||
loadVideoOnItemClick,
|
loadVideoOnItemClick,
|
||||||
controlsBinding,
|
|
||||||
videoPlayerCallback);
|
videoPlayerCallback);
|
||||||
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
|
|
||||||
// final float newVol = videoPlayerViewHelper.toggleMute();
|
|
||||||
// setMuteIcon(newVol);
|
|
||||||
// Utils.sessionVolumeFull = newVol == 1f;
|
|
||||||
// });
|
|
||||||
binding.playerView.setOnClickListener(v -> {
|
binding.playerView.setOnClickListener(v -> {
|
||||||
if (sliderCallback != null) {
|
if (sliderCallback != null) {
|
||||||
sliderCallback.onItemClicked(position);
|
sliderCallback.onItemClicked(position, media, binding.getRoot());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -162,62 +154,4 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
if (videoPlayerViewHelper == null) return;
|
if (videoPlayerViewHelper == null) return;
|
||||||
videoPlayerViewHelper.releasePlayer();
|
videoPlayerViewHelper.releasePlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetPlayerTimeline() {
|
|
||||||
if (videoPlayerViewHelper == null) return;
|
|
||||||
videoPlayerViewHelper.resetTimeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeCallbacks() {
|
|
||||||
if (videoPlayerViewHelper == null) return;
|
|
||||||
videoPlayerViewHelper.removeCallbacks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
|
||||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
|
||||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
|
||||||
// final int spanWidth = deviceWidth / spanCount;
|
|
||||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
|
||||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
|
||||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
|
||||||
// if (animate) {
|
|
||||||
// Animation animation = AnimationUtils.expand(
|
|
||||||
// binding.imageViewer,
|
|
||||||
// layoutParams.width,
|
|
||||||
// layoutParams.height,
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// new Animation.AnimationListener() {
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationStart(final Animation animation) {
|
|
||||||
// showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationEnd(final Animation animation) {
|
|
||||||
// // showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationRepeat(final Animation animation) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// binding.imageViewer.startAnimation(animation);
|
|
||||||
// } else {
|
|
||||||
// layoutParams.width = width;
|
|
||||||
// layoutParams.height = height;
|
|
||||||
// binding.imageViewer.requestLayout();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void showOrHideDetails(final int spanCount) {
|
|
||||||
// if (spanCount == 1) {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// } else {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final FeedStoryModel model,
|
public void bind(final FeedStoryModel model,
|
||||||
final int position,
|
|
||||||
final OnFeedStoryClickListener notificationClickListener) {
|
final OnFeedStoryClickListener notificationClickListener) {
|
||||||
if (model == null) return;
|
if (model == null) return;
|
||||||
|
|
||||||
@ -53,7 +52,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
itemView.setOnClickListener(v -> {
|
itemView.setOnClickListener(v -> {
|
||||||
if (notificationClickListener == null) return;
|
if (notificationClickListener == null) return;
|
||||||
notificationClickListener.onFeedStoryClick(model, position);
|
notificationClickListener.onFeedStoryClick(model);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,12 +54,14 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (onTopicClickListener != null) {
|
if (onTopicClickListener != null) {
|
||||||
itemView.setOnClickListener(v -> onTopicClickListener.onTopicClick(
|
itemView.setOnClickListener(v -> onTopicClickListener.onTopicClick(
|
||||||
topicCluster,
|
topicCluster,
|
||||||
binding.getRoot(),
|
|
||||||
binding.cover,
|
binding.cover,
|
||||||
binding.title,
|
|
||||||
titleColor.get(),
|
titleColor.get(),
|
||||||
backgroundColor.get()
|
backgroundColor.get()
|
||||||
));
|
));
|
||||||
|
itemView.setOnLongClickListener(v -> {
|
||||||
|
onTopicClickListener.onTopicLongClick(topicCluster.getCoverMedia());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
// binding.title.setTransitionName("title-" + topicCluster.getId());
|
// binding.title.setTransitionName("title-" + topicCluster.getId());
|
||||||
binding.cover.setTransitionName("cover-" + topicCluster.getId());
|
binding.cover.setTransitionName("cover-" + topicCluster.getId());
|
||||||
@ -126,11 +128,11 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
|||||||
backgroundColor.get()
|
backgroundColor.get()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// binding.title.setTransitionName("title-" + topicCluster.getId());
|
// binding.title.setTransitionName("title-" + topicCluster.getCollectionId());
|
||||||
binding.cover.setTransitionName("cover-" + topicCluster.getId());
|
binding.cover.setTransitionName("cover-" + topicCluster.getCollectionId());
|
||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMediaList() == null
|
||||||
? topicCluster.getCoverMedia()
|
? topicCluster.getCoverMedia()
|
||||||
: topicCluster.getCoverMedias().get(0));
|
: topicCluster.getCoverMediaList().get(0));
|
||||||
if (thumbUrl == null) {
|
if (thumbUrl == null) {
|
||||||
binding.cover.setImageURI((String) null);
|
binding.cover.setImageURI((String) null);
|
||||||
} else {
|
} else {
|
||||||
@ -172,6 +174,6 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
|||||||
}, CallerThreadExecutor.getInstance());
|
}, CallerThreadExecutor.getInstance());
|
||||||
binding.cover.setImageRequest(imageRequest);
|
binding.cover.setImageRequest(imageRequest);
|
||||||
}
|
}
|
||||||
binding.title.setText(topicCluster.getTitle());
|
binding.title.setText(topicCluster.getCollectionName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
package awais.instagrabber.adapters.viewholder.comments;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
|
|
||||||
import awais.instagrabber.databinding.ItemCommentSmallBinding;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public final class ChildCommentViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ItemCommentSmallBinding binding;
|
|
||||||
|
|
||||||
public ChildCommentViewHolder(@NonNull final ItemCommentSmallBinding binding) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(final CommentModel comment,
|
|
||||||
final boolean selected,
|
|
||||||
final CommentCallback commentCallback) {
|
|
||||||
if (comment == null) return;
|
|
||||||
if (commentCallback != null) {
|
|
||||||
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
|
|
||||||
} else {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
|
|
||||||
}
|
|
||||||
setupCommentText(comment, commentCallback);
|
|
||||||
binding.tvDate.setText(comment.getDateTime());
|
|
||||||
setLiked(comment.getLiked());
|
|
||||||
setLikes((int) comment.getLikes());
|
|
||||||
setUser(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
|
|
||||||
binding.tvComment.clearOnURLClickListeners();
|
|
||||||
binding.tvComment.clearOnHashtagClickListeners();
|
|
||||||
binding.tvComment.clearOnMentionClickListeners();
|
|
||||||
binding.tvComment.clearOnEmailClickListeners();
|
|
||||||
binding.tvComment.setText(comment.getText());
|
|
||||||
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onHashtagClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onMentionClick(originalText);
|
|
||||||
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onEmailClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onURLClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnLongClickListener(v -> {
|
|
||||||
Utils.copyText(itemView.getContext(), comment.getText());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUser(final CommentModel comment) {
|
|
||||||
final User profileModel = comment.getProfileModel();
|
|
||||||
if (profileModel == null) return;
|
|
||||||
binding.tvUsername.setText(profileModel.getUsername());
|
|
||||||
binding.ivProfilePic.setImageURI(profileModel.getProfilePicUrl());
|
|
||||||
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLikes(final int likes) {
|
|
||||||
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
|
|
||||||
binding.tvLikes.setText(likesString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setLiked(final boolean liked) {
|
|
||||||
if (liked) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package awais.instagrabber.adapters.viewholder.comments;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
|
|
||||||
import awais.instagrabber.databinding.ItemCommentBinding;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public final class ParentCommentViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ItemCommentBinding binding;
|
|
||||||
|
|
||||||
public ParentCommentViewHolder(@NonNull final ItemCommentBinding binding) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(final CommentModel comment,
|
|
||||||
final boolean selected,
|
|
||||||
final CommentCallback commentCallback) {
|
|
||||||
if (comment == null) return;
|
|
||||||
if (commentCallback != null) {
|
|
||||||
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
|
|
||||||
} else {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
|
|
||||||
}
|
|
||||||
setupCommentText(comment, commentCallback);
|
|
||||||
binding.tvDate.setText(comment.getDateTime());
|
|
||||||
setLiked(comment.getLiked());
|
|
||||||
setLikes((int) comment.getLikes());
|
|
||||||
setUser(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
|
|
||||||
binding.tvComment.clearOnURLClickListeners();
|
|
||||||
binding.tvComment.clearOnHashtagClickListeners();
|
|
||||||
binding.tvComment.clearOnMentionClickListeners();
|
|
||||||
binding.tvComment.clearOnEmailClickListeners();
|
|
||||||
binding.tvComment.setText(comment.getText());
|
|
||||||
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onHashtagClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onMentionClick(originalText);
|
|
||||||
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onEmailClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onURLClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnLongClickListener(v -> {
|
|
||||||
Utils.copyText(itemView.getContext(), comment.getText());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUser(final CommentModel comment) {
|
|
||||||
final User profileModel = comment.getProfileModel();
|
|
||||||
if (profileModel == null) return;
|
|
||||||
binding.tvUsername.setText(profileModel.getUsername());
|
|
||||||
binding.ivProfilePic.setImageURI(profileModel.getProfilePicUrl());
|
|
||||||
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLikes(final int likes) {
|
|
||||||
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
|
|
||||||
binding.tvLikes.setText(likesString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setLiked(final boolean liked) {
|
|
||||||
if (liked) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,7 +14,7 @@ import java.util.HashSet;
|
|||||||
|
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.KeywordsFilterAdapter;
|
import awais.instagrabber.adapters.KeywordsFilterAdapter;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||||
import awais.instagrabber.utils.SettingsHelper;
|
import awais.instagrabber.utils.SettingsHelper;
|
||||||
|
|
||||||
public class KeywordsFilterDialogViewHolder extends RecyclerView.ViewHolder {
|
public class KeywordsFilterDialogViewHolder extends RecyclerView.ViewHolder {
|
||||||
@ -34,7 +34,7 @@ public class KeywordsFilterDialogViewHolder extends RecyclerView.ViewHolder {
|
|||||||
final String s = items.get(position);
|
final String s = items.get(position);
|
||||||
SettingsHelper settingsHelper = new SettingsHelper(context);
|
SettingsHelper settingsHelper = new SettingsHelper(context);
|
||||||
items.remove(position);
|
items.remove(position);
|
||||||
settingsHelper.putStringSet(Constants.KEYWORD_FILTERS, new HashSet<>(items));
|
settingsHelper.putStringSet(PreferenceKeys.KEYWORD_FILTERS, new HashSet<>(items));
|
||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
final String message = context.getString(R.string.removed_keywords, s);
|
final String message = context.getString(R.string.removed_keywords, s);
|
||||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||||
|
@ -17,11 +17,9 @@ import java.util.List;
|
|||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
|
import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
|
||||||
import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
|
import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
|
|
||||||
import awais.instagrabber.utils.DMUtils;
|
import awais.instagrabber.utils.DMUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
|
||||||
@ -136,7 +134,7 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
private void setDateTime(@NonNull final DirectItem item) {
|
private void setDateTime(@NonNull final DirectItem item) {
|
||||||
final long timestamp = item.getTimestamp() / 1000;
|
final long timestamp = item.getTimestamp() / 1000;
|
||||||
final String dateTimeString = TextUtils.getRelativeDateTimeString(itemView.getContext(), timestamp);
|
final String dateTimeString = TextUtils.getRelativeDateTimeString(timestamp);
|
||||||
binding.tvDate.setText(dateTimeString);
|
binding.tvDate.setText(dateTimeString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemActionLog;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemActionLog;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.TextRange;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
|
||||||
public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
|
public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
|
||||||
@ -45,16 +46,16 @@ public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
|
|||||||
final DirectItemActionLog actionLog = directItemModel.getActionLog();
|
final DirectItemActionLog actionLog = directItemModel.getActionLog();
|
||||||
final String text = actionLog.getDescription();
|
final String text = actionLog.getDescription();
|
||||||
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||||
final List<DirectItemActionLog.TextRange> bold = actionLog.getBold();
|
final List<TextRange> bold = actionLog.getBold();
|
||||||
if (bold != null && !bold.isEmpty()) {
|
if (bold != null && !bold.isEmpty()) {
|
||||||
for (final DirectItemActionLog.TextRange textRange : bold) {
|
for (final TextRange textRange : bold) {
|
||||||
final StyleSpan boldStyleSpan = new StyleSpan(Typeface.BOLD);
|
final StyleSpan boldStyleSpan = new StyleSpan(Typeface.BOLD);
|
||||||
sb.setSpan(boldStyleSpan, textRange.getStart(), textRange.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
sb.setSpan(boldStyleSpan, textRange.getStart(), textRange.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final List<DirectItemActionLog.TextRange> textAttributes = actionLog.getTextAttributes();
|
final List<TextRange> textAttributes = actionLog.getTextAttributes();
|
||||||
if (textAttributes != null && !textAttributes.isEmpty()) {
|
if (textAttributes != null && !textAttributes.isEmpty()) {
|
||||||
for (final DirectItemActionLog.TextRange textAttribute : textAttributes) {
|
for (final TextRange textAttribute : textAttributes) {
|
||||||
if (!TextUtils.isEmpty(textAttribute.getColor())) {
|
if (!TextUtils.isEmpty(textAttribute.getColor())) {
|
||||||
final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
|
final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
|
||||||
sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
@ -23,6 +22,7 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
|||||||
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
|
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
|
||||||
if (fixedHeight == null) return;
|
if (fixedHeight == null) return;
|
||||||
final String url = fixedHeight.getWebp();
|
final String url = fixedHeight.getWebp();
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
fixedHeight.getHeight(),
|
fixedHeight.getHeight(),
|
||||||
fixedHeight.getWidth(),
|
fixedHeight.getWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
@ -56,8 +56,8 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
|||||||
);
|
);
|
||||||
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
||||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
final int width = widthHeight.first;
|
||||||
final int height = widthHeight.second != null ? widthHeight.second : 0;
|
final int height = widthHeight.second;
|
||||||
layoutParams.width = width;
|
layoutParams.width = width;
|
||||||
layoutParams.height = height;
|
layoutParams.height = height;
|
||||||
binding.ivAnimatedMessage.requestLayout();
|
binding.ivAnimatedMessage.requestLayout();
|
||||||
|
@ -6,7 +6,6 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
@ -31,6 +30,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
@ -103,15 +103,15 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
|||||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||||
.setRoundingParams(roundingParams)
|
.setRoundingParams(roundingParams)
|
||||||
.build());
|
.build());
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
mediaImageMaxWidth
|
mediaImageMaxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.mediaPreview.requestLayout();
|
binding.mediaPreview.requestLayout();
|
||||||
binding.mediaPreview.setTag(url);
|
binding.mediaPreview.setTag(url);
|
||||||
binding.mediaPreview.setImageURI(url);
|
binding.mediaPreview.setImageURI(url);
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
@ -14,11 +13,11 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
|||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
import awais.instagrabber.databinding.LayoutDmMediaBinding;
|
import awais.instagrabber.databinding.LayoutDmMediaBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.ImageVersions2;
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
|
|
||||||
@ -53,16 +52,16 @@ public class DirectItemMediaViewHolder extends DirectItemViewHolder {
|
|||||||
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
||||||
? View.VISIBLE
|
? View.VISIBLE
|
||||||
: View.GONE);
|
: View.GONE);
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
mediaImageMaxWidth
|
mediaImageMaxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
||||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
final int width = widthHeight.first;
|
||||||
layoutParams.width = width;
|
layoutParams.width = width;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.mediaPreview.requestLayout();
|
binding.mediaPreview.requestLayout();
|
||||||
binding.bgTime.getLayoutParams().width = width;
|
binding.bgTime.getLayoutParams().width = width;
|
||||||
binding.bgTime.requestLayout();
|
binding.bgTime.requestLayout();
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
@ -21,6 +20,7 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
@ -170,15 +170,15 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
|
|||||||
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
||||||
? View.VISIBLE
|
? View.VISIBLE
|
||||||
: View.GONE);
|
: View.GONE);
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
maxWidth
|
maxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
|
||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.preview.requestLayout();
|
binding.preview.requestLayout();
|
||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
|
||||||
binding.preview.setImageURI(thumbUrl);
|
binding.preview.setImageURI(thumbUrl);
|
||||||
|
@ -5,7 +5,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
@ -17,12 +16,12 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
|||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
|
import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.ImageVersions2;
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
@ -76,15 +75,15 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
|
|||||||
.setRoundingParams(roundingParams)
|
.setRoundingParams(roundingParams)
|
||||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||||
.build());
|
.build());
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
storyShareMedia.getOriginalHeight(),
|
storyShareMedia.getOriginalHeight(),
|
||||||
storyShareMedia.getOriginalWidth(),
|
storyShareMedia.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
mediaImageMaxWidth
|
mediaImageMaxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
|
||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.ivMediaPreview.requestLayout();
|
binding.ivMediaPreview.requestLayout();
|
||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
|
||||||
binding.ivMediaPreview.setImageURI(thumbUrl);
|
binding.ivMediaPreview.setImageURI(thumbUrl);
|
||||||
|
@ -17,9 +17,9 @@ import awais.instagrabber.databinding.LayoutDmActionLogBinding;
|
|||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemActionLog;
|
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemVideoCallEvent;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemVideoCallEvent;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.TextRange;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
|
||||||
public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
|
public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
|
||||||
@ -41,9 +41,9 @@ public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
|
|||||||
final DirectItemVideoCallEvent videoCallEvent = directItemModel.getVideoCallEvent();
|
final DirectItemVideoCallEvent videoCallEvent = directItemModel.getVideoCallEvent();
|
||||||
final String text = videoCallEvent.getDescription();
|
final String text = videoCallEvent.getDescription();
|
||||||
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||||
final List<DirectItemActionLog.TextRange> textAttributes = videoCallEvent.getTextAttributes();
|
final List<TextRange> textAttributes = videoCallEvent.getTextAttributes();
|
||||||
if (textAttributes != null && !textAttributes.isEmpty()) {
|
if (textAttributes != null && !textAttributes.isEmpty()) {
|
||||||
for (final DirectItemActionLog.TextRange textAttribute : textAttributes) {
|
for (final TextRange textAttribute : textAttributes) {
|
||||||
if (!TextUtils.isEmpty(textAttribute.getColor())) {
|
if (!TextUtils.isEmpty(textAttribute.getColor())) {
|
||||||
final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
|
final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
|
||||||
sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
|
@ -5,7 +5,6 @@ import android.content.res.ColorStateList;
|
|||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
@ -25,6 +24,9 @@ import androidx.transition.TransitionManager;
|
|||||||
import com.google.android.material.transition.MaterialFade;
|
import com.google.android.material.transition.MaterialFade;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.FormatStyle;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@ -144,7 +146,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
}
|
}
|
||||||
setupReply(item, messageDirection);
|
setupReply(item, messageDirection);
|
||||||
setReactions(item, position);
|
setReactions(item, position);
|
||||||
if (item.getRepliedToMessage() == null && item.showForwardAttribution()) {
|
if (item.getRepliedToMessage() == null && item.getShowForwardAttribution()) {
|
||||||
setForwardInfo(messageDirection);
|
setForwardInfo(messageDirection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +165,9 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
binding.ivProfilePic.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
|
binding.ivProfilePic.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
|
||||||
binding.tvUsername.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
|
binding.tvUsername.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
|
||||||
if (messageDirection == MessageDirection.INCOMING && thread.isGroup()) {
|
if (messageDirection == MessageDirection.INCOMING && thread.isGroup()) {
|
||||||
final User user = getUser(item.getUserId(), thread.getUsers());
|
final List<User> allUsers = new LinkedList(thread.getUsers());
|
||||||
|
allUsers.addAll(thread.getLeftUsers());
|
||||||
|
final User user = getUser(item.getUserId(), allUsers);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
binding.tvUsername.setText(user.getUsername());
|
binding.tvUsername.setText(user.getUsername());
|
||||||
binding.ivProfilePic.setImageURI(user.getProfilePicUrl());
|
binding.ivProfilePic.setImageURI(user.getProfilePicUrl());
|
||||||
@ -193,7 +197,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
if (showMessageInfo()) {
|
if (showMessageInfo()) {
|
||||||
binding.messageInfo.setVisibility(View.VISIBLE);
|
binding.messageInfo.setVisibility(View.VISIBLE);
|
||||||
binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE);
|
binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE);
|
||||||
binding.messageTime.setText(DateFormat.getTimeFormat(itemView.getContext()).format(item.getDate()));
|
if (item.getDate() != null) {
|
||||||
|
final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
|
||||||
|
binding.messageTime.setText(dateFormatter.format(item.getDate()));
|
||||||
|
}
|
||||||
if (messageDirection == MessageDirection.OUTGOING) {
|
if (messageDirection == MessageDirection.OUTGOING) {
|
||||||
if (item.isPending()) {
|
if (item.isPending()) {
|
||||||
binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
|
binding.deliveryStatus.setImageResource(R.drawable.ic_check_24);
|
||||||
@ -216,7 +223,9 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
|
|
||||||
private void setupReply(final DirectItem item, final MessageDirection messageDirection) {
|
private void setupReply(final DirectItem item, final MessageDirection messageDirection) {
|
||||||
if (item.getRepliedToMessage() != null) {
|
if (item.getRepliedToMessage() != null) {
|
||||||
setReply(item, messageDirection, thread.getUsers());
|
final List<User> allUsers = new LinkedList(thread.getUsers());
|
||||||
|
allUsers.addAll(thread.getLeftUsers());
|
||||||
|
setReply(item, messageDirection, allUsers);
|
||||||
} else {
|
} else {
|
||||||
binding.quoteLine.setVisibility(View.GONE);
|
binding.quoteLine.setVisibility(View.GONE);
|
||||||
binding.replyContainer.setVisibility(View.GONE);
|
binding.replyContainer.setVisibility(View.GONE);
|
||||||
@ -551,6 +560,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
menu.setOnDismissListener(() -> setSelected(false));
|
menu.setOnDismissListener(() -> setSelected(false));
|
||||||
menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
|
menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
|
||||||
menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb));
|
menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb));
|
||||||
|
menu.setOnAddReactionListener(() -> {
|
||||||
|
menu.dismiss();
|
||||||
|
itemView.postDelayed(() -> callback.onAddReactionListener(item), 300);
|
||||||
|
});
|
||||||
menu.show(itemView, location);
|
menu.show(itemView, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
@ -16,6 +15,8 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemXma;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemXma;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.repositories.responses.directmessages.XmaUrlInfo;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
|
|
||||||
public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
||||||
@ -35,15 +36,15 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
|||||||
@Override
|
@Override
|
||||||
public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
|
public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
|
||||||
final DirectItemXma xma = item.getXma();
|
final DirectItemXma xma = item.getXma();
|
||||||
final DirectItemXma.XmaUrlInfo playableUrlInfo = xma.getPlayableUrlInfo();
|
final XmaUrlInfo playableUrlInfo = xma.getPlayableUrlInfo();
|
||||||
final DirectItemXma.XmaUrlInfo previewUrlInfo = xma.getPreviewUrlInfo();
|
final XmaUrlInfo previewUrlInfo = xma.getPreviewUrlInfo();
|
||||||
if (playableUrlInfo == null && previewUrlInfo == null) {
|
if (playableUrlInfo == null && previewUrlInfo == null) {
|
||||||
binding.ivAnimatedMessage.setController(null);
|
binding.ivAnimatedMessage.setController(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo;
|
final XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo;
|
||||||
final String url = urlInfo.getUrl();
|
final String url = urlInfo.getUrl();
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
urlInfo.getHeight(),
|
urlInfo.getHeight(),
|
||||||
urlInfo.getWidth(),
|
urlInfo.getWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
@ -51,8 +52,8 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
|||||||
);
|
);
|
||||||
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
||||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
final int width = widthHeight.first;
|
||||||
final int height = widthHeight.second != null ? widthHeight.second : 0;
|
final int height = widthHeight.second;
|
||||||
layoutParams.width = width;
|
layoutParams.width = width;
|
||||||
layoutParams.height = height;
|
layoutParams.height = height;
|
||||||
binding.ivAnimatedMessage.requestLayout();
|
binding.ivAnimatedMessage.requestLayout();
|
||||||
|
@ -31,7 +31,7 @@ public class DirectReactionViewHolder extends RecyclerView.ViewHolder {
|
|||||||
this.onReactionClickListener = onReactionClickListener;
|
this.onReactionClickListener = onReactionClickListener;
|
||||||
binding.info.setVisibility(View.GONE);
|
binding.info.setVisibility(View.GONE);
|
||||||
binding.secondaryImage.setVisibility(View.VISIBLE);
|
binding.secondaryImage.setVisibility(View.VISIBLE);
|
||||||
emojiParser = EmojiParser.getInstance();
|
emojiParser = EmojiParser.Companion.getInstance(itemView.getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final DirectItemEmojiReaction reaction,
|
public void bind(final DirectItemEmojiReaction reaction,
|
||||||
|
@ -37,7 +37,7 @@ public class RecipientThreadViewHolder extends RecyclerView.ViewHolder {
|
|||||||
final DirectThread thread,
|
final DirectThread thread,
|
||||||
final boolean showSelection,
|
final boolean showSelection,
|
||||||
final boolean isSelected) {
|
final boolean isSelected) {
|
||||||
if (thread == null) return;
|
if (thread == null || thread.getUsers().size() == 0) return;
|
||||||
binding.getRoot().setOnClickListener(v -> {
|
binding.getRoot().setOnClickListener(v -> {
|
||||||
if (onThreadClickListener == null) return;
|
if (onThreadClickListener == null) return;
|
||||||
onThreadClickListener.onClick(position, RankedRecipient.of(thread), isSelected);
|
onThreadClickListener.onClick(position, RankedRecipient.of(thread), isSelected);
|
||||||
|
@ -55,7 +55,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
private void setupComments(@NonNull final Media feedModel) {
|
private void setupComments(@NonNull final Media feedModel) {
|
||||||
final long commentsCount = feedModel.getCommentCount();
|
final long commentsCount = feedModel.getCommentCount();
|
||||||
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
|
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
|
||||||
bottomBinding.commentsCount.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
|
bottomBinding.btnComments.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupProfilePic(@NonNull final Media media) {
|
private void setupProfilePic(@NonNull final Media media) {
|
||||||
@ -75,6 +75,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
// final SpannableString spannableString = new SpannableString();
|
// final SpannableString spannableString = new SpannableString();
|
||||||
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
|
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
|
||||||
final User user = media.getUser();
|
final User user = media.getUser();
|
||||||
|
if (user == null) return;
|
||||||
final String title = "@" + user.getUsername();
|
final String title = "@" + user.getUsername();
|
||||||
topBinding.title.setText(title);
|
topBinding.title.setText(title);
|
||||||
topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(media, topBinding.ivProfilePic));
|
topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(media, topBinding.ivProfilePic));
|
||||||
@ -120,8 +121,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
|
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
|
||||||
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
|
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
|
||||||
));
|
));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
final String locationName = location.getName();
|
final String locationName = location.getName();
|
||||||
if (TextUtils.isEmpty(locationName)) {
|
if (TextUtils.isEmpty(locationName)) {
|
||||||
topBinding.location.setVisibility(View.GONE);
|
topBinding.location.setVisibility(View.GONE);
|
||||||
|
@ -45,9 +45,9 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
|
|||||||
final String text = "1/" + sliderItemLen;
|
final String text = "1/" + sliderItemLen;
|
||||||
binding.mediaCounter.setText(text);
|
binding.mediaCounter.setText(text);
|
||||||
binding.mediaList.setOffscreenPageLimit(1);
|
binding.mediaList.setOffscreenPageLimit(1);
|
||||||
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, null, false, new SliderCallbackAdapter() {
|
final SliderItemsAdapter adapter = new SliderItemsAdapter(false, new SliderCallbackAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClicked(final int position) {
|
public void onItemClicked(final int position, final Media media, final View view) {
|
||||||
feedItemCallback.onSliderClick(feedModel, position);
|
feedItemCallback.onSliderClick(feedModel, position);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -18,9 +18,9 @@ import awais.instagrabber.adapters.FeedAdapterV2;
|
|||||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||||
import awais.instagrabber.databinding.ItemFeedVideoBinding;
|
import awais.instagrabber.databinding.ItemFeedVideoBinding;
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.VideoVersion;
|
import awais.instagrabber.repositories.responses.VideoVersion;
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
@ -65,7 +65,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
|||||||
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
|
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
|
||||||
this.media = media;
|
this.media = media;
|
||||||
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(media.getViewCount()));
|
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(media.getViewCount()));
|
||||||
final float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
|
final float vol = settingsHelper.getBoolean(PreferenceKeys.MUTED_VIDEOS) ? 0f : 1f;
|
||||||
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
|
final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -97,7 +97,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
ResponseBodyUtils.getThumbUrl(media),
|
ResponseBodyUtils.getThumbUrl(media),
|
||||||
false,
|
false,
|
||||||
null,
|
// null,
|
||||||
videoPlayerCallback);
|
videoPlayerCallback);
|
||||||
binding.videoPost.thumbnail.post(() -> {
|
binding.videoPost.thumbnail.post(() -> {
|
||||||
if (media.getOriginalHeight() > 0.8 * Utils.displayMetrics.heightPixels) {
|
if (media.getOriginalHeight() > 0.8 * Utils.displayMetrics.heightPixels) {
|
||||||
|
@ -1,268 +0,0 @@
|
|||||||
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.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.BuildConfig;
|
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.FriendshipStatus;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.NetworkUtils;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
//import awaisomereport.LogCollector;
|
|
||||||
|
|
||||||
//import static awais.instagrabber.utils.Utils.logCollector;
|
|
||||||
|
|
||||||
public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
|
|
||||||
private static final String TAG = "CommentsFetcher";
|
|
||||||
|
|
||||||
private final String shortCode, endCursor;
|
|
||||||
private final FetchListener<List<CommentModel>> fetchListener;
|
|
||||||
|
|
||||||
public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener<List<CommentModel>> fetchListener) {
|
|
||||||
this.shortCode = shortCode;
|
|
||||||
this.endCursor = endCursor;
|
|
||||||
this.fetchListener = fetchListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected List<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 List<CommentModel> commentModels = getParentComments();
|
|
||||||
if (commentModels != null) {
|
|
||||||
for (final CommentModel commentModel : commentModels) {
|
|
||||||
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
|
|
||||||
if (childCommentModels != null) {
|
|
||||||
final int childCommentsLen = childCommentModels.size();
|
|
||||||
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
|
|
||||||
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
|
|
||||||
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
|
|
||||||
commentModel.setChildCommentModels(remoteChildComments);
|
|
||||||
lastChild.setPageCursor(false, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commentModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
if (fetchListener != null) fetchListener.doBefore();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(final List<CommentModel> result) {
|
|
||||||
if (fetchListener != null) fetchListener.onResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private synchronized List<CommentModel> getChildComments(final String commentId) {
|
|
||||||
final List<CommentModel> commentModels = new ArrayList<>();
|
|
||||||
String childEndCursor = "";
|
|
||||||
while (childEndCursor != null) {
|
|
||||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
|
|
||||||
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}";
|
|
||||||
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(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
|
|
||||||
.getJSONObject("comment")
|
|
||||||
.getJSONObject("edge_threaded_comments");
|
|
||||||
|
|
||||||
final JSONObject pageInfo = data.getJSONObject("page_info");
|
|
||||||
childEndCursor = pageInfo.getString("end_cursor");
|
|
||||||
if (TextUtils.isEmpty(childEndCursor)) childEndCursor = 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 User user = new User(
|
|
||||||
owner.optLong(Constants.EXTRAS_ID, 0),
|
|
||||||
owner.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
owner.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
|
|
||||||
false, false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null,
|
|
||||||
null, null, null);
|
|
||||||
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,
|
|
||||||
childComment.getBoolean("viewer_has_liked"),
|
|
||||||
user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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(TAG, "", e);
|
|
||||||
if (fetchListener != null) fetchListener.onFailure(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return commentModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private synchronized List<CommentModel> getParentComments() {
|
|
||||||
final List<CommentModel> commentModels = new ArrayList<>();
|
|
||||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" +
|
|
||||||
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}";
|
|
||||||
try {
|
|
||||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
conn.setUseCaches(false);
|
|
||||||
conn.connect();
|
|
||||||
|
|
||||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null;
|
|
||||||
else {
|
|
||||||
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
|
|
||||||
.getJSONObject("shortcode_media")
|
|
||||||
.getJSONObject(
|
|
||||||
"edge_media_to_parent_comment");
|
|
||||||
|
|
||||||
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
|
|
||||||
final String foundEndCursor = pageInfo.optString("end_cursor");
|
|
||||||
final boolean hasNextPage = pageInfo.optBoolean("has_next_page", !TextUtils.isEmpty(foundEndCursor));
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
for (int i = 0; i < commentsLen; ++i) {
|
|
||||||
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node");
|
|
||||||
|
|
||||||
final JSONObject owner = comment.getJSONObject("owner");
|
|
||||||
final User user = new User(
|
|
||||||
owner.optLong(Constants.EXTRAS_ID, 0),
|
|
||||||
owner.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
owner.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
|
|
||||||
owner.optBoolean("is_verified"),
|
|
||||||
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null,
|
|
||||||
null, null);
|
|
||||||
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
|
|
||||||
final String commentId = comment.getString(Constants.EXTRAS_ID);
|
|
||||||
final CommentModel commentModel = new CommentModel(commentId,
|
|
||||||
comment.getString("text"),
|
|
||||||
comment.getLong("created_at"),
|
|
||||||
likedBy != null ? likedBy.optLong("count", 0) : 0,
|
|
||||||
comment.getBoolean("viewer_has_liked"),
|
|
||||||
user);
|
|
||||||
if (i == 0 && !foundEndCursor.contains("tao_cursor"))
|
|
||||||
commentModel.setPageCursor(hasNextPage, TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor);
|
|
||||||
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 childHasNextPage;
|
|
||||||
if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) {
|
|
||||||
childEndCursor = tempJsonObject.optString("end_cursor");
|
|
||||||
childHasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor));
|
|
||||||
} else {
|
|
||||||
childEndCursor = null;
|
|
||||||
childHasNextPage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<CommentModel> childCommentModels = new ArrayList<>();
|
|
||||||
for (int j = 0; j < childCommentsLen; ++j) {
|
|
||||||
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node");
|
|
||||||
|
|
||||||
tempJsonObject = childComment.getJSONObject("owner");
|
|
||||||
final User childUser = new User(
|
|
||||||
tempJsonObject.optLong(Constants.EXTRAS_ID, 0),
|
|
||||||
tempJsonObject.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
tempJsonObject.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
|
|
||||||
tempJsonObject.optBoolean("is_verified"), false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0,
|
|
||||||
null, null, null, null, null, null);
|
|
||||||
|
|
||||||
tempJsonObject = childComment.optJSONObject("edge_liked_by");
|
|
||||||
childCommentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
|
|
||||||
childComment.getString("text"),
|
|
||||||
childComment.getLong("created_at"),
|
|
||||||
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
|
|
||||||
childComment.getBoolean("viewer_has_liked"),
|
|
||||||
childUser));
|
|
||||||
}
|
|
||||||
childCommentModels.get(childCommentsLen - 1).setPageCursor(childHasNextPage, childEndCursor);
|
|
||||||
commentModel.setChildCommentModels(childCommentModels);
|
|
||||||
}
|
|
||||||
commentModels.add(commentModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.disconnect();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
// if (logCollector != null)
|
|
||||||
// logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
|
|
||||||
// new Pair<>("commentModelsList.size", commentModels.size()));
|
|
||||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
|
||||||
if (fetchListener != null) fetchListener.onFailure(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return commentModels;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package awais.instagrabber.asyncs;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
import awais.instagrabber.webservices.DirectMessagesService;
|
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
public class CreateThreadAction extends AsyncTask<Void, Void, Void> {
|
|
||||||
private static final String TAG = "CommentAction";
|
|
||||||
|
|
||||||
private final String cookie;
|
|
||||||
private final long userId;
|
|
||||||
private final OnTaskCompleteListener onTaskCompleteListener;
|
|
||||||
private final DirectMessagesService directMessagesService;
|
|
||||||
|
|
||||||
public CreateThreadAction(final String cookie, final long userId, final OnTaskCompleteListener onTaskCompleteListener) {
|
|
||||||
this.cookie = cookie;
|
|
||||||
this.userId = userId;
|
|
||||||
this.onTaskCompleteListener = onTaskCompleteListener;
|
|
||||||
directMessagesService = DirectMessagesService.getInstance(CookieUtils.getCsrfTokenFromCookie(cookie),
|
|
||||||
CookieUtils.getUserIdFromCookie(cookie),
|
|
||||||
Utils.settingsHelper.getString(Constants.DEVICE_UUID));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Void doInBackground(Void... lmao) {
|
|
||||||
final Call<DirectThread> createThreadRequest = directMessagesService.createThread(Collections.singletonList(userId), null);
|
|
||||||
createThreadRequest.enqueue(new Callback<DirectThread>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull final Call<DirectThread> call, @NonNull final Response<DirectThread> response) {
|
|
||||||
if (!response.isSuccessful()) {
|
|
||||||
if (response.errorBody() != null) {
|
|
||||||
try {
|
|
||||||
final String string = response.errorBody().string();
|
|
||||||
final String msg = String.format(Locale.US,
|
|
||||||
"onResponse: url: %s, responseCode: %d, errorBody: %s",
|
|
||||||
call.request().url().toString(),
|
|
||||||
response.code(),
|
|
||||||
string);
|
|
||||||
Log.e(TAG, msg);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "onResponse: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.e(TAG, "onResponse: request was not successful and response error body was null");
|
|
||||||
}
|
|
||||||
onTaskCompleteListener.onTaskComplete(response.body());
|
|
||||||
if (response.body() == null) {
|
|
||||||
Log.e(TAG, "onResponse: thread is null");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull final Call<DirectThread> call, @NonNull final Throwable t) {
|
|
||||||
onTaskCompleteListener.onTaskComplete(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// protected void onPostExecute() {
|
|
||||||
// }
|
|
||||||
|
|
||||||
public interface OnTaskCompleteListener {
|
|
||||||
void onTaskComplete(final DirectThread thread);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package awais.instagrabber.asyncs;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
|
||||||
|
|
||||||
public final class DownloadedCheckerAsyncTask extends AsyncTask<Media, Void, Map<String, List<Boolean>>> {
|
|
||||||
private static final String TAG = "DownloadedCheckerAsyncTask";
|
|
||||||
|
|
||||||
private final OnCheckResultListener listener;
|
|
||||||
|
|
||||||
public DownloadedCheckerAsyncTask(final OnCheckResultListener listener) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Map<String, List<Boolean>> doInBackground(final Media... feedModels) {
|
|
||||||
if (feedModels == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final Map<String, List<Boolean>> map = new HashMap<>();
|
|
||||||
for (final Media media : feedModels) {
|
|
||||||
map.put(media.getPk(), DownloadUtils.checkDownloaded(media));
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(final Map<String, List<Boolean>> result) {
|
|
||||||
if (listener == null) return;
|
|
||||||
listener.onResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnCheckResultListener {
|
|
||||||
void onResult(final Map<String, List<Boolean>> result);
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,13 +7,15 @@ import awais.instagrabber.interfaces.FetchListener;
|
|||||||
import awais.instagrabber.repositories.responses.Hashtag;
|
import awais.instagrabber.repositories.responses.Hashtag;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||||
import awais.instagrabber.webservices.GraphQLService;
|
import awais.instagrabber.utils.CoroutineUtilsKt;
|
||||||
|
import awais.instagrabber.webservices.GraphQLRepository;
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
import awais.instagrabber.webservices.TagsService;
|
import awais.instagrabber.webservices.TagsService;
|
||||||
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
|
||||||
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
||||||
private final TagsService tagsService;
|
private final TagsService tagsService;
|
||||||
private final GraphQLService graphQLService;
|
private final GraphQLRepository graphQLRepository;
|
||||||
private final Hashtag hashtagModel;
|
private final Hashtag hashtagModel;
|
||||||
private String nextMaxId;
|
private String nextMaxId;
|
||||||
private boolean moreAvailable;
|
private boolean moreAvailable;
|
||||||
@ -23,7 +25,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
|||||||
this.hashtagModel = hashtagModel;
|
this.hashtagModel = hashtagModel;
|
||||||
this.isLoggedIn = isLoggedIn;
|
this.isLoggedIn = isLoggedIn;
|
||||||
tagsService = isLoggedIn ? TagsService.getInstance() : null;
|
tagsService = isLoggedIn ? TagsService.getInstance() : null;
|
||||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -48,7 +50,17 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
|
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
|
||||||
else graphQLService.fetchHashtagPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
|
else graphQLRepository.fetchHashtagPosts(
|
||||||
|
hashtagModel.getName().toLowerCase(),
|
||||||
|
nextMaxId,
|
||||||
|
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
cb.onFailure(throwable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.onSuccess(postsFetchResponse);
|
||||||
|
}, Dispatchers.getIO())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,13 +7,15 @@ import awais.instagrabber.interfaces.FetchListener;
|
|||||||
import awais.instagrabber.repositories.responses.Location;
|
import awais.instagrabber.repositories.responses.Location;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||||
import awais.instagrabber.webservices.GraphQLService;
|
import awais.instagrabber.utils.CoroutineUtilsKt;
|
||||||
|
import awais.instagrabber.webservices.GraphQLRepository;
|
||||||
import awais.instagrabber.webservices.LocationService;
|
import awais.instagrabber.webservices.LocationService;
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
|
||||||
public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
||||||
private final LocationService locationService;
|
private final LocationService locationService;
|
||||||
private final GraphQLService graphQLService;
|
private final GraphQLRepository graphQLRepository;
|
||||||
private final Location locationModel;
|
private final Location locationModel;
|
||||||
private String nextMaxId;
|
private String nextMaxId;
|
||||||
private boolean moreAvailable;
|
private boolean moreAvailable;
|
||||||
@ -23,7 +25,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
|||||||
this.locationModel = locationModel;
|
this.locationModel = locationModel;
|
||||||
this.isLoggedIn = isLoggedIn;
|
this.isLoggedIn = isLoggedIn;
|
||||||
locationService = isLoggedIn ? LocationService.getInstance() : null;
|
locationService = isLoggedIn ? LocationService.getInstance() : null;
|
||||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -48,7 +50,17 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLoggedIn) locationService.fetchPosts(locationModel.getPk(), nextMaxId, cb);
|
if (isLoggedIn) locationService.fetchPosts(locationModel.getPk(), nextMaxId, cb);
|
||||||
else graphQLService.fetchLocationPosts(locationModel.getPk(), nextMaxId, cb);
|
else graphQLRepository.fetchLocationPosts(
|
||||||
|
locationModel.getPk(),
|
||||||
|
nextMaxId,
|
||||||
|
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
cb.onFailure(throwable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.onSuccess(postsFetchResponse);
|
||||||
|
}, Dispatchers.getIO())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,160 +0,0 @@
|
|||||||
package awais.instagrabber.asyncs;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
|
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
|
||||||
import awais.instagrabber.utils.NetworkUtils;
|
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
|
||||||
//import awaisomereport.LogCollector;
|
|
||||||
|
|
||||||
//import static awais.instagrabber.utils.Utils.logCollector;
|
|
||||||
|
|
||||||
public final class PostFetcher extends AsyncTask<Void, Void, Media> {
|
|
||||||
private static final String TAG = "PostFetcher";
|
|
||||||
|
|
||||||
private final String shortCode;
|
|
||||||
private final FetchListener<Media> fetchListener;
|
|
||||||
|
|
||||||
public PostFetcher(final String shortCode, final FetchListener<Media> fetchListener) {
|
|
||||||
this.shortCode = shortCode;
|
|
||||||
this.fetchListener = fetchListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Media doInBackground(final Void... voids) {
|
|
||||||
HttpURLConnection conn = null;
|
|
||||||
try {
|
|
||||||
conn = (HttpURLConnection) new URL("https://www.instagram.com/p/" + shortCode + "/?__a=1").openConnection();
|
|
||||||
conn.setUseCaches(false);
|
|
||||||
conn.connect();
|
|
||||||
|
|
||||||
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
|
||||||
|
|
||||||
final JSONObject media = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql")
|
|
||||||
.getJSONObject("shortcode_media");
|
|
||||||
// ProfileModel profileModel = null;
|
|
||||||
// if (media.has("owner")) {
|
|
||||||
// final JSONObject owner = media.getJSONObject("owner");
|
|
||||||
// profileModel = new ProfileModel(
|
|
||||||
// owner.optBoolean("is_private"),
|
|
||||||
// owner.optBoolean("is_private"),
|
|
||||||
// owner.optBoolean("is_verified"),
|
|
||||||
// owner.optString("id"),
|
|
||||||
// owner.optString("username"),
|
|
||||||
// owner.optString("full_name"),
|
|
||||||
// null,
|
|
||||||
// null,
|
|
||||||
// owner.optString("profile_pic_url"),
|
|
||||||
// owner.optString("profile_pic_url"),
|
|
||||||
// owner.optInt("edge_owner_to_timeline_media"),
|
|
||||||
// owner.optInt("edge_followed_by"),
|
|
||||||
// -1,
|
|
||||||
// owner.optBoolean("followed_by_viewer"),
|
|
||||||
// false,
|
|
||||||
// owner.optBoolean("restricted_by_viewer"),
|
|
||||||
// owner.optBoolean("blocked_by_viewer"),
|
|
||||||
// owner.optBoolean("requested_by_viewer")
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// 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;
|
|
||||||
// final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
|
|
||||||
// .setItemType(mediaItemType)
|
|
||||||
// .setPostId(media.getString(Constants.EXTRAS_ID))
|
|
||||||
// .setDisplayUrl(isVideo ? media.getString("video_url")
|
|
||||||
// : ResponseBodyUtils.getHighQualityImage(media))
|
|
||||||
// .setThumbnailUrl(media.getString("display_url"))
|
|
||||||
// .setImageHeight(media.getJSONObject("dimensions").getInt("height"))
|
|
||||||
// .setImageWidth(media.getJSONObject("dimensions").getInt("width"))
|
|
||||||
// .setShortCode(shortCode)
|
|
||||||
// .setPostCaption(TextUtils.isEmpty(postCaption) ? null : postCaption)
|
|
||||||
// .setProfileModel(profileModel)
|
|
||||||
// .setViewCount(isVideo && media.has("video_view_count")
|
|
||||||
// ? media.getLong("video_view_count")
|
|
||||||
// : -1)
|
|
||||||
// .setTimestamp(timestamp)
|
|
||||||
// .setLiked(media.getBoolean("viewer_has_liked"))
|
|
||||||
// .setBookmarked(media.getBoolean("viewer_has_saved"))
|
|
||||||
// .setLikesCount(media.getJSONObject("edge_media_preview_like")
|
|
||||||
// .getLong("count"))
|
|
||||||
// .setLocationName(media.isNull("location")
|
|
||||||
// ? null
|
|
||||||
// : media.getJSONObject("location").optString("name"))
|
|
||||||
// .setLocationId(media.isNull("location")
|
|
||||||
// ? null
|
|
||||||
// : media.getJSONObject("location").optString("id"))
|
|
||||||
// .setCommentsCount(commentsCount);
|
|
||||||
// if (isSlider) {
|
|
||||||
// final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
|
|
||||||
// final List<PostChild> postModels = new ArrayList<>();
|
|
||||||
// for (int i = 0; i < children.length(); ++i) {
|
|
||||||
// final JSONObject childNode = children.getJSONObject(i).getJSONObject("node");
|
|
||||||
// final boolean isChildVideo = childNode.getBoolean("is_video");
|
|
||||||
// postModels.add(new PostChild.Builder()
|
|
||||||
// .setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
|
|
||||||
// : MediaItemType.MEDIA_TYPE_IMAGE)
|
|
||||||
// .setDisplayUrl(isChildVideo ? childNode.getString("video_url")
|
|
||||||
// : childNode.getString("display_url"))
|
|
||||||
// .setShortCode(media.getString(Constants.EXTRAS_SHORTCODE))
|
|
||||||
// .setVideoViews(isChildVideo && childNode.has("video_view_count")
|
|
||||||
// ? childNode.getLong("video_view_count")
|
|
||||||
// : -1)
|
|
||||||
// .setThumbnailUrl(childNode.getString("display_url"))
|
|
||||||
// .setHeight(childNode.getJSONObject("dimensions").getInt("height"))
|
|
||||||
// .setWidth(childNode.getJSONObject("dimensions").getInt("width"))
|
|
||||||
// .build());
|
|
||||||
// }
|
|
||||||
// feedModelBuilder.setSliderItems(postModels);
|
|
||||||
// }
|
|
||||||
// return feedModelBuilder.build();
|
|
||||||
return ResponseBodyUtils.parseGraphQLItem(media, null);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// if (logCollector != null) {
|
|
||||||
// logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground");
|
|
||||||
// }
|
|
||||||
Log.e(TAG, "Error fetching post", e);
|
|
||||||
} finally {
|
|
||||||
if (conn != null) {
|
|
||||||
conn.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
if (fetchListener != null) fetchListener.doBefore();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(final Media feedModel) {
|
|
||||||
if (fetchListener != null) fetchListener.onResult(feedModel);
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,14 +7,16 @@ import awais.instagrabber.interfaces.FetchListener;
|
|||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.webservices.GraphQLService;
|
import awais.instagrabber.utils.CoroutineUtilsKt;
|
||||||
|
import awais.instagrabber.webservices.GraphQLRepository;
|
||||||
import awais.instagrabber.webservices.ProfileService;
|
import awais.instagrabber.webservices.ProfileService;
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
|
||||||
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
||||||
private static final String TAG = "ProfilePostFetchService";
|
private static final String TAG = "ProfilePostFetchService";
|
||||||
private final ProfileService profileService;
|
private final ProfileService profileService;
|
||||||
private final GraphQLService graphQLService;
|
private final GraphQLRepository graphQLRepository;
|
||||||
private final User profileModel;
|
private final User profileModel;
|
||||||
private final boolean isLoggedIn;
|
private final boolean isLoggedIn;
|
||||||
private String nextMaxId;
|
private String nextMaxId;
|
||||||
@ -23,7 +25,7 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
|||||||
public ProfilePostFetchService(final User profileModel, final boolean isLoggedIn) {
|
public ProfilePostFetchService(final User profileModel, final boolean isLoggedIn) {
|
||||||
this.profileModel = profileModel;
|
this.profileModel = profileModel;
|
||||||
this.isLoggedIn = isLoggedIn;
|
this.isLoggedIn = isLoggedIn;
|
||||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||||
profileService = isLoggedIn ? ProfileService.getInstance() : null;
|
profileService = isLoggedIn ? ProfileService.getInstance() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +51,19 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (isLoggedIn) profileService.fetchPosts(profileModel.getPk(), nextMaxId, cb);
|
if (isLoggedIn) profileService.fetchPosts(profileModel.getPk(), nextMaxId, cb);
|
||||||
else graphQLService.fetchProfilePosts(profileModel.getPk(), 30, nextMaxId, profileModel, cb);
|
else graphQLRepository.fetchProfilePosts(
|
||||||
|
profileModel.getPk(),
|
||||||
|
30,
|
||||||
|
nextMaxId,
|
||||||
|
profileModel,
|
||||||
|
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
cb.onFailure(throwable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb.onSuccess(postsFetchResponse);
|
||||||
|
}, Dispatchers.getIO())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,13 +7,15 @@ import awais.instagrabber.interfaces.FetchListener;
|
|||||||
import awais.instagrabber.models.enums.PostItemType;
|
import awais.instagrabber.models.enums.PostItemType;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
import awais.instagrabber.repositories.responses.PostsFetchResponse;
|
||||||
import awais.instagrabber.webservices.GraphQLService;
|
import awais.instagrabber.utils.CoroutineUtilsKt;
|
||||||
|
import awais.instagrabber.webservices.GraphQLRepository;
|
||||||
import awais.instagrabber.webservices.ProfileService;
|
import awais.instagrabber.webservices.ProfileService;
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
import awais.instagrabber.webservices.ServiceCallback;
|
||||||
|
import kotlinx.coroutines.Dispatchers;
|
||||||
|
|
||||||
public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
||||||
private final ProfileService profileService;
|
private final ProfileService profileService;
|
||||||
private final GraphQLService graphQLService;
|
private final GraphQLRepository graphQLRepository;
|
||||||
private final long profileId;
|
private final long profileId;
|
||||||
private final PostItemType type;
|
private final PostItemType type;
|
||||||
private final boolean isLoggedIn;
|
private final boolean isLoggedIn;
|
||||||
@ -27,7 +29,7 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
this.isLoggedIn = isLoggedIn;
|
this.isLoggedIn = isLoggedIn;
|
||||||
this.collectionId = collectionId;
|
this.collectionId = collectionId;
|
||||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||||
profileService = isLoggedIn ? ProfileService.getInstance() : null;
|
profileService = isLoggedIn ? ProfileService.getInstance() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +60,18 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
|||||||
break;
|
break;
|
||||||
case TAGGED:
|
case TAGGED:
|
||||||
if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback);
|
if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback);
|
||||||
else graphQLService.fetchTaggedPosts(profileId, 30, nextMaxId, callback);
|
else graphQLRepository.fetchTaggedPosts(
|
||||||
|
profileId,
|
||||||
|
30,
|
||||||
|
nextMaxId,
|
||||||
|
CoroutineUtilsKt.getContinuation((postsFetchResponse, throwable) -> {
|
||||||
|
if (throwable != null) {
|
||||||
|
callback.onFailure(throwable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onSuccess(postsFetchResponse);
|
||||||
|
}, Dispatchers.getIO())
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case COLLECTION:
|
case COLLECTION:
|
||||||
case SAVED:
|
case SAVED:
|
||||||
|
@ -105,12 +105,15 @@ public class ChatMessageLayout extends FrameLayout {
|
|||||||
viewPartMainLastLineWidth = viewPartMainLineCount > 0
|
viewPartMainLastLineWidth = viewPartMainLineCount > 0
|
||||||
? ((TextView) firstChild).getLayout().getLineWidth(viewPartMainLineCount - 1)
|
? ((TextView) firstChild).getLayout().getLineWidth(viewPartMainLineCount - 1)
|
||||||
: 0;
|
: 0;
|
||||||
|
// also include start left padding
|
||||||
|
viewPartMainLastLineWidth += firstChild.getPaddingLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewPartMainLineCount > 1 && !(viewPartMainLastLineWidth + viewPartInfoWidth > viewPartMain.getMeasuredWidth())) {
|
final float lastLineWithInfoWidth = viewPartMainLastLineWidth + viewPartInfoWidth;
|
||||||
|
if (viewPartMainLineCount > 1 && lastLineWithInfoWidth <= viewPartMain.getMeasuredWidth()) {
|
||||||
widthSize += viewPartMainWidth;
|
widthSize += viewPartMainWidth;
|
||||||
heightSize += viewPartMainHeight;
|
heightSize += viewPartMainHeight;
|
||||||
} else if (viewPartMainLineCount > 1 && (viewPartMainLastLineWidth + viewPartInfoWidth > availableWidth)) {
|
} else if (viewPartMainLineCount > 1 && (lastLineWithInfoWidth > availableWidth)) {
|
||||||
widthSize += viewPartMainWidth;
|
widthSize += viewPartMainWidth;
|
||||||
heightSize += viewPartMainHeight + viewPartInfoHeight;
|
heightSize += viewPartMainHeight + viewPartInfoHeight;
|
||||||
} else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartInfoWidth > availableWidth)) {
|
} else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartInfoWidth > availableWidth)) {
|
||||||
@ -120,6 +123,16 @@ public class ChatMessageLayout extends FrameLayout {
|
|||||||
heightSize += viewPartMainHeight;
|
heightSize += viewPartMainHeight;
|
||||||
widthSize += viewPartMainWidth + viewPartInfoWidth;
|
widthSize += viewPartMainWidth + viewPartInfoWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (isInEditMode()) {
|
||||||
|
// TextView wDebugView = (TextView) ((ViewGroup) this.getParent()).findViewWithTag("debug");
|
||||||
|
// wDebugView.setText(lastLineWithInfoWidth
|
||||||
|
// + "\n" + availableWidth
|
||||||
|
// + "\n" + viewPartMain.getMeasuredWidth()
|
||||||
|
// + "\n" + (lastLineWithInfoWidth <= viewPartMain.getMeasuredWidth())
|
||||||
|
// + "\n" + (lastLineWithInfoWidth > availableWidth)
|
||||||
|
// + "\n" + (viewPartMainWidth + viewPartInfoWidth > availableWidth));
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
setMeasuredDimension(widthSize, heightSize);
|
setMeasuredDimension(widthSize, heightSize);
|
||||||
super.onMeasure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
super.onMeasure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));
|
||||||
|
@ -80,7 +80,7 @@ public class DirectItemContextMenu extends PopupWindow {
|
|||||||
if (!showReactions && (options == null || options.isEmpty())) {
|
if (!showReactions && (options == null || options.isEmpty())) {
|
||||||
throw new IllegalArgumentException("showReactions is set false and options are empty");
|
throw new IllegalArgumentException("showReactions is set false and options are empty");
|
||||||
}
|
}
|
||||||
reactionsManager = ReactionsManager.getInstance();
|
reactionsManager = ReactionsManager.getInstance(context);
|
||||||
final Resources resources = context.getResources();
|
final Resources resources = context.getResources();
|
||||||
emojiSize = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_size);
|
emojiSize = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_size);
|
||||||
emojiMargin = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_margin);
|
emojiMargin = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_margin);
|
||||||
|
@ -0,0 +1,165 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
import androidx.transition.ChangeBounds;
|
||||||
|
import androidx.transition.Transition;
|
||||||
|
import androidx.transition.TransitionManager;
|
||||||
|
import androidx.transition.TransitionSet;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import awais.instagrabber.customviews.helpers.ChangeText;
|
||||||
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
|
|
||||||
|
public class FormattedNumberTextView extends AppCompatTextView {
|
||||||
|
private static final String TAG = FormattedNumberTextView.class.getSimpleName();
|
||||||
|
private static final Transition TRANSITION;
|
||||||
|
|
||||||
|
private long number = Long.MIN_VALUE;
|
||||||
|
private boolean showAbbreviation = true;
|
||||||
|
private boolean animateChanges = false;
|
||||||
|
private boolean toggleOnClick = true;
|
||||||
|
private boolean autoToggleToAbbreviation = true;
|
||||||
|
private long autoToggleTimeoutMs = Duration.ofSeconds(2).toMillis();
|
||||||
|
private boolean initDone = false;
|
||||||
|
|
||||||
|
static {
|
||||||
|
final TransitionSet transitionSet = new TransitionSet();
|
||||||
|
final ChangeText changeText = new ChangeText().setChangeBehavior(ChangeText.CHANGE_BEHAVIOR_OUT_IN);
|
||||||
|
transitionSet.addTransition(changeText).addTransition(new ChangeBounds());
|
||||||
|
TRANSITION = transitionSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public FormattedNumberTextView(@NonNull final Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattedNumberTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
if (initDone) return;
|
||||||
|
setupClickToggle();
|
||||||
|
initDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupClickToggle() {
|
||||||
|
setOnClickListener(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OnClickListener getWrappedClickListener(@Nullable final OnClickListener l) {
|
||||||
|
if (!toggleOnClick) {
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
return v -> {
|
||||||
|
toggleAbbreviation();
|
||||||
|
if (l != null) {
|
||||||
|
l.onClick(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumber(final long number) {
|
||||||
|
if (this.number == number) return;
|
||||||
|
this.number = number;
|
||||||
|
format();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearNumber() {
|
||||||
|
if (number == Long.MIN_VALUE) return;
|
||||||
|
number = Long.MIN_VALUE;
|
||||||
|
format();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowAbbreviation(final boolean showAbbreviation) {
|
||||||
|
if (this.showAbbreviation && showAbbreviation) return;
|
||||||
|
this.showAbbreviation = showAbbreviation;
|
||||||
|
format();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isShowAbbreviation() {
|
||||||
|
return showAbbreviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleAbbreviation() {
|
||||||
|
if (number == Long.MIN_VALUE) return;
|
||||||
|
setShowAbbreviation(!showAbbreviation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToggleOnClick(final boolean toggleOnClick) {
|
||||||
|
this.toggleOnClick = toggleOnClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isToggleOnClick() {
|
||||||
|
return toggleOnClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoToggleToAbbreviation(final boolean autoToggleToAbbreviation) {
|
||||||
|
this.autoToggleToAbbreviation = autoToggleToAbbreviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAutoToggleToAbbreviation() {
|
||||||
|
return autoToggleToAbbreviation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoToggleTimeoutMs(final long autoToggleTimeoutMs) {
|
||||||
|
this.autoToggleTimeoutMs = autoToggleTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAutoToggleTimeoutMs() {
|
||||||
|
return autoToggleTimeoutMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAnimateChanges(final boolean animateChanges) {
|
||||||
|
this.animateChanges = animateChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAnimateChanges() {
|
||||||
|
return animateChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOnClickListener(@Nullable final OnClickListener l) {
|
||||||
|
super.setOnClickListener(getWrappedClickListener(l));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void format() {
|
||||||
|
post(() -> {
|
||||||
|
if (animateChanges) {
|
||||||
|
try {
|
||||||
|
TransitionManager.beginDelayedTransition((ViewGroup) getParent(), TRANSITION);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "format: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (number == Long.MIN_VALUE) {
|
||||||
|
setText(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (showAbbreviation) {
|
||||||
|
setText(NumberUtils.abbreviate(number, null));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setText(String.valueOf(number));
|
||||||
|
if (autoToggleToAbbreviation) {
|
||||||
|
getHandler().postDelayed(() -> setShowAbbreviation(true), autoToggleTimeoutMs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.navigation.NavDestination;
|
||||||
|
import androidx.navigation.NavOptions;
|
||||||
|
import androidx.navigation.Navigator;
|
||||||
|
import androidx.navigation.fragment.FragmentNavigator;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
|
||||||
|
@Navigator.Name("fragment")
|
||||||
|
public class FragmentNavigatorWithDefaultAnimations extends FragmentNavigator {
|
||||||
|
|
||||||
|
private final NavOptions emptyNavOptions = new NavOptions.Builder().build();
|
||||||
|
// private final NavOptions defaultNavOptions = new NavOptions.Builder()
|
||||||
|
// .setEnterAnim(R.animator.nav_default_enter_anim)
|
||||||
|
// .setExitAnim(R.animator.nav_default_exit_anim)
|
||||||
|
// .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
|
||||||
|
// .setPopExitAnim(R.animator.nav_default_pop_exit_anim)
|
||||||
|
// .build();
|
||||||
|
|
||||||
|
private final NavOptions defaultNavOptions = new NavOptions.Builder()
|
||||||
|
.setEnterAnim(R.anim.slide_in_right)
|
||||||
|
.setExitAnim(R.anim.slide_out_left)
|
||||||
|
.setPopEnterAnim(android.R.anim.slide_in_left)
|
||||||
|
.setPopExitAnim(android.R.anim.slide_out_right)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public FragmentNavigatorWithDefaultAnimations(@NonNull final Context context,
|
||||||
|
@NonNull final FragmentManager manager,
|
||||||
|
final int containerId) {
|
||||||
|
super(context, manager, containerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public NavDestination navigate(@NonNull final Destination destination,
|
||||||
|
@Nullable final Bundle args,
|
||||||
|
@Nullable final NavOptions navOptions,
|
||||||
|
@Nullable final Navigator.Extras navigatorExtras) {
|
||||||
|
// this will try to fill in empty animations with defaults when no shared element transitions are set
|
||||||
|
// https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element
|
||||||
|
final boolean shouldUseTransitionsInstead = navigatorExtras != null;
|
||||||
|
final NavOptions navOptions1 = shouldUseTransitionsInstead ? navOptions : fillEmptyAnimationsWithDefaults(navOptions);
|
||||||
|
return super.navigate(destination, args, navOptions1, navigatorExtras);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NavOptions fillEmptyAnimationsWithDefaults(@Nullable final NavOptions navOptions) {
|
||||||
|
if (navOptions == null) {
|
||||||
|
return defaultNavOptions;
|
||||||
|
}
|
||||||
|
return copyNavOptionsWithDefaultAnimations(navOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private NavOptions copyNavOptionsWithDefaultAnimations(@NonNull final NavOptions navOptions) {
|
||||||
|
return new NavOptions.Builder()
|
||||||
|
.setLaunchSingleTop(navOptions.shouldLaunchSingleTop())
|
||||||
|
.setPopUpTo(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive())
|
||||||
|
.setEnterAnim(navOptions.getEnterAnim() == emptyNavOptions.getEnterAnim()
|
||||||
|
? defaultNavOptions.getEnterAnim() : navOptions.getEnterAnim())
|
||||||
|
.setExitAnim(navOptions.getExitAnim() == emptyNavOptions.getExitAnim()
|
||||||
|
? defaultNavOptions.getExitAnim() : navOptions.getExitAnim())
|
||||||
|
.setPopEnterAnim(navOptions.getPopEnterAnim() == emptyNavOptions.getPopEnterAnim()
|
||||||
|
? defaultNavOptions.getPopEnterAnim() : navOptions.getPopEnterAnim())
|
||||||
|
.setPopExitAnim(navOptions.getPopExitAnim() == emptyNavOptions.getPopExitAnim()
|
||||||
|
? defaultNavOptions.getPopExitAnim() : navOptions.getPopExitAnim())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,246 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowInsetsAnimation;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.view.NestedScrollingParent3;
|
||||||
|
import androidx.core.view.NestedScrollingParentHelper;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import awais.instagrabber.customviews.helpers.SimpleImeAnimationController;
|
||||||
|
import awais.instagrabber.utils.ViewUtils;
|
||||||
|
|
||||||
|
import static androidx.core.view.ViewCompat.TYPE_TOUCH;
|
||||||
|
|
||||||
|
public final class InsetsAnimationLinearLayout extends LinearLayout implements NestedScrollingParent3 {
|
||||||
|
private final NestedScrollingParentHelper nestedScrollingParentHelper = new NestedScrollingParentHelper(this);
|
||||||
|
private final SimpleImeAnimationController imeAnimController = new SimpleImeAnimationController();
|
||||||
|
private final int[] tempIntArray2 = new int[2];
|
||||||
|
private final int[] startViewLocation = new int[2];
|
||||||
|
|
||||||
|
private View currentNestedScrollingChild;
|
||||||
|
private int dropNextY;
|
||||||
|
private boolean scrollImeOffScreenWhenVisible = true;
|
||||||
|
private boolean scrollImeOnScreenWhenNotVisible = true;
|
||||||
|
private boolean scrollImeOffScreenWhenVisibleOnFling = false;
|
||||||
|
private boolean scrollImeOnScreenWhenNotVisibleOnFling = false;
|
||||||
|
|
||||||
|
public InsetsAnimationLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsAnimationLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean getScrollImeOffScreenWhenVisible() {
|
||||||
|
return scrollImeOffScreenWhenVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setScrollImeOffScreenWhenVisible(boolean scrollImeOffScreenWhenVisible) {
|
||||||
|
this.scrollImeOffScreenWhenVisible = scrollImeOffScreenWhenVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean getScrollImeOnScreenWhenNotVisible() {
|
||||||
|
return scrollImeOnScreenWhenNotVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setScrollImeOnScreenWhenNotVisible(boolean scrollImeOnScreenWhenNotVisible) {
|
||||||
|
this.scrollImeOnScreenWhenNotVisible = scrollImeOnScreenWhenNotVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getScrollImeOffScreenWhenVisibleOnFling() {
|
||||||
|
return scrollImeOffScreenWhenVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollImeOffScreenWhenVisibleOnFling(final boolean scrollImeOffScreenWhenVisibleOnFling) {
|
||||||
|
this.scrollImeOffScreenWhenVisibleOnFling = scrollImeOffScreenWhenVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getScrollImeOnScreenWhenNotVisibleOnFling() {
|
||||||
|
return scrollImeOnScreenWhenNotVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollImeOnScreenWhenNotVisibleOnFling(final boolean scrollImeOnScreenWhenNotVisibleOnFling) {
|
||||||
|
this.scrollImeOnScreenWhenNotVisibleOnFling = scrollImeOnScreenWhenNotVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleImeAnimationController getImeAnimController() {
|
||||||
|
return imeAnimController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onStartNestedScroll(@NonNull final View child,
|
||||||
|
@NonNull final View target,
|
||||||
|
final int axes,
|
||||||
|
final int type) {
|
||||||
|
return (axes & SCROLL_AXIS_VERTICAL) != 0 && type == TYPE_TOUCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScrollAccepted(@NonNull final View child,
|
||||||
|
@NonNull final View target,
|
||||||
|
final int axes,
|
||||||
|
final int type) {
|
||||||
|
nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
|
||||||
|
currentNestedScrollingChild = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedPreScroll(@NonNull final View target,
|
||||||
|
final int dx,
|
||||||
|
final int dy,
|
||||||
|
@NonNull final int[] consumed,
|
||||||
|
final int type) {
|
||||||
|
if (imeAnimController.isInsetAnimationRequestPending()) {
|
||||||
|
consumed[0] = dx;
|
||||||
|
consumed[1] = dy;
|
||||||
|
} else {
|
||||||
|
int deltaY = dy;
|
||||||
|
if (dropNextY != 0) {
|
||||||
|
consumed[1] = dropNextY;
|
||||||
|
deltaY = dy - dropNextY;
|
||||||
|
dropNextY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaY < 0) {
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress()) {
|
||||||
|
consumed[1] -= imeAnimController.insetBy(-deltaY);
|
||||||
|
} else if (scrollImeOffScreenWhenVisible && !imeAnimController.isInsetAnimationRequestPending()) {
|
||||||
|
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
|
||||||
|
if (rootWindowInsets != null) {
|
||||||
|
if (rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
|
||||||
|
startControlRequest();
|
||||||
|
consumed[1] = deltaY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScroll(@NonNull final View target,
|
||||||
|
final int dxConsumed,
|
||||||
|
final int dyConsumed,
|
||||||
|
final int dxUnconsumed,
|
||||||
|
final int dyUnconsumed,
|
||||||
|
final int type,
|
||||||
|
@NonNull final int[] consumed) {
|
||||||
|
if (dyUnconsumed > 0) {
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress()) {
|
||||||
|
consumed[1] = -imeAnimController.insetBy(-dyUnconsumed);
|
||||||
|
} else if (scrollImeOnScreenWhenNotVisible && !imeAnimController.isInsetAnimationRequestPending()) {
|
||||||
|
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
|
||||||
|
if (rootWindowInsets != null) {
|
||||||
|
if (!rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
|
||||||
|
startControlRequest();
|
||||||
|
consumed[1] = dyUnconsumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNestedFling(@NonNull final View target,
|
||||||
|
final float velocityX,
|
||||||
|
final float velocityY,
|
||||||
|
final boolean consumed) {
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress()) {
|
||||||
|
imeAnimController.animateToFinish(velocityY);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
boolean imeVisible = false;
|
||||||
|
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
|
||||||
|
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
|
||||||
|
imeVisible = true;
|
||||||
|
}
|
||||||
|
if (velocityY > 0 && scrollImeOnScreenWhenNotVisibleOnFling && !imeVisible) {
|
||||||
|
imeAnimController.startAndFling(this, velocityY);
|
||||||
|
return true;
|
||||||
|
} else if (velocityY < 0 && scrollImeOffScreenWhenVisibleOnFling && imeVisible) {
|
||||||
|
imeAnimController.startAndFling(this, velocityY);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopNestedScroll(@NonNull final View target, final int type) {
|
||||||
|
nestedScrollingParentHelper.onStopNestedScroll(target, type);
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress() && !imeAnimController.isInsetAnimationFinishing()) {
|
||||||
|
imeAnimController.animateToFinish(null);
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchWindowInsetsAnimationPrepare(@NonNull final WindowInsetsAnimation animation) {
|
||||||
|
super.dispatchWindowInsetsAnimationPrepare(animation);
|
||||||
|
ViewUtils.suppressLayoutCompat(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startControlRequest() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ViewUtils.suppressLayoutCompat(this, true);
|
||||||
|
if (currentNestedScrollingChild != null) {
|
||||||
|
currentNestedScrollingChild.getLocationInWindow(startViewLocation);
|
||||||
|
}
|
||||||
|
imeAnimController.startControlRequest(this, windowInsetsAnimationControllerCompat -> onControllerReady());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onControllerReady() {
|
||||||
|
if (currentNestedScrollingChild != null) {
|
||||||
|
imeAnimController.insetBy(0);
|
||||||
|
int[] location = tempIntArray2;
|
||||||
|
currentNestedScrollingChild.getLocationInWindow(location);
|
||||||
|
dropNextY = location[1] - startViewLocation[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
dropNextY = 0;
|
||||||
|
Arrays.fill(startViewLocation, 0);
|
||||||
|
ViewUtils.suppressLayoutCompat(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScrollAccepted(@NonNull final View child,
|
||||||
|
@NonNull final View target,
|
||||||
|
final int axes) {
|
||||||
|
onNestedScrollAccepted(child, target, axes, TYPE_TOUCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScroll(@NonNull final View target,
|
||||||
|
final int dxConsumed,
|
||||||
|
final int dyConsumed,
|
||||||
|
final int dxUnconsumed,
|
||||||
|
final int dyUnconsumed,
|
||||||
|
final int type) {
|
||||||
|
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, tempIntArray2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopNestedScroll(@NonNull final View target) {
|
||||||
|
onStopNestedScroll(target, TYPE_TOUCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
|
||||||
|
public class InsetsNotifyingCoordinatorLayout extends CoordinatorLayout {
|
||||||
|
|
||||||
|
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||||
|
int childCount = getChildCount();
|
||||||
|
for (int index = 0; index < childCount; index++) {
|
||||||
|
getChildAt(index).dispatchApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class InsetsNotifyingLinearLayout extends LinearLayout {
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||||
|
int childCount = getChildCount();
|
||||||
|
for (int index = 0; index < childCount; index++) {
|
||||||
|
getChildAt(index).dispatchApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NavigationRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.navigation.NavController;
|
||||||
|
import androidx.navigation.Navigator;
|
||||||
|
import androidx.navigation.fragment.FragmentNavigator;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
|
public class NavHostFragmentWithDefaultAnimations extends NavHostFragment {
|
||||||
|
private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId";
|
||||||
|
private static final String KEY_START_DESTINATION_ARGS =
|
||||||
|
"android-support-nav:fragment:startDestinationArgs";
|
||||||
|
private static final String KEY_NAV_CONTROLLER_STATE =
|
||||||
|
"android-support-nav:fragment:navControllerState";
|
||||||
|
private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static NavHostFragment create(@NavigationRes int graphResId) {
|
||||||
|
return create(graphResId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static NavHostFragment create(@NavigationRes int graphResId,
|
||||||
|
@Nullable Bundle startDestinationArgs) {
|
||||||
|
Bundle b = null;
|
||||||
|
if (graphResId != 0) {
|
||||||
|
b = new Bundle();
|
||||||
|
b.putInt(KEY_GRAPH_ID, graphResId);
|
||||||
|
}
|
||||||
|
if (startDestinationArgs != null) {
|
||||||
|
if (b == null) {
|
||||||
|
b = new Bundle();
|
||||||
|
}
|
||||||
|
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
final NavHostFragmentWithDefaultAnimations result = new NavHostFragmentWithDefaultAnimations();
|
||||||
|
if (b != null) {
|
||||||
|
result.setArguments(b);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
|
||||||
|
return new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreateNavController(@NonNull final NavController navController) {
|
||||||
|
super.onCreateNavController(navController);
|
||||||
|
navController.getNavigatorProvider()
|
||||||
|
.addNavigator(new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId()));
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ package awais.instagrabber.customviews;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -25,15 +27,16 @@ import java.util.ArrayList;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
|
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
|
||||||
import awais.instagrabber.customviews.helpers.PostFetcher;
|
import awais.instagrabber.customviews.helpers.PostFetcher;
|
||||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
||||||
|
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
import awais.instagrabber.interfaces.FetchListener;
|
||||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.KeywordsFilterUtils;
|
import awais.instagrabber.utils.KeywordsFilterUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
@ -60,14 +63,17 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
private FeedAdapterV2.FeedItemCallback feedItemCallback;
|
private FeedAdapterV2.FeedItemCallback feedItemCallback;
|
||||||
private boolean shouldScrollToTop;
|
private boolean shouldScrollToTop;
|
||||||
private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
|
private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
|
||||||
|
private Function<ViewGroup, View> headerViewCreator;
|
||||||
|
private Function<View, Void> headerBinder;
|
||||||
|
private boolean refresh = true;
|
||||||
|
|
||||||
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
|
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
|
||||||
|
|
||||||
private final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
|
private final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResult(final List<Media> result) {
|
public void onResult(final List<Media> result) {
|
||||||
final int currentPage = lazyLoader.getCurrentPage();
|
if (refresh) {
|
||||||
if (currentPage == 0) {
|
refresh = false;
|
||||||
mediaViewModel.getList().postValue(result);
|
mediaViewModel.getList().postValue(result);
|
||||||
shouldScrollToTop = true;
|
shouldScrollToTop = true;
|
||||||
dispatchFetchStatus();
|
dispatchFetchStatus();
|
||||||
@ -75,8 +81,8 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
}
|
}
|
||||||
final List<Media> models = mediaViewModel.getList().getValue();
|
final List<Media> models = mediaViewModel.getList().getValue();
|
||||||
final List<Media> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
|
final List<Media> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
|
||||||
if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)) {
|
if (settingsHelper.getBoolean(PreferenceKeys.TOGGLE_KEYWORD_FILTER)) {
|
||||||
final ArrayList<String> items = new ArrayList<>(settingsHelper.getStringSet(Constants.KEYWORD_FILTERS));
|
final ArrayList<String> items = new ArrayList<>(settingsHelper.getStringSet(PreferenceKeys.KEYWORD_FILTERS));
|
||||||
modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result));
|
modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result));
|
||||||
} else {
|
} else {
|
||||||
modelsCopy.addAll(result);
|
modelsCopy.addAll(result);
|
||||||
@ -192,22 +198,25 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initSelf() {
|
private void initSelf() {
|
||||||
|
try {
|
||||||
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
|
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
|
||||||
mediaViewModel.getList().observe(lifeCycleOwner, list -> {
|
} catch (Exception e) {
|
||||||
if (list.size() <= 0) return;
|
Log.e(TAG, "initSelf: ", e);
|
||||||
feedAdapter.submitList(list, () -> {
|
}
|
||||||
|
if (mediaViewModel == null) return;
|
||||||
|
mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
|
||||||
// postDelayed(this::fetchMoreIfPossible, 1000);
|
// postDelayed(this::fetchMoreIfPossible, 1000);
|
||||||
if (!shouldScrollToTop) return;
|
if (!shouldScrollToTop) return;
|
||||||
smoothScrollToPosition(0);
|
|
||||||
shouldScrollToTop = false;
|
shouldScrollToTop = false;
|
||||||
});
|
post(() -> smoothScrollToPosition(0));
|
||||||
});
|
}));
|
||||||
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
||||||
if (layoutPreferences.getHasGap()) {
|
if (layoutPreferences.getHasGap()) {
|
||||||
addItemDecoration(gridSpacingItemDecoration);
|
addItemDecoration(gridSpacingItemDecoration);
|
||||||
}
|
}
|
||||||
setHasFixedSize(true);
|
setHasFixedSize(true);
|
||||||
setNestedScrollingEnabled(true);
|
setNestedScrollingEnabled(true);
|
||||||
|
setItemAnimator(null);
|
||||||
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
|
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
|
||||||
if (postFetcher.hasMore()) {
|
if (postFetcher.hasMore()) {
|
||||||
postFetcher.fetch();
|
postFetcher.fetch();
|
||||||
@ -311,11 +320,12 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
|
refresh = true;
|
||||||
if (lazyLoader != null) {
|
if (lazyLoader != null) {
|
||||||
lazyLoader.resetState();
|
lazyLoader.resetState();
|
||||||
}
|
}
|
||||||
if (postFetcher != null) {
|
if (postFetcher != null) {
|
||||||
mediaViewModel.getList().postValue(Collections.emptyList());
|
// mediaViewModel.getList().postValue(Collections.emptyList());
|
||||||
postFetcher.reset();
|
postFetcher.reset();
|
||||||
postFetcher.fetch();
|
postFetcher.fetch();
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,9 @@ public final class ProfilePicView extends CircularImageView {
|
|||||||
case SMALL:
|
case SMALL:
|
||||||
dimenRes = R.dimen.profile_pic_size_small;
|
dimenRes = R.dimen.profile_pic_size_small;
|
||||||
break;
|
break;
|
||||||
|
case SMALLER:
|
||||||
|
dimenRes = R.dimen.profile_pic_size_smaller;
|
||||||
|
break;
|
||||||
case TINY:
|
case TINY:
|
||||||
dimenRes = R.dimen.profile_pic_size_tiny;
|
dimenRes = R.dimen.profile_pic_size_tiny;
|
||||||
break;
|
break;
|
||||||
@ -113,7 +116,8 @@ public final class ProfilePicView extends CircularImageView {
|
|||||||
TINY(0),
|
TINY(0),
|
||||||
SMALL(1),
|
SMALL(1),
|
||||||
REGULAR(2),
|
REGULAR(2),
|
||||||
LARGE(3);
|
LARGE(3),
|
||||||
|
SMALLER(4);
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
private static final Map<Integer, Size> map = new HashMap<>();
|
private static final Map<Integer, Size> map = new HashMap<>();
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user