mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 06:37:30 +00:00
Merge branch 'master' into pr/934
This commit is contained in:
commit
f961c422ca
@ -79,6 +79,25 @@
|
||||
"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",
|
||||
"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.
|
||||
- [ ] 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.
|
||||
- [ ] 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.
|
||||
|
||||
|
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -6,9 +6,9 @@ labels: bug
|
||||
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).
|
||||
|
||||
<!--
|
||||
@ -30,4 +30,4 @@ Describe the bug here. Don't stress too much, but do include the key points.
|
||||
|
||||
## 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!!!
|
17
.github/workflows/github_nightly_release.yml
vendored
17
.github/workflows/github_nightly_release.yml
vendored
@ -15,18 +15,19 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
distribution: 'zulu'
|
||||
java-version: '8'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
- 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
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
uses: ammargitham/sign-android-release@v1.1.1
|
||||
# ID used to access action output
|
||||
id: sign_app
|
||||
with:
|
||||
@ -45,7 +46,8 @@ jobs:
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
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
|
||||
- name: Send success Telegram notification
|
||||
@ -55,7 +57,8 @@ jobs:
|
||||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
||||
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}}"
|
||||
document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
# document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
document: app/build/outputs/apk/github/release/*-signed.apk
|
||||
|
||||
# Send failure notification
|
||||
- name: Send failure Telegram notification
|
||||
|
33
.github/workflows/github_pre_release.yml
vendored
33
.github/workflows/github_pre_release.yml
vendored
@ -14,20 +14,21 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
distribution: 'zulu'
|
||||
java-version: '8'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
|
||||
|
||||
- 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
|
||||
uses: r0adkll/sign-android-release@v1
|
||||
uses: ammargitham/sign-android-release@v1.1.1
|
||||
# ID used to access action output
|
||||
id: sign_app
|
||||
with:
|
||||
@ -36,18 +37,19 @@ jobs:
|
||||
alias: ${{ secrets.ALIAS }}
|
||||
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
|
||||
|
||||
- name: Get current date and time
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y%m%d_%H%M%S')"
|
||||
|
||||
# Create artifact
|
||||
|
||||
# Create artifact
|
||||
- name: Create apk artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
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
|
||||
- name: Send success Telegram notification
|
||||
if: ${{ success() }}
|
||||
@ -56,8 +58,9 @@ jobs:
|
||||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
||||
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}}"
|
||||
document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
|
||||
# document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||
document: app/build/outputs/apk/github/release/*-signed.apk
|
||||
|
||||
# Send failure notification
|
||||
- name: Send failure Telegram notification
|
||||
if: ${{ failure() }}
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -20,3 +20,5 @@ app/release
|
||||
/sentry.properties
|
||||
/app/fdroid/
|
||||
/app/github/
|
||||
/repo
|
||||
/.fdroid.yml
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
<bytecodeTargetLevel target="11" />
|
||||
</component>
|
||||
</project>
|
@ -7,7 +7,6 @@
|
||||
<option name="testRunner" value="PLATFORM" />
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="1.8" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
@ -40,7 +40,7 @@
|
||||
</value>
|
||||
</option>
|
||||
</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" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
@ -3,6 +3,7 @@
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<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.TestClassGradleConfigurationProducer" />
|
||||
<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)
|
||||
[![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)
|
||||
[![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 -->
|
||||
|
||||
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
|
||||
|
||||
<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/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/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/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/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/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/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.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.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.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.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.png"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/6.png" alt="Discover Topics" width="15%"/></a>
|
||||
|
||||
## We need maintainers!
|
||||
|
||||
@ -63,49 +63,53 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
|
||||
</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/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://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="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>
|
||||
<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/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/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>
|
||||
<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/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/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>
|
||||
<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://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/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>
|
||||
<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/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/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>
|
||||
<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://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/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/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>
|
||||
@ -121,7 +125,7 @@ This app's predecessor, InstaGrabber, was originally made by [@AwaisKing](https:
|
||||
|
||||
Barinsta
|
||||
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
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
128
app/build.gradle
128
app/build.gradle
@ -1,5 +1,7 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: "androidx.navigation.safeargs"
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply from: 'sentry.gradle'
|
||||
|
||||
def getGitHash = { ->
|
||||
@ -20,8 +22,8 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
|
||||
versionCode 61
|
||||
versionName '19.2.0'
|
||||
versionCode 63
|
||||
versionName '19.2.2'
|
||||
|
||||
multiDexEnabled true
|
||||
|
||||
@ -33,6 +35,12 @@ android {
|
||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||
}
|
||||
}
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||
}
|
||||
|
||||
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 ->
|
||||
if (variant.flavorName != "github") return
|
||||
variant.outputs.all { output ->
|
||||
@ -84,17 +113,47 @@ android {
|
||||
// def versionCode = variant.versionCode
|
||||
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")) {
|
||||
buildConfigField("boolean", "isPre", "true")
|
||||
// append latest commit short hash for pre-release
|
||||
suffix = "${versionName}.${getGitHash()}-${flavor}" // eg. 19.1.0.b123456-github
|
||||
|
||||
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
|
||||
suffix = "${versionName}.${flavorBuiltType}" // eg. 19.1.0.b123456-github
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -104,49 +163,61 @@ configurations.all {
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
def appcompat_version = "1.2.0"
|
||||
def nav_version = '2.3.4'
|
||||
def exoplayer_version = '2.13.2'
|
||||
def nav_version = '2.3.5'
|
||||
def exoplayer_version = '2.13.3'
|
||||
|
||||
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-dash:$exoplayer_version"
|
||||
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
|
||||
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation "androidx.viewpager2:viewpager2:1.0.0"
|
||||
implementation "androidx.navigation:navigation-fragment:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui:$nav_version"
|
||||
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
|
||||
implementation "androidx.preference:preference:1.1.1"
|
||||
implementation "androidx.work:work-runtime:2.5.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'
|
||||
|
||||
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
|
||||
def room_version = "2.2.6"
|
||||
def room_version = "2.3.0"
|
||||
implementation "androidx.room:room-runtime:$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"
|
||||
|
||||
// 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-lifecycle:$camerax_version"
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha22"
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha24"
|
||||
|
||||
// EmojiCompat
|
||||
def emoji_compat_version = "1.1.0"
|
||||
implementation "androidx.emoji:emoji:$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:animated-webp:2.3.0'
|
||||
@ -157,12 +228,27 @@ dependencies {
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
|
||||
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'
|
||||
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
|
||||
|
||||
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"?>
|
||||
<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>
|
||||
<string name="enable_sentry">Povolit Sentry</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 se spustí při příštím spuštění</string>
|
||||
</resources>
|
||||
|
@ -1,6 +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>
|
||||
<string name="enable_sentry">Aktiviere Sentry</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 startet beim nächsten Start</string>
|
||||
</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"?>
|
||||
<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>
|
||||
<string name="enable_sentry">Sentry inschakelen</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 zal starten bij de volgende lancering</string>
|
||||
</resources>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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>
|
||||
</resources>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="enable_sentry">Включить режим \"часового\"</string>
|
||||
<string name="sentry_summary">\"Часовой\" - это слушатель/обработчик ошибок, который асинхронно отправляет ошибку/событие на Sentry.io</string>
|
||||
<string name="sentry_start_next_launch">\"Часовой\" будет запущен при следующем запуске</string>
|
||||
<string name="enable_sentry">Включить Sentry</string>
|
||||
<string name="sentry_summary">Sentry - это обработчик событий, который асинхронно отправляет сообщения об ошибках/поломках на Sentry.io</string>
|
||||
<string name="sentry_start_next_launch">Sentry включится при следующем запуске приложения</string>
|
||||
</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"?>
|
||||
<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>
|
||||
<string name="enable_sentry">Bật Sentry</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 sẽ được bật vào lần khởi động kế tiếp</string>
|
||||
</resources>
|
||||
|
@ -1,6 +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>
|
||||
<string name="enable_sentry">啟用 Sentry</string>
|
||||
<string name="sentry_summary">Sentry 將會錯誤報告發送至 Sentry.io</string>
|
||||
<string name="sentry_start_next_launch">下次啟用應用程式時將會開啟 Sentry</string>
|
||||
</resources>
|
||||
|
@ -26,8 +26,7 @@
|
||||
<activity
|
||||
android:name=".activities.MainActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=".Main"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
android:taskAffinity=".Main">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
@ -51,13 +50,14 @@
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
<data android:host="ig.me" />
|
||||
<data android:host="www.ig.me" />
|
||||
<data android:host="instagr.am" />
|
||||
<data android:host="www.instagr.am" />
|
||||
<data android:host="instagram.com" />
|
||||
<data android:host="www.instagram.com" />
|
||||
<data android:pathPrefix="/" />
|
||||
<data android:pathPrefix="/p" />
|
||||
<data android:pathPrefix="/explore/tags" />
|
||||
<data android:pathPrefix="/explore/locations" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
@ -76,41 +76,6 @@
|
||||
android:screenOrientation="portrait"
|
||||
android:taskAffinity="awais.instagrabber.errorreport"
|
||||
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
|
||||
android:name=".activities.Login"
|
||||
android:label="@string/login"
|
||||
@ -173,4 +138,4 @@
|
||||
android:value="Noto Color Emoji Compat" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
@ -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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import awais.instagrabber.adapters.viewholder.comments.ChildCommentViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.comments.ParentCommentViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.CommentViewHolder;
|
||||
import awais.instagrabber.databinding.ItemCommentBinding;
|
||||
import awais.instagrabber.databinding.ItemCommentSmallBinding;
|
||||
import awais.instagrabber.models.CommentModel;
|
||||
import awais.instagrabber.models.Comment;
|
||||
|
||||
public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerView.ViewHolder> {
|
||||
private static final int TYPE_PARENT = 1;
|
||||
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>() {
|
||||
public final class CommentsAdapter extends ListAdapter<Comment, CommentViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
|
||||
return oldItem.getId().equals(newItem.getId());
|
||||
public boolean areItemsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
|
||||
return Objects.equals(oldItem.getPk(), newItem.getPk());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
|
||||
return oldItem.getId().equals(newItem.getId());
|
||||
public boolean areContentsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
|
||||
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);
|
||||
this.showingReplies = showingReplies;
|
||||
this.currentUserId = currentUserId;
|
||||
this.commentCallback = commentCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
||||
final Context context = parent.getContext();
|
||||
final LayoutInflater layoutInflater = LayoutInflater.from(context);
|
||||
if (type == TYPE_PARENT) {
|
||||
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
|
||||
return new ParentCommentViewHolder(binding);
|
||||
}
|
||||
final ItemCommentSmallBinding binding = ItemCommentSmallBinding.inflate(layoutInflater, parent, false);
|
||||
return new ChildCommentViewHolder(binding);
|
||||
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
||||
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
|
||||
return new CommentViewHolder(binding, currentUserId, commentCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
||||
CommentModel commentModel = getItem(position);
|
||||
if (commentModel == null) return;
|
||||
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 void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) {
|
||||
final Comment comment = getItem(position);
|
||||
holder.bind(comment, showingReplies && position == 0, showingReplies && position != 0);
|
||||
}
|
||||
|
||||
public interface CommentCallback {
|
||||
void onClick(final CommentModel comment);
|
||||
void onClick(final Comment comment);
|
||||
|
||||
void onHashtagClick(final String hashtag);
|
||||
|
||||
@ -198,5 +63,15 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
|
||||
void onURLClick(final String url);
|
||||
|
||||
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;
|
||||
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
@ -13,8 +12,10 @@ import androidx.recyclerview.widget.AsyncListDiffer;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
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.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
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.DirectItemStoryShare;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.DateUtils;
|
||||
|
||||
public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
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));
|
||||
}
|
||||
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);
|
||||
itemViewHolder.setLongClickListener(longClickListener);
|
||||
return itemViewHolder;
|
||||
@ -292,12 +292,15 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
|
||||
private List<DirectItemOrHeader> sectionAndSort(final List<DirectItem> list) {
|
||||
final List<DirectItemOrHeader> itemOrHeaders = new ArrayList<>();
|
||||
Date prevSectionDate = null;
|
||||
LocalDate prevSectionDate = null;
|
||||
for (int i = 0; i < list.size(); 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);
|
||||
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
|
||||
final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
|
||||
itemOrHeader.item = item;
|
||||
@ -320,7 +323,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
final DirectItemOrHeader itemOrHeader = new DirectItemOrHeader();
|
||||
itemOrHeader.item = item;
|
||||
itemOrHeaders.add(itemOrHeader);
|
||||
prevSectionDate = DateUtils.dateAtZeroHours(item.getDate());
|
||||
prevSectionDate = item.getDate().toLocalDate();
|
||||
}
|
||||
return itemOrHeaders;
|
||||
}
|
||||
@ -352,7 +355,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
}
|
||||
|
||||
public static class DirectItemOrHeader {
|
||||
Date date;
|
||||
LocalDate date;
|
||||
public DirectItem item;
|
||||
|
||||
public boolean isHeader() {
|
||||
@ -377,12 +380,13 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
public void bind(final Date date) {
|
||||
public void bind(final LocalDate date) {
|
||||
if (date == null) {
|
||||
binding.header.setText("");
|
||||
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 onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
|
||||
|
||||
void onAddReactionListener(DirectItem item);
|
||||
}
|
||||
|
||||
public interface DirectItemInternalLongClickListener {
|
||||
|
@ -11,6 +11,7 @@ import androidx.recyclerview.widget.ListAdapter;
|
||||
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
|
||||
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
|
||||
import awais.instagrabber.repositories.responses.discover.TopicCluster;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
|
||||
public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicClusterViewHolder> {
|
||||
@ -50,6 +51,8 @@ public class DiscoverTopicsAdapter extends ListAdapter<TopicCluster, TopicCluste
|
||||
}
|
||||
|
||||
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.adapters.viewholder.FavoriteViewHolder;
|
||||
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
||||
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||
import awais.instagrabber.db.entities.Favorite;
|
||||
import awais.instagrabber.models.enums.FavoriteType;
|
||||
|
||||
@ -73,7 +73,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
||||
// header
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -4,19 +4,12 @@ import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
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.databinding.ItemHighlightBinding;
|
||||
import awais.instagrabber.models.FeedStoryModel;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> {
|
||||
private final OnFeedStoryClickListener listener;
|
||||
@ -29,7 +22,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt
|
||||
|
||||
@Override
|
||||
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 final Filter filter = new Filter() {
|
||||
@Nullable
|
||||
@NonNull
|
||||
@Override
|
||||
protected FilterResults performFiltering(final CharSequence filter) {
|
||||
final boolean isFilterEmpty = TextUtils.isEmpty(filter);
|
||||
final String query = isFilterEmpty ? null : filter.toString().toLowerCase();
|
||||
|
||||
for (FeedStoryModel item : list) {
|
||||
if (isFilterEmpty) item.setShown(true);
|
||||
else item.setShown(item.getProfileModel().getUsername().toLowerCase().contains(query));
|
||||
final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase();
|
||||
List<FeedStoryModel> filteredList = list;
|
||||
if (list != null && query != null) {
|
||||
filteredList = list.stream()
|
||||
.filter(feedStoryModel -> feedStoryModel.getProfileModel()
|
||||
.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
|
||||
protected void publishResults(final CharSequence constraint, final FilterResults results) {
|
||||
submitList(list);
|
||||
notifyDataSetChanged();
|
||||
//noinspection unchecked
|
||||
submitList((List<FeedStoryModel>) results.values, true);
|
||||
}
|
||||
};
|
||||
|
||||
@ -51,7 +57,7 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
|
||||
|
||||
@Override
|
||||
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;
|
||||
}
|
||||
|
||||
private void submitList(@Nullable final List<FeedStoryModel> list, final boolean isFiltered) {
|
||||
if (!isFiltered) {
|
||||
this.list = list;
|
||||
}
|
||||
super.submitList(list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void submitList(final List<FeedStoryModel> list) {
|
||||
super.submitList(list.stream().filter(i -> i.isShown()).collect(Collectors.toList()));
|
||||
this.list = list;
|
||||
submitList(list, false);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -82,11 +94,11 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
|
||||
final FeedStoryModel model = getItem(position);
|
||||
holder.bind(model, position, listener);
|
||||
holder.bind(model, listener);
|
||||
}
|
||||
|
||||
public interface OnFeedStoryClickListener {
|
||||
void onFeedStoryClick(final FeedStoryModel model, final int position);
|
||||
void onFeedStoryClick(final FeedStoryModel model);
|
||||
|
||||
void onProfileClick(final String username);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public final class LikesAdapter extends RecyclerView.Adapter<FollowsViewHolder>
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
|
||||
final User model = profileModels.get(position);
|
||||
holder.bind(model, null, onClickListener);
|
||||
holder.bind(model, onClickListener);
|
||||
}
|
||||
|
||||
@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>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull final Notification oldItem, @NonNull final Notification newItem) {
|
||||
return oldItem.getPk().equals(newItem.getPk());
|
||||
public boolean areItemsTheSame(final Notification oldItem, final Notification newItem) {
|
||||
return oldItem != null && newItem != null && oldItem.getPk().equals(newItem.getPk());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -11,20 +11,19 @@ import androidx.recyclerview.widget.ListAdapter;
|
||||
import awais.instagrabber.adapters.viewholder.TopicClusterViewHolder;
|
||||
import awais.instagrabber.databinding.ItemDiscoverTopicBinding;
|
||||
import awais.instagrabber.repositories.responses.saved.SavedCollection;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
|
||||
public class SavedCollectionsAdapter extends ListAdapter<SavedCollection, TopicClusterViewHolder> {
|
||||
private static final DiffUtil.ItemCallback<SavedCollection> DIFF_CALLBACK = new DiffUtil.ItemCallback<SavedCollection>() {
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
||||
return oldItem.getId().equals(newItem.getId());
|
||||
return oldItem.getCollectionId().equals(newItem.getCollectionId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull final SavedCollection oldItem, @NonNull final SavedCollection newItem) {
|
||||
if (oldItem.getCoverMedias() != null && newItem.getCoverMedias() != null
|
||||
&& oldItem.getCoverMedias().size() == newItem.getCoverMedias().size()) {
|
||||
return oldItem.getCoverMedias().get(0).getId().equals(newItem.getCoverMedias().get(0).getId());
|
||||
if (oldItem.getCoverMediaList() != null && newItem.getCoverMediaList() != null
|
||||
&& oldItem.getCoverMediaList().size() == newItem.getCoverMediaList().size()) {
|
||||
return oldItem.getCoverMediaList().get(0).getId().equals(newItem.getCoverMediaList().get(0).getId());
|
||||
}
|
||||
else if (oldItem.getCoverMedia() != null && newItem.getCoverMedia() != null) {
|
||||
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;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
|
||||
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
||||
@Override
|
||||
public void onThumbnailLoaded(final int position) {}
|
||||
|
||||
@Override
|
||||
public void onItemClicked(final int position) {}
|
||||
public void onItemClicked(final int position, final Media media, final View view) {}
|
||||
|
||||
@Override
|
||||
public void onPlayerPlay(final int position) {}
|
||||
@ -15,4 +21,12 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback
|
||||
|
||||
@Override
|
||||
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;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||
|
||||
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
|
||||
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
|
||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
|
||||
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
|
||||
|
||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
||||
private final boolean loadVideoOnItemClick;
|
||||
private final SliderCallback sliderCallback;
|
||||
private final LayoutExoCustomControlsBinding controlsBinding;
|
||||
|
||||
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
|
||||
@Override
|
||||
@ -36,15 +35,11 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
||||
}
|
||||
};
|
||||
|
||||
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
||||
final LayoutExoCustomControlsBinding controlsBinding,
|
||||
final boolean loadVideoOnItemClick,
|
||||
public SliderItemsAdapter(final boolean loadVideoOnItemClick,
|
||||
final SliderCallback sliderCallback) {
|
||||
super(DIFF_CALLBACK);
|
||||
this.onVerticalDragListener = onVerticalDragListener;
|
||||
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
||||
this.sliderCallback = sliderCallback;
|
||||
this.controlsBinding = controlsBinding;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -55,12 +50,12 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
||||
switch (mediaItemType) {
|
||||
case MEDIA_TYPE_VIDEO: {
|
||||
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
|
||||
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding, loadVideoOnItemClick);
|
||||
return new SliderVideoViewHolder(binding, loadVideoOnItemClick);
|
||||
}
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
default:
|
||||
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 {
|
||||
void onThumbnailLoaded(int position);
|
||||
|
||||
void onItemClicked(int position);
|
||||
void onItemClicked(int position, final Media media, final View view);
|
||||
|
||||
void onPlayerPlay(int position);
|
||||
|
||||
void onPlayerPause(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;
|
||||
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.facebook.drawee.view.SimpleDraweeView;
|
||||
|
||||
import awais.instagrabber.R;
|
||||
|
||||
public final class DiscoverViewHolder extends RecyclerView.ViewHolder {
|
||||
public final SimpleDraweeView postImage;
|
||||
public final ImageView typeIcon;
|
||||
public final View selectedView;
|
||||
// public final View progressView;
|
||||
|
||||
public DiscoverViewHolder(@NonNull final View itemView) {
|
||||
super(itemView);
|
||||
typeIcon = itemView.findViewById(R.id.typeIcon);
|
||||
postImage = itemView.findViewById(R.id.postImage);
|
||||
selectedView = itemView.findViewById(R.id.selectedView);
|
||||
// progressView = itemView.findViewById(R.id.progressView);
|
||||
}
|
||||
}
|
||||
//package awais.instagrabber.adapters.viewholder;
|
||||
//
|
||||
//import android.view.View;
|
||||
//import android.widget.ImageView;
|
||||
//
|
||||
//import androidx.annotation.NonNull;
|
||||
//import androidx.recyclerview.widget.RecyclerView;
|
||||
//
|
||||
//import com.facebook.drawee.view.SimpleDraweeView;
|
||||
//
|
||||
//import awais.instagrabber.R;
|
||||
//
|
||||
//public final class DiscoverViewHolder extends RecyclerView.ViewHolder {
|
||||
// public final SimpleDraweeView postImage;
|
||||
// public final ImageView typeIcon;
|
||||
// public final View selectedView;
|
||||
// // public final View progressView;
|
||||
//
|
||||
// public DiscoverViewHolder(@NonNull final View itemView) {
|
||||
// super(itemView);
|
||||
// typeIcon = itemView.findViewById(R.id.typeIcon);
|
||||
// postImage = itemView.findViewById(R.id.postImage);
|
||||
// selectedView = itemView.findViewById(R.id.selectedView);
|
||||
// // progressView = itemView.findViewById(R.id.progressView);
|
||||
// }
|
||||
//}
|
@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import awais.instagrabber.adapters.FavoritesAdapter;
|
||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
||||
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||
import awais.instagrabber.db.entities.Favorite;
|
||||
import awais.instagrabber.models.enums.FavoriteType;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
@ -14,12 +14,12 @@ import awais.instagrabber.utils.Constants;
|
||||
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
||||
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());
|
||||
this.binding = binding;
|
||||
binding.isVerified.setVisibility(View.GONE);
|
||||
binding.verified.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public void bind(final Favorite model,
|
||||
@ -36,12 +36,12 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
||||
return longClickListener.onLongClick(model);
|
||||
});
|
||||
if (model.getType() == FavoriteType.HASHTAG) {
|
||||
binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
|
||||
binding.profilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
|
||||
} else {
|
||||
binding.ivProfilePic.setImageURI(model.getPicUrl());
|
||||
binding.profilePic.setImageURI(model.getPicUrl());
|
||||
}
|
||||
binding.tvFullName.setText(model.getDisplayName());
|
||||
binding.tvUsername.setVisibility(View.VISIBLE);
|
||||
binding.title.setVisibility(View.VISIBLE);
|
||||
binding.subtitle.setText(model.getDisplayName());
|
||||
String query = model.getQuery();
|
||||
switch (model.getType()) {
|
||||
case HASHTAG:
|
||||
@ -51,11 +51,11 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
||||
query = "@" + query;
|
||||
break;
|
||||
case LOCATION:
|
||||
binding.tvUsername.setVisibility(View.GONE);
|
||||
binding.title.setVisibility(View.GONE);
|
||||
break;
|
||||
default:
|
||||
// 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.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.asyncs.DownloadedCheckerAsyncTask;
|
||||
import awais.instagrabber.databinding.ItemFeedGridBinding;
|
||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.utils.DownloadUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
@ -68,7 +69,9 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
|
||||
setUserDetails(media, layoutPreferences);
|
||||
String thumbnailUrl = null;
|
||||
final int typeIconRes;
|
||||
switch (media.getMediaType()) {
|
||||
final MediaItemType mediaType = media.getMediaType();
|
||||
if (mediaType == null) return;
|
||||
switch (mediaType) {
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
typeIconRes = -1;
|
||||
thumbnailUrl = ResponseBodyUtils.getThumbUrl(media);
|
||||
@ -102,31 +105,28 @@ public class FeedGridItemViewHolder extends RecyclerView.ViewHolder {
|
||||
binding.typeIcon.setVisibility(View.VISIBLE);
|
||||
binding.typeIcon.setImageResource(typeIconRes);
|
||||
}
|
||||
final DownloadedCheckerAsyncTask task = new DownloadedCheckerAsyncTask(result -> {
|
||||
final List<Boolean> checkList = result.get(media.getPk());
|
||||
if (checkList == null || checkList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (media.getMediaType()) {
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
case MEDIA_TYPE_VIDEO:
|
||||
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
|
||||
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(R.color.green_A400)));
|
||||
break;
|
||||
case MEDIA_TYPE_SLIDER:
|
||||
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
|
||||
final List<Media> carouselMedia = media.getCarouselMedia();
|
||||
boolean allDownloaded = checkList.size() == (carouselMedia == null ? 0 : carouselMedia.size());
|
||||
if (allDownloaded) {
|
||||
allDownloaded = checkList.stream().allMatch(downloaded -> downloaded);
|
||||
}
|
||||
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(
|
||||
allDownloaded ? R.color.green_A400 : R.color.yellow_400)));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
task.execute(media);
|
||||
final List<Boolean> checkList = DownloadUtils.checkDownloaded(media);
|
||||
if (checkList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (media.getMediaType()) {
|
||||
case MEDIA_TYPE_IMAGE:
|
||||
case MEDIA_TYPE_VIDEO:
|
||||
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
|
||||
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(R.color.green_A400)));
|
||||
break;
|
||||
case MEDIA_TYPE_SLIDER:
|
||||
binding.downloaded.setVisibility(checkList.get(0) ? View.VISIBLE : View.GONE);
|
||||
final List<Media> carouselMedia = media.getCarouselMedia();
|
||||
boolean allDownloaded = checkList.size() == (carouselMedia == null ? 0 : carouselMedia.size());
|
||||
if (allDownloaded) {
|
||||
allDownloaded = checkList.stream().allMatch(downloaded -> downloaded);
|
||||
}
|
||||
binding.downloaded.setImageTintList(ColorStateList.valueOf(itemView.getResources().getColor(
|
||||
allDownloaded ? R.color.green_A400 : R.color.yellow_400)));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
private void setThumbImage(final String thumbnailUrl) {
|
||||
|
@ -33,7 +33,7 @@ public class FilterViewHolder extends RecyclerView.ViewHolder {
|
||||
this.binding = binding;
|
||||
this.tuneFilters = tuneFilters;
|
||||
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) {
|
||||
@ -55,13 +55,13 @@ public class FilterViewHolder extends RecyclerView.ViewHolder {
|
||||
final Bitmap bitmap = BitmapUtils.getBitmapFromMemCache(filterKey);
|
||||
if (bitmap == null) {
|
||||
final GPUImageFilter filter = item.getInstance();
|
||||
appExecutors.tasksThread().submit(() -> {
|
||||
appExecutors.getTasksThread().submit(() -> {
|
||||
GPUImage.getBitmapForMultipleFilters(
|
||||
originalBitmap,
|
||||
ImmutableList.<GPUImageFilter>builder().add(filter).addAll(tuneFilters).build(),
|
||||
filteredBitmap -> {
|
||||
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 androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.databinding.ItemFollowBinding;
|
||||
import awais.instagrabber.models.FollowModel;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
@ -14,23 +13,19 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final ItemFollowBinding binding;
|
||||
|
||||
public FollowsViewHolder(final ItemFollowBinding binding) {
|
||||
public FollowsViewHolder(@NonNull final ItemFollowBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
public void bind(final User model,
|
||||
final List<Long> admins,
|
||||
final View.OnClickListener onClickListener) {
|
||||
if (model == null) return;
|
||||
itemView.setTag(model);
|
||||
itemView.setOnClickListener(onClickListener);
|
||||
binding.tvUsername.setText(model.getUsername());
|
||||
binding.tvFullName.setText(model.getFullName());
|
||||
if (admins != null && admins.contains(model.getPk())) {
|
||||
binding.isAdmin.setVisibility(View.VISIBLE);
|
||||
}
|
||||
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
|
||||
binding.username.setUsername("@" + model.getUsername(), model.isVerified());
|
||||
binding.fullName.setText(model.getFullName());
|
||||
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
||||
}
|
||||
|
||||
public void bind(final FollowModel model,
|
||||
@ -38,8 +33,8 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
||||
if (model == null) return;
|
||||
itemView.setTag(model);
|
||||
itemView.setOnClickListener(onClickListener);
|
||||
binding.tvUsername.setText(model.getUsername());
|
||||
binding.tvFullName.setText(model.getFullName());
|
||||
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
|
||||
binding.username.setUsername("@" + model.getUsername());
|
||||
binding.fullName.setText(model.getFullName());
|
||||
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.net.Uri;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -14,8 +13,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||
|
||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
||||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
||||
import awais.instagrabber.customviews.drawee.DoubleTapGestureListener;
|
||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
@ -24,13 +23,10 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
||||
private static final String TAG = "FeedSliderPhotoViewHolder";
|
||||
|
||||
private final ItemSliderPhotoBinding binding;
|
||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
||||
|
||||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
|
||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
|
||||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding) {
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
this.onVerticalDragListener = onVerticalDragListener;
|
||||
}
|
||||
|
||||
public void bind(@NonNull final Media model,
|
||||
@ -62,74 +58,19 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
||||
})
|
||||
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
||||
.build());
|
||||
// binding.getRoot().setOnClickListener(v -> {
|
||||
// if (sliderCallback != null) {
|
||||
// sliderCallback.onItemClicked(position);
|
||||
// }
|
||||
// });
|
||||
binding.getRoot().setTapListener(new GestureDetector.SimpleOnGestureListener() {
|
||||
final DoubleTapGestureListener tapListener = new DoubleTapGestureListener(binding.getRoot()) {
|
||||
@Override
|
||||
public boolean onSingleTapUp(final MotionEvent e) {
|
||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onItemClicked(position);
|
||||
return true;
|
||||
sliderCallback.onItemClicked(position, model, binding.getRoot());
|
||||
}
|
||||
return false;
|
||||
return super.onSingleTapConfirmed(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
binding.getRoot().setTapListener(tapListener);
|
||||
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
||||
zoomableController.setMaxScaleFactor(3f);
|
||||
binding.getRoot().setZoomableController(zoomableController);
|
||||
if (onVerticalDragListener != null) {
|
||||
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
|
||||
}
|
||||
binding.getRoot().setZoomingEnabled(true);
|
||||
}
|
||||
|
||||
// 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 com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.VideoVersion;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
@ -28,40 +28,23 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
||||
private static final String TAG = "SliderVideoViewHolder";
|
||||
|
||||
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
||||
private final LayoutExoCustomControlsBinding controlsBinding;
|
||||
private final boolean loadVideoOnItemClick;
|
||||
private final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||
binding.playerView.performClick();
|
||||
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.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||
binding.playerView.performClick();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
|
||||
binding.playerView.setOnTouchListener((v, event) -> {
|
||||
gestureDetector.onTouchEvent(event);
|
||||
@ -72,13 +55,13 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
||||
public void bind(@NonNull final Media media,
|
||||
final int position,
|
||||
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() {
|
||||
|
||||
@Override
|
||||
public void onThumbnailClick() {
|
||||
if (sliderCallback != null) {
|
||||
sliderCallback.onItemClicked(position);
|
||||
sliderCallback.onItemClicked(position, media, binding.getRoot());
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,6 +104,21 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
||||
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();
|
||||
String videoUrl = null;
|
||||
@ -139,16 +137,10 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
||||
aspectRatio,
|
||||
ResponseBodyUtils.getThumbUrl(media),
|
||||
loadVideoOnItemClick,
|
||||
controlsBinding,
|
||||
videoPlayerCallback);
|
||||
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
|
||||
// final float newVol = videoPlayerViewHelper.toggleMute();
|
||||
// setMuteIcon(newVol);
|
||||
// Utils.sessionVolumeFull = newVol == 1f;
|
||||
// });
|
||||
binding.playerView.setOnClickListener(v -> {
|
||||
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;
|
||||
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,
|
||||
final int position,
|
||||
final OnFeedStoryClickListener notificationClickListener) {
|
||||
if (model == null) return;
|
||||
|
||||
@ -53,7 +52,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
itemView.setOnClickListener(v -> {
|
||||
if (notificationClickListener == null) return;
|
||||
notificationClickListener.onFeedStoryClick(model, position);
|
||||
notificationClickListener.onFeedStoryClick(model);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,14 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
||||
if (onTopicClickListener != null) {
|
||||
itemView.setOnClickListener(v -> onTopicClickListener.onTopicClick(
|
||||
topicCluster,
|
||||
binding.getRoot(),
|
||||
binding.cover,
|
||||
binding.title,
|
||||
titleColor.get(),
|
||||
backgroundColor.get()
|
||||
));
|
||||
itemView.setOnLongClickListener(v -> {
|
||||
onTopicClickListener.onTopicLongClick(topicCluster.getCoverMedia());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
// binding.title.setTransitionName("title-" + topicCluster.getId());
|
||||
binding.cover.setTransitionName("cover-" + topicCluster.getId());
|
||||
@ -126,11 +128,11 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
||||
backgroundColor.get()
|
||||
));
|
||||
}
|
||||
// binding.title.setTransitionName("title-" + topicCluster.getId());
|
||||
binding.cover.setTransitionName("cover-" + topicCluster.getId());
|
||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMedias() == null
|
||||
// binding.title.setTransitionName("title-" + topicCluster.getCollectionId());
|
||||
binding.cover.setTransitionName("cover-" + topicCluster.getCollectionId());
|
||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(topicCluster.getCoverMediaList() == null
|
||||
? topicCluster.getCoverMedia()
|
||||
: topicCluster.getCoverMedias().get(0));
|
||||
: topicCluster.getCoverMediaList().get(0));
|
||||
if (thumbUrl == null) {
|
||||
binding.cover.setImageURI((String) null);
|
||||
} else {
|
||||
@ -172,6 +174,6 @@ public class TopicClusterViewHolder extends RecyclerView.ViewHolder {
|
||||
}, CallerThreadExecutor.getInstance());
|
||||
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.adapters.KeywordsFilterAdapter;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||
import awais.instagrabber.utils.SettingsHelper;
|
||||
|
||||
public class KeywordsFilterDialogViewHolder extends RecyclerView.ViewHolder {
|
||||
@ -34,7 +34,7 @@ public class KeywordsFilterDialogViewHolder extends RecyclerView.ViewHolder {
|
||||
final String s = items.get(position);
|
||||
SettingsHelper settingsHelper = new SettingsHelper(context);
|
||||
items.remove(position);
|
||||
settingsHelper.putStringSet(Constants.KEYWORD_FILTERS, new HashSet<>(items));
|
||||
settingsHelper.putStringSet(PreferenceKeys.KEYWORD_FILTERS, new HashSet<>(items));
|
||||
adapter.notifyDataSetChanged();
|
||||
final String message = context.getString(R.string.removed_keywords, s);
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
|
@ -17,11 +17,9 @@ import java.util.List;
|
||||
import awais.instagrabber.R;
|
||||
import awais.instagrabber.adapters.DirectMessageInboxAdapter.OnItemClickListener;
|
||||
import awais.instagrabber.databinding.LayoutDmInboxItemBinding;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
|
||||
import awais.instagrabber.utils.DMUtils;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
@ -136,7 +134,7 @@ public final class DirectInboxItemViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private void setDateTime(@NonNull final DirectItem item) {
|
||||
final long timestamp = item.getTimestamp() / 1000;
|
||||
final String dateTimeString = TextUtils.getRelativeDateTimeString(itemView.getContext(), timestamp);
|
||||
final String dateTimeString = TextUtils.getRelativeDateTimeString(timestamp);
|
||||
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.DirectItemActionLog;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.repositories.responses.directmessages.TextRange;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
|
||||
@ -45,16 +46,16 @@ public class DirectItemActionLogViewHolder extends DirectItemViewHolder {
|
||||
final DirectItemActionLog actionLog = directItemModel.getActionLog();
|
||||
final String text = actionLog.getDescription();
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||
final List<DirectItemActionLog.TextRange> bold = actionLog.getBold();
|
||||
final List<TextRange> bold = actionLog.getBold();
|
||||
if (bold != null && !bold.isEmpty()) {
|
||||
for (final DirectItemActionLog.TextRange textRange : bold) {
|
||||
for (final TextRange textRange : bold) {
|
||||
final StyleSpan boldStyleSpan = new StyleSpan(Typeface.BOLD);
|
||||
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()) {
|
||||
for (final DirectItemActionLog.TextRange textAttribute : textAttributes) {
|
||||
for (final TextRange textAttribute : textAttributes) {
|
||||
if (!TextUtils.isEmpty(textAttribute.getColor())) {
|
||||
final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
|
||||
sb.setSpan(colorSpan, textAttribute.getStart(), textAttribute.getEnd(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
|
||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
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.DirectItemAnimatedMedia;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.NullSafePair;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
|
||||
@ -48,7 +48,7 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
||||
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
|
||||
if (fixedHeight == null) return;
|
||||
final String url = fixedHeight.getWebp();
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
fixedHeight.getHeight(),
|
||||
fixedHeight.getWidth(),
|
||||
mediaImageMaxHeight,
|
||||
@ -56,8 +56,8 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
||||
);
|
||||
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
||||
final int height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
final int width = widthHeight.first;
|
||||
final int height = widthHeight.second;
|
||||
layoutParams.width = width;
|
||||
layoutParams.height = height;
|
||||
binding.ivAnimatedMessage.requestLayout();
|
||||
|
@ -6,7 +6,6 @@ import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
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.DirectItemFelixShare;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.NullSafePair;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
@ -103,15 +103,15 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||
.setRoundingParams(roundingParams)
|
||||
.build());
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
media.getOriginalHeight(),
|
||||
media.getOriginalWidth(),
|
||||
mediaImageMaxHeight,
|
||||
mediaImageMaxWidth
|
||||
);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
layoutParams.width = widthHeight.first;
|
||||
layoutParams.height = widthHeight.second;
|
||||
binding.mediaPreview.requestLayout();
|
||||
binding.mediaPreview.setTag(url);
|
||||
binding.mediaPreview.setImageURI(url);
|
||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils;
|
||||
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.LayoutDmMediaBinding;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.repositories.responses.ImageVersions2;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.NullSafePair;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
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
|
||||
? View.VISIBLE
|
||||
: View.GONE);
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
media.getOriginalHeight(),
|
||||
media.getOriginalWidth(),
|
||||
mediaImageMaxHeight,
|
||||
mediaImageMaxWidth
|
||||
);
|
||||
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.height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
layoutParams.height = widthHeight.second;
|
||||
binding.mediaPreview.requestLayout();
|
||||
binding.bgTime.getLayoutParams().width = width;
|
||||
binding.bgTime.requestLayout();
|
||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import com.facebook.drawee.drawable.ScalingUtils;
|
||||
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.DirectItemVisualMedia;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.NullSafePair;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
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
|
||||
? View.VISIBLE
|
||||
: View.GONE);
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
media.getOriginalHeight(),
|
||||
media.getOriginalWidth(),
|
||||
mediaImageMaxHeight,
|
||||
maxWidth
|
||||
);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
|
||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
layoutParams.width = widthHeight.first;
|
||||
layoutParams.height = widthHeight.second;
|
||||
binding.preview.requestLayout();
|
||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
|
||||
binding.preview.setImageURI(thumbUrl);
|
||||
|
@ -5,7 +5,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
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.LayoutDmStoryShareBinding;
|
||||
import awais.instagrabber.models.enums.MediaItemType;
|
||||
import awais.instagrabber.repositories.responses.ImageVersions2;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.utils.NullSafePair;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
@ -76,15 +75,15 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
|
||||
.setRoundingParams(roundingParams)
|
||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||
.build());
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
storyShareMedia.getOriginalHeight(),
|
||||
storyShareMedia.getOriginalWidth(),
|
||||
mediaImageMaxHeight,
|
||||
mediaImageMaxWidth
|
||||
);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
|
||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
layoutParams.width = widthHeight.first;
|
||||
layoutParams.height = widthHeight.second;
|
||||
binding.ivMediaPreview.requestLayout();
|
||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
|
||||
binding.ivMediaPreview.setImageURI(thumbUrl);
|
||||
|
@ -17,9 +17,9 @@ import awais.instagrabber.databinding.LayoutDmActionLogBinding;
|
||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||
import awais.instagrabber.repositories.responses.User;
|
||||
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.DirectThread;
|
||||
import awais.instagrabber.repositories.responses.directmessages.TextRange;
|
||||
import awais.instagrabber.utils.TextUtils;
|
||||
|
||||
public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
|
||||
@ -41,9 +41,9 @@ public class DirectItemVideoCallEventViewHolder extends DirectItemViewHolder {
|
||||
final DirectItemVideoCallEvent videoCallEvent = directItemModel.getVideoCallEvent();
|
||||
final String text = videoCallEvent.getDescription();
|
||||
final SpannableStringBuilder sb = new SpannableStringBuilder(text);
|
||||
final List<DirectItemActionLog.TextRange> textAttributes = videoCallEvent.getTextAttributes();
|
||||
final List<TextRange> textAttributes = videoCallEvent.getTextAttributes();
|
||||
if (textAttributes != null && !textAttributes.isEmpty()) {
|
||||
for (final DirectItemActionLog.TextRange textAttribute : textAttributes) {
|
||||
for (final TextRange textAttribute : textAttributes) {
|
||||
if (!TextUtils.isEmpty(textAttribute.getColor())) {
|
||||
final ForegroundColorSpan colorSpan = new ForegroundColorSpan(itemView.getResources().getColor(R.color.deep_orange_400));
|
||||
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.graphics.Point;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
@ -25,6 +24,9 @@ import androidx.transition.TransitionManager;
|
||||
import com.google.android.material.transition.MaterialFade;
|
||||
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.stream.Collectors;
|
||||
|
||||
@ -144,7 +146,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
||||
}
|
||||
setupReply(item, messageDirection);
|
||||
setReactions(item, position);
|
||||
if (item.getRepliedToMessage() == null && item.showForwardAttribution()) {
|
||||
if (item.getRepliedToMessage() == null && item.getShowForwardAttribution()) {
|
||||
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.tvUsername.setVisibility(messageDirection == MessageDirection.INCOMING && thread.isGroup() ? View.VISIBLE : View.GONE);
|
||||
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) {
|
||||
binding.tvUsername.setText(user.getUsername());
|
||||
binding.ivProfilePic.setImageURI(user.getProfilePicUrl());
|
||||
@ -193,7 +197,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
||||
if (showMessageInfo()) {
|
||||
binding.messageInfo.setVisibility(View.VISIBLE);
|
||||
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 (item.isPending()) {
|
||||
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) {
|
||||
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 {
|
||||
binding.quoteLine.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.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
|
||||
menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb));
|
||||
menu.setOnAddReactionListener(() -> {
|
||||
menu.dismiss();
|
||||
itemView.postDelayed(() -> callback.onAddReactionListener(item), 300);
|
||||
});
|
||||
menu.show(itemView, location);
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.util.Pair;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
|
||||
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.DirectItemXma;
|
||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||
import awais.instagrabber.repositories.responses.directmessages.XmaUrlInfo;
|
||||
import awais.instagrabber.utils.NullSafePair;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
|
||||
public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
||||
@ -35,15 +36,15 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
||||
@Override
|
||||
public void bindItem(final DirectItem item, final MessageDirection messageDirection) {
|
||||
final DirectItemXma xma = item.getXma();
|
||||
final DirectItemXma.XmaUrlInfo playableUrlInfo = xma.getPlayableUrlInfo();
|
||||
final DirectItemXma.XmaUrlInfo previewUrlInfo = xma.getPreviewUrlInfo();
|
||||
final XmaUrlInfo playableUrlInfo = xma.getPlayableUrlInfo();
|
||||
final XmaUrlInfo previewUrlInfo = xma.getPreviewUrlInfo();
|
||||
if (playableUrlInfo == null && previewUrlInfo == null) {
|
||||
binding.ivAnimatedMessage.setController(null);
|
||||
return;
|
||||
}
|
||||
final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo;
|
||||
final XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo;
|
||||
final String url = urlInfo.getUrl();
|
||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||
urlInfo.getHeight(),
|
||||
urlInfo.getWidth(),
|
||||
mediaImageMaxHeight,
|
||||
@ -51,8 +52,8 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
|
||||
);
|
||||
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
||||
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
||||
final int height = widthHeight.second != null ? widthHeight.second : 0;
|
||||
final int width = widthHeight.first;
|
||||
final int height = widthHeight.second;
|
||||
layoutParams.width = width;
|
||||
layoutParams.height = height;
|
||||
binding.ivAnimatedMessage.requestLayout();
|
||||
|
@ -31,7 +31,7 @@ public class DirectReactionViewHolder extends RecyclerView.ViewHolder {
|
||||
this.onReactionClickListener = onReactionClickListener;
|
||||
binding.info.setVisibility(View.GONE);
|
||||
binding.secondaryImage.setVisibility(View.VISIBLE);
|
||||
emojiParser = EmojiParser.getInstance();
|
||||
emojiParser = EmojiParser.Companion.getInstance(itemView.getContext());
|
||||
}
|
||||
|
||||
public void bind(final DirectItemEmojiReaction reaction,
|
||||
|
@ -37,7 +37,7 @@ public class RecipientThreadViewHolder extends RecyclerView.ViewHolder {
|
||||
final DirectThread thread,
|
||||
final boolean showSelection,
|
||||
final boolean isSelected) {
|
||||
if (thread == null) return;
|
||||
if (thread == null || thread.getUsers().size() == 0) return;
|
||||
binding.getRoot().setOnClickListener(v -> {
|
||||
if (onThreadClickListener == null) return;
|
||||
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) {
|
||||
final long commentsCount = feedModel.getCommentCount();
|
||||
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) {
|
||||
@ -75,6 +75,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
||||
// final SpannableString spannableString = new SpannableString();
|
||||
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
|
||||
final User user = media.getUser();
|
||||
if (user == null) return;
|
||||
final String title = "@" + user.getUsername();
|
||||
topBinding.title.setText(title);
|
||||
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(
|
||||
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
|
||||
));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
final String locationName = location.getName();
|
||||
if (TextUtils.isEmpty(locationName)) {
|
||||
topBinding.location.setVisibility(View.GONE);
|
||||
|
@ -45,9 +45,9 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
|
||||
final String text = "1/" + sliderItemLen;
|
||||
binding.mediaCounter.setText(text);
|
||||
binding.mediaList.setOffscreenPageLimit(1);
|
||||
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, null, false, new SliderCallbackAdapter() {
|
||||
final SliderItemsAdapter adapter = new SliderItemsAdapter(false, new SliderCallbackAdapter() {
|
||||
@Override
|
||||
public void onItemClicked(final int position) {
|
||||
public void onItemClicked(final int position, final Media media, final View view) {
|
||||
feedItemCallback.onSliderClick(feedModel, position);
|
||||
}
|
||||
});
|
||||
|
@ -18,9 +18,9 @@ import awais.instagrabber.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||
import awais.instagrabber.databinding.ItemFeedVideoBinding;
|
||||
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.repositories.responses.VideoVersion;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.NumberUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
@ -65,7 +65,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
||||
// Log.d(TAG, "Binding post: " + feedModel.getPostId());
|
||||
this.media = media;
|
||||
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() {
|
||||
|
||||
@Override
|
||||
@ -97,7 +97,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
||||
aspectRatio,
|
||||
ResponseBodyUtils.getThumbUrl(media),
|
||||
false,
|
||||
null,
|
||||
// null,
|
||||
videoPlayerCallback);
|
||||
binding.videoPost.thumbnail.post(() -> {
|
||||
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.Media;
|
||||
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.TagsService;
|
||||
import kotlinx.coroutines.Dispatchers;
|
||||
|
||||
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
||||
private final TagsService tagsService;
|
||||
private final GraphQLService graphQLService;
|
||||
private final GraphQLRepository graphQLRepository;
|
||||
private final Hashtag hashtagModel;
|
||||
private String nextMaxId;
|
||||
private boolean moreAvailable;
|
||||
@ -23,7 +25,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
||||
this.hashtagModel = hashtagModel;
|
||||
this.isLoggedIn = isLoggedIn;
|
||||
tagsService = isLoggedIn ? TagsService.getInstance() : null;
|
||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
||||
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,7 +50,17 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
|
||||
}
|
||||
};
|
||||
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
|
||||
|
@ -7,13 +7,15 @@ import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.repositories.responses.Location;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
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.ServiceCallback;
|
||||
import kotlinx.coroutines.Dispatchers;
|
||||
|
||||
public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
||||
private final LocationService locationService;
|
||||
private final GraphQLService graphQLService;
|
||||
private final GraphQLRepository graphQLRepository;
|
||||
private final Location locationModel;
|
||||
private String nextMaxId;
|
||||
private boolean moreAvailable;
|
||||
@ -23,7 +25,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
||||
this.locationModel = locationModel;
|
||||
this.isLoggedIn = isLoggedIn;
|
||||
locationService = isLoggedIn ? LocationService.getInstance() : null;
|
||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
||||
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -48,7 +50,17 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
|
||||
}
|
||||
};
|
||||
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
|
||||
|
@ -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.PostsFetchResponse;
|
||||
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.ServiceCallback;
|
||||
import kotlinx.coroutines.Dispatchers;
|
||||
|
||||
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
||||
private static final String TAG = "ProfilePostFetchService";
|
||||
private final ProfileService profileService;
|
||||
private final GraphQLService graphQLService;
|
||||
private final GraphQLRepository graphQLRepository;
|
||||
private final User profileModel;
|
||||
private final boolean isLoggedIn;
|
||||
private String nextMaxId;
|
||||
@ -23,7 +25,7 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
||||
public ProfilePostFetchService(final User profileModel, final boolean isLoggedIn) {
|
||||
this.profileModel = profileModel;
|
||||
this.isLoggedIn = isLoggedIn;
|
||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
||||
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||
profileService = isLoggedIn ? ProfileService.getInstance() : null;
|
||||
}
|
||||
|
||||
@ -49,7 +51,19 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
|
||||
}
|
||||
};
|
||||
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
|
||||
|
@ -7,13 +7,15 @@ import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.enums.PostItemType;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
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.ServiceCallback;
|
||||
import kotlinx.coroutines.Dispatchers;
|
||||
|
||||
public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
||||
private final ProfileService profileService;
|
||||
private final GraphQLService graphQLService;
|
||||
private final GraphQLRepository graphQLRepository;
|
||||
private final long profileId;
|
||||
private final PostItemType type;
|
||||
private final boolean isLoggedIn;
|
||||
@ -27,7 +29,7 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
||||
this.type = type;
|
||||
this.isLoggedIn = isLoggedIn;
|
||||
this.collectionId = collectionId;
|
||||
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
|
||||
graphQLRepository = isLoggedIn ? null : GraphQLRepository.Companion.getInstance();
|
||||
profileService = isLoggedIn ? ProfileService.getInstance() : null;
|
||||
}
|
||||
|
||||
@ -58,7 +60,18 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
|
||||
break;
|
||||
case TAGGED:
|
||||
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;
|
||||
case COLLECTION:
|
||||
case SAVED:
|
||||
|
@ -105,12 +105,15 @@ public class ChatMessageLayout extends FrameLayout {
|
||||
viewPartMainLastLineWidth = viewPartMainLineCount > 0
|
||||
? ((TextView) firstChild).getLayout().getLineWidth(viewPartMainLineCount - 1)
|
||||
: 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;
|
||||
heightSize += viewPartMainHeight;
|
||||
} else if (viewPartMainLineCount > 1 && (viewPartMainLastLineWidth + viewPartInfoWidth > availableWidth)) {
|
||||
} else if (viewPartMainLineCount > 1 && (lastLineWithInfoWidth > availableWidth)) {
|
||||
widthSize += viewPartMainWidth;
|
||||
heightSize += viewPartMainHeight + viewPartInfoHeight;
|
||||
} else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartInfoWidth > availableWidth)) {
|
||||
@ -120,6 +123,16 @@ public class ChatMessageLayout extends FrameLayout {
|
||||
heightSize += viewPartMainHeight;
|
||||
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);
|
||||
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())) {
|
||||
throw new IllegalArgumentException("showReactions is set false and options are empty");
|
||||
}
|
||||
reactionsManager = ReactionsManager.getInstance();
|
||||
reactionsManager = ReactionsManager.getInstance(context);
|
||||
final Resources resources = context.getResources();
|
||||
emojiSize = resources.getDimensionPixelSize(R.dimen.reaction_picker_emoji_size);
|
||||
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.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -25,15 +27,16 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
import awais.instagrabber.adapters.FeedAdapterV2;
|
||||
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
|
||||
import awais.instagrabber.customviews.helpers.PostFetcher;
|
||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
|
||||
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||
import awais.instagrabber.interfaces.FetchListener;
|
||||
import awais.instagrabber.models.PostsLayoutPreferences;
|
||||
import awais.instagrabber.repositories.responses.Media;
|
||||
import awais.instagrabber.utils.Constants;
|
||||
import awais.instagrabber.utils.KeywordsFilterUtils;
|
||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||
import awais.instagrabber.utils.Utils;
|
||||
@ -60,14 +63,17 @@ public class PostsRecyclerView extends RecyclerView {
|
||||
private FeedAdapterV2.FeedItemCallback feedItemCallback;
|
||||
private boolean shouldScrollToTop;
|
||||
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 FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
|
||||
@Override
|
||||
public void onResult(final List<Media> result) {
|
||||
final int currentPage = lazyLoader.getCurrentPage();
|
||||
if (currentPage == 0) {
|
||||
if (refresh) {
|
||||
refresh = false;
|
||||
mediaViewModel.getList().postValue(result);
|
||||
shouldScrollToTop = true;
|
||||
dispatchFetchStatus();
|
||||
@ -75,8 +81,8 @@ public class PostsRecyclerView extends RecyclerView {
|
||||
}
|
||||
final List<Media> models = mediaViewModel.getList().getValue();
|
||||
final List<Media> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
|
||||
if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)) {
|
||||
final ArrayList<String> items = new ArrayList<>(settingsHelper.getStringSet(Constants.KEYWORD_FILTERS));
|
||||
if (settingsHelper.getBoolean(PreferenceKeys.TOGGLE_KEYWORD_FILTER)) {
|
||||
final ArrayList<String> items = new ArrayList<>(settingsHelper.getStringSet(PreferenceKeys.KEYWORD_FILTERS));
|
||||
modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result));
|
||||
} else {
|
||||
modelsCopy.addAll(result);
|
||||
@ -192,22 +198,25 @@ public class PostsRecyclerView extends RecyclerView {
|
||||
}
|
||||
|
||||
private void initSelf() {
|
||||
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
|
||||
mediaViewModel.getList().observe(lifeCycleOwner, list -> {
|
||||
if (list.size() <= 0) return;
|
||||
feedAdapter.submitList(list, () -> {
|
||||
// postDelayed(this::fetchMoreIfPossible, 1000);
|
||||
if (!shouldScrollToTop) return;
|
||||
smoothScrollToPosition(0);
|
||||
shouldScrollToTop = false;
|
||||
});
|
||||
});
|
||||
try {
|
||||
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "initSelf: ", e);
|
||||
}
|
||||
if (mediaViewModel == null) return;
|
||||
mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
|
||||
// postDelayed(this::fetchMoreIfPossible, 1000);
|
||||
if (!shouldScrollToTop) return;
|
||||
shouldScrollToTop = false;
|
||||
post(() -> smoothScrollToPosition(0));
|
||||
}));
|
||||
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
||||
if (layoutPreferences.getHasGap()) {
|
||||
addItemDecoration(gridSpacingItemDecoration);
|
||||
}
|
||||
setHasFixedSize(true);
|
||||
setNestedScrollingEnabled(true);
|
||||
setItemAnimator(null);
|
||||
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
|
||||
if (postFetcher.hasMore()) {
|
||||
postFetcher.fetch();
|
||||
@ -311,11 +320,12 @@ public class PostsRecyclerView extends RecyclerView {
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
refresh = true;
|
||||
if (lazyLoader != null) {
|
||||
lazyLoader.resetState();
|
||||
}
|
||||
if (postFetcher != null) {
|
||||
mediaViewModel.getList().postValue(Collections.emptyList());
|
||||
// mediaViewModel.getList().postValue(Collections.emptyList());
|
||||
postFetcher.reset();
|
||||
postFetcher.fetch();
|
||||
}
|
||||
|
@ -70,6 +70,9 @@ public final class ProfilePicView extends CircularImageView {
|
||||
case SMALL:
|
||||
dimenRes = R.dimen.profile_pic_size_small;
|
||||
break;
|
||||
case SMALLER:
|
||||
dimenRes = R.dimen.profile_pic_size_smaller;
|
||||
break;
|
||||
case TINY:
|
||||
dimenRes = R.dimen.profile_pic_size_tiny;
|
||||
break;
|
||||
@ -113,7 +116,8 @@ public final class ProfilePicView extends CircularImageView {
|
||||
TINY(0),
|
||||
SMALL(1),
|
||||
REGULAR(2),
|
||||
LARGE(3);
|
||||
LARGE(3),
|
||||
SMALLER(4);
|
||||
|
||||
private final int value;
|
||||
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