1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-12-23 13:26:59 +00:00

Merge branch 'stamatiap/development' of https://github.com/raniapl/barinsta into stamatiap/development

This commit is contained in:
stamatiap 2021-05-16 10:10:52 +03:00
commit 892bfa336f
131 changed files with 5084 additions and 3100 deletions

View File

@ -79,6 +79,25 @@
"code" "code"
] ]
}, },
{
"login": "stamatiap",
"name": "Stamatia Papageorgiou",
"avatar_url": "https://avatars.githubusercontent.com/u/57223967?v=4",
"profile": "https://github.com/stamatiap",
"contributions": [
"code",
"translation"
]
},
{
"login": "The-EDev",
"name": "Farook Al-Sammarraie",
"avatar_url": "https://avatars.githubusercontent.com/u/60552923?v=4",
"profile": "https://github.com/The-EDev",
"contributions": [
"code"
]
},
{ {
"login": "Zopieux", "login": "Zopieux",
"name": "Alexandre Macabies", "name": "Alexandre Macabies",

View File

@ -15,18 +15,19 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: set up JDK 1.8 - name: set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: 1.8 distribution: 'zulu'
java-version: '8'
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew
- name: Build Github unsigned apk - name: Build Github unsigned apk
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: ammargitham/sign-android-release@v1.1.1
# ID used to access action output # ID used to access action output
id: sign_app id: sign_app
with: with:
@ -45,7 +46,8 @@ jobs:
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: barinsta_nightly_${{ steps.date.outputs.date }} name: barinsta_nightly_${{ steps.date.outputs.date }}
path: ${{steps.sign_app.outputs.signedReleaseFile}} # path: ${{steps.sign_app.outputs.signedReleaseFile}}
path: app/build/outputs/apk/github/release/*-signed.apk
# Send success notification # Send success notification
- name: Send success Telegram notification - name: Send success Telegram notification
@ -55,7 +57,8 @@ jobs:
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }} to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }} token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
document: ${{steps.sign_app.outputs.signedReleaseFile}} # document: ${{steps.sign_app.outputs.signedReleaseFile}}
document: app/build/outputs/apk/github/release/*-signed.apk
# Send failure notification # Send failure notification
- name: Send failure Telegram notification - name: Send failure Telegram notification

View File

@ -16,18 +16,19 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: set up JDK 1.8 - name: set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: 1.8 distribution: 'zulu'
java-version: '8'
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew
- name: Build Github unsigned pre-release apk - name: Build Github unsigned pre-release apk
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split
- name: Sign APK - name: Sign APK
uses: r0adkll/sign-android-release@v1 uses: ammargitham/sign-android-release@v1.1.1
# ID used to access action output # ID used to access action output
id: sign_app id: sign_app
with: with:
@ -46,7 +47,8 @@ jobs:
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: barinsta_pre-release_${{ steps.date.outputs.date }} name: barinsta_pre-release_${{ steps.date.outputs.date }}
path: ${{steps.sign_app.outputs.signedReleaseFile}} # path: ${{steps.sign_app.outputs.signedReleaseFile}}
path: app/build/outputs/apk/github/release/*-signed.apk
# Send success notification # Send success notification
- name: Send success Telegram notification - name: Send success Telegram notification
@ -56,7 +58,8 @@ jobs:
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }} to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }} token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}" message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
document: ${{steps.sign_app.outputs.signedReleaseFile}} # document: ${{steps.sign_app.outputs.signedReleaseFile}}
document: app/build/outputs/apk/github/release/*-signed.apk
# Send failure notification # Send failure notification
- name: Send failure Telegram notification - name: Send failure Telegram notification

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="1.8" /> <bytecodeTargetLevel target="11" />
</component> </component>
</project> </project>

View File

@ -7,7 +7,6 @@
<option name="testRunner" value="PLATFORM" /> <option name="testRunner" value="PLATFORM" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

View File

@ -40,7 +40,7 @@
</value> </value>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@ -3,6 +3,7 @@
<component name="RunConfigurationProducerService"> <component name="RunConfigurationProducerService">
<option name="ignoredProducers"> <option name="ignoredProducers">
<set> <set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" /> <option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />

View File

@ -9,7 +9,7 @@
[![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)](http://makeapullrequest.com)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE) [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> [![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg)](#contributors) [![All Contributors](https://img.shields.io/badge/all_contributors-44-orange.svg)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END --> <!-- ALL-CONTRIBUTORS-BADGE:END -->
Instagram client; previously known as InstaGrabber. Instagram client; previously known as InstaGrabber.
@ -63,49 +63,53 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td> <td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/stamatiap"><img src="https://avatars.githubusercontent.com/u/57223967?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stamatia Papageorgiou</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=stamatiap" title="Code">💻</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/The-EDev"><img src="https://avatars.githubusercontent.com/u/60552923?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Farook Al-Sammarraie</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=The-EDev" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td> <td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td>
<td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td> <td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td>
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="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>
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr> </tr>
@ -121,7 +125,7 @@ This app's predecessor, InstaGrabber, was originally made by [@AwaisKing](https:
Barinsta Barinsta
Copyright (C) 2020-2021 Austin Huang <im@austinhuang.me> Copyright (C) 2020-2021 Austin Huang <im@austinhuang.me>
Ammar Githam <ammargitham786@gmail.com> Ammar Githam <ammar.githam@outlook.com>
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by

View File

@ -12,7 +12,7 @@ def getGitHash = { ->
} }
android { android {
compileSdkVersion 29 compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId 'me.austinhuang.instagrabber' applicationId 'me.austinhuang.instagrabber'
@ -82,6 +82,27 @@ android {
} }
} }
splits {
// Configures multiple APKs based on ABI.
abi {
// Enables building multiple APKs per ABI.
enable project.hasProperty("split") && !gradle.startParameter.taskNames.isEmpty() && gradle.startParameter.taskNames.get(0).contains('Release')
// By default all ABIs are included, so use reset() and include to specify that we only
// want APKs for x86 and x86_64.
// Resets the list of ABIs that Gradle should create APKs for to none.
reset()
// Specifies a list of ABIs that Gradle should create APKs for.
include "x86", "x86_64", "arm64-v8a", "armeabi-v7a"
// Specifies that we want to also generate a universal APK that includes all ABIs.
universalApk true
}
}
android.applicationVariants.all { variant -> android.applicationVariants.all { variant ->
if (variant.flavorName != "github") return if (variant.flavorName != "github") return
variant.outputs.all { output -> variant.outputs.all { output ->
@ -90,15 +111,32 @@ android {
// def versionCode = variant.versionCode // def versionCode = variant.versionCode
def flavor = variant.flavorName def flavor = variant.flavorName
def suffix = "${versionName}-${flavor}_${builtType}" // eg. 19.1.0-github_debug or release def flavorBuiltType = "${flavor}_${builtType}"
def suffix
// For x86 and x86_64, the versionNames are already overridden
if (versionName.contains(flavorBuiltType)) {
suffix = "${versionName}"
} else {
suffix = "${versionName}-${flavorBuiltType}" // eg. 19.1.0-github_debug or release
}
if (builtType.toString() == 'release' && project.hasProperty("pre")) { if (builtType.toString() == 'release' && project.hasProperty("pre")) {
buildConfigField("boolean", "isPre", "true") buildConfigField("boolean", "isPre", "true")
flavorBuiltType = "${getGitHash()}-${flavor}"
// For x86 and x86_64, the versionNames are already overridden
if (versionName.contains(flavorBuiltType)) {
suffix = "${versionName}"
} else {
// append latest commit short hash for pre-release // append latest commit short hash for pre-release
suffix = "${versionName}.${getGitHash()}-${flavor}" // eg. 19.1.0.b123456-github suffix = "${versionName}.${flavorBuiltType}" // eg. 19.1.0.b123456-github
}
} }
output.versionNameOverride = suffix output.versionNameOverride = suffix
outputFileName = "barinsta_${suffix}.apk" def abi = output.getFilter(com.android.build.OutputFile.ABI)
// println(abi + ", " + versionName + ", " + flavor + ", " + builtType + ", " + suffix)
outputFileName = abi == null ? "barinsta_${suffix}.apk" : "barinsta_${suffix}_${abi}.apk"
} }
} }
@ -118,18 +156,16 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
def appcompat_version = "1.2.0" def appcompat_version = "1.2.0"
def nav_version = '2.3.4' def nav_version = '2.3.5'
def exoplayer_version = '2.13.3' 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-core:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation "androidx.appcompat:appcompat:$appcompat_version" implementation "androidx.recyclerview:recyclerview:1.2.0"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-fragment:$nav_version"
@ -142,6 +178,9 @@ dependencies {
implementation 'com.google.guava:guava:27.0.1-android' implementation 'com.google.guava:guava:27.0.1-android'
def core_version = "1.6.0-alpha03"
implementation "androidx.core:core:$core_version"
// Room // Room
def room_version = "2.2.6" def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
@ -169,6 +208,8 @@ dependencies {
implementation 'org.apache.commons:commons-imaging:1.0-alpha2' implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
implementation 'com.github.skydoves:balloon:1.3.4'
implementation 'com.github.ammargitham:AutoLinkTextViewV2:v3.1.0' implementation 'com.github.ammargitham:AutoLinkTextViewV2:v3.1.0'
implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2' implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2'
implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4' implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4'

View File

@ -27,8 +27,7 @@
<activity <activity
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:launchMode="singleTop" android:launchMode="singleTop"
android:taskAffinity=".Main" android:taskAffinity=".Main">
android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View File

@ -31,6 +31,9 @@ import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.provider.FontRequest; 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;
import androidx.emoji.text.FontRequestEmojiCompatConfig; import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -61,6 +64,7 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager; import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter; import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.ActivityMainBinding; import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.PostViewV2Fragment;
@ -137,11 +141,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
instance = this; instance = this;
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = ActivityMainBinding.inflate(getLayoutInflater());
setupCookie(); setupCookie();
if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
setContentView(binding.getRoot()); setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar; final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
final RootViewDeferringInsetsCallback deferringInsetsCallback = new RootViewDeferringInsetsCallback(
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
ViewCompat.setWindowInsetsAnimationCallback(binding.getRoot(), deferringInsetsCallback);
ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), deferringInsetsCallback);
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
createNotificationChannels(); createNotificationChannels();
try { try {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams(); final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();
@ -335,6 +347,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "onDestroy: ", e); Log.e(TAG, "onDestroy: ", e);
} }
instance = null;
} }
@Override @Override
@ -504,11 +517,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack(); @SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId); setupMenu(backStack.size(), destinationId);
final boolean contains = showBottomViewDestinations.contains(destinationId); final boolean contains = showBottomViewDestinations.contains(destinationId);
binding.getRoot().post(() -> {
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE); binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
if (contains && behavior != null) { if (contains && behavior != null) {
behavior.slideUp(binding.bottomNavView); behavior.slideUp(binding.bottomNavView);
} }
});
// explicitly hide keyboard when we navigate // explicitly hide keyboard when we navigate
final View view = getCurrentFocus(); final View view = getCurrentFocus();
Utils.hideKeyboard(view); Utils.hideKeyboard(view);
@ -651,7 +665,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
if (navController == null) return; if (navController == null) return;
final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle();
bundle.putString("username", "@" + username); bundle.putString("username", "@" + username);
try {
navController.navigate(R.id.action_global_profileFragment, bundle); navController.navigate(R.id.action_global_profileFragment, bundle);
} catch (Exception e) {
Log.e(TAG, "showProfileView: ", e);
}
} }
private void showPostView(@NonNull final IntentModel intentModel) { private void showPostView(@NonNull final IntentModel intentModel) {
@ -664,11 +682,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
alertDialog.show(); alertDialog.show();
new PostFetcher(shortCode, feedModel -> { new PostFetcher(shortCode, feedModel -> {
if (feedModel != null) { if (feedModel != null) {
final PostViewV2Fragment fragment = PostViewV2Fragment if (currentNavControllerLiveData == null) return;
.builder(feedModel) final NavController navController = currentNavControllerLiveData.getValue();
.build(); if (navController == null) return;
fragment.setOnShowListener(dialog -> alertDialog.dismiss()); final Bundle bundle = new Bundle();
fragment.show(getSupportFragmentManager(), "post_view"); bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "showPostView: ", e);
}
return; return;
} }
Toast.makeText(getApplicationContext(), R.string.post_not_found, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.post_not_found, Toast.LENGTH_SHORT).show();
@ -724,11 +747,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
} }
public void setCollapsingView(@NonNull final View view) { public void setCollapsingView(@NonNull final View view) {
try {
binding.collapsingToolbarLayout.addView(view, 0); binding.collapsingToolbarLayout.addView(view, 0);
} catch (Exception e) {
Log.e(TAG, "setCollapsingView: ", e);
}
} }
public void removeCollapsingView(@NonNull final View view) { public void removeCollapsingView(@NonNull final View view) {
try {
binding.collapsingToolbarLayout.removeView(view); binding.collapsingToolbarLayout.removeView(view);
} catch (Exception e) {
Log.e(TAG, "removeCollapsingView: ", e);
}
} }
public void setToolbar(final Toolbar toolbar) { public void setToolbar(final Toolbar toolbar) {

View File

@ -406,6 +406,8 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
void onReactionClick(DirectItem item, int position); void onReactionClick(DirectItem item, int position);
void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback); void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
void onAddReactionListener(DirectItem item);
} }
public interface DirectItemInternalLongClickListener { public interface DirectItemInternalLongClickListener {

View File

@ -23,23 +23,29 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
private List<FeedStoryModel> list; private List<FeedStoryModel> list;
private final Filter filter = new Filter() { private final Filter filter = new Filter() {
@Nullable @NonNull
@Override @Override
protected FilterResults performFiltering(final CharSequence filter) { protected FilterResults performFiltering(final CharSequence filter) {
final boolean isFilterEmpty = TextUtils.isEmpty(filter); final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase();
final String query = isFilterEmpty ? null : filter.toString().toLowerCase(); List<FeedStoryModel> filteredList = list;
if (list != null && query != null) {
for (FeedStoryModel item : list) { filteredList = list.stream()
if (isFilterEmpty) item.setShown(true); .filter(feedStoryModel -> feedStoryModel.getProfileModel()
else item.setShown(item.getProfileModel().getUsername().toLowerCase().contains(query)); .getUsername()
.toLowerCase()
.contains(query))
.collect(Collectors.toList());
} }
return null; final FilterResults filterResults = new FilterResults();
filterResults.count = filteredList != null ? filteredList.size() : 0;
filterResults.values = filteredList;
return filterResults;
} }
@Override @Override
protected void publishResults(final CharSequence constraint, final FilterResults results) { protected void publishResults(final CharSequence constraint, final FilterResults results) {
submitList(list); //noinspection unchecked
notifyDataSetChanged(); submitList((List<FeedStoryModel>) results.values, true);
} }
}; };
@ -65,10 +71,16 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
return filter; return filter;
} }
private void submitList(@Nullable final List<FeedStoryModel> list, final boolean isFiltered) {
if (!isFiltered) {
this.list = list;
}
super.submitList(list);
}
@Override @Override
public void submitList(final List<FeedStoryModel> list) { public void submitList(final List<FeedStoryModel> list) {
super.submitList(list.stream().filter(i -> i.isShown()).collect(Collectors.toList())); submitList(list, false);
this.list = list;
} }
@NonNull @NonNull
@ -82,11 +94,11 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St
@Override @Override
public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) {
final FeedStoryModel model = getItem(position); final FeedStoryModel model = getItem(position);
holder.bind(model, position, listener); holder.bind(model, listener);
} }
public interface OnFeedStoryClickListener { public interface OnFeedStoryClickListener {
void onFeedStoryClick(final FeedStoryModel model, final int position); void onFeedStoryClick(final FeedStoryModel model);
void onProfileClick(final String username); void onProfileClick(final String username);
} }

View File

@ -1,11 +1,17 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.view.View;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import awais.instagrabber.repositories.responses.Media;
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback { public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
@Override @Override
public void onThumbnailLoaded(final int position) {} public void onThumbnailLoaded(final int position) {}
@Override @Override
public void onItemClicked(final int position) {} public void onItemClicked(final int position, final Media media, final View view) {}
@Override @Override
public void onPlayerPlay(final int position) {} public void onPlayerPlay(final int position) {}
@ -15,4 +21,12 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback
@Override @Override
public void onPlayerRelease(final int position) {} public void onPlayerRelease(final int position) {}
@Override
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
@Override
public boolean isInFullScreen() {
return false;
}
} }

View File

@ -1,16 +1,18 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.ListAdapter;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder; import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder; import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder; import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.databinding.ItemSliderPhotoBinding; import awais.instagrabber.databinding.ItemSliderPhotoBinding;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
@ -18,10 +20,8 @@ import awais.instagrabber.repositories.responses.Media;
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> { public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
private final boolean loadVideoOnItemClick; private final boolean loadVideoOnItemClick;
private final SliderCallback sliderCallback; private final SliderCallback sliderCallback;
// private final LayoutExoCustomControlsBinding controlsBinding;
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() { private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
@Override @Override
@ -35,15 +35,11 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
} }
}; };
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener, public SliderItemsAdapter(final boolean loadVideoOnItemClick,
// final LayoutExoCustomControlsBinding controlsBinding,
final boolean loadVideoOnItemClick,
final SliderCallback sliderCallback) { final SliderCallback sliderCallback) {
super(DIFF_CALLBACK); super(DIFF_CALLBACK);
this.onVerticalDragListener = onVerticalDragListener;
this.loadVideoOnItemClick = loadVideoOnItemClick; this.loadVideoOnItemClick = loadVideoOnItemClick;
this.sliderCallback = sliderCallback; this.sliderCallback = sliderCallback;
// this.controlsBinding = controlsBinding;
} }
@NonNull @NonNull
@ -54,12 +50,12 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
switch (mediaItemType) { switch (mediaItemType) {
case MEDIA_TYPE_VIDEO: { case MEDIA_TYPE_VIDEO: {
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false); final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
return new SliderVideoViewHolder(binding, onVerticalDragListener, loadVideoOnItemClick); return new SliderVideoViewHolder(binding, loadVideoOnItemClick);
} }
case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_IMAGE:
default: default:
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false); final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
return new SliderPhotoViewHolder(binding, onVerticalDragListener); return new SliderPhotoViewHolder(binding);
} }
} }
@ -142,12 +138,16 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
public interface SliderCallback { public interface SliderCallback {
void onThumbnailLoaded(int position); void onThumbnailLoaded(int position);
void onItemClicked(int position); void onItemClicked(int position, final Media media, final View view);
void onPlayerPlay(int position); void onPlayerPlay(int position);
void onPlayerPause(int position); void onPlayerPause(int position);
void onPlayerRelease(int position); void onPlayerRelease(int position);
void onFullScreenModeChanged(boolean isFullScreen, final StyledPlayerView playerView);
boolean isInFullScreen();
} }
} }

View File

@ -2,7 +2,6 @@ package awais.instagrabber.adapters.viewholder;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.net.Uri; import android.net.Uri;
import android.view.GestureDetector;
import android.view.MotionEvent; import android.view.MotionEvent;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -14,8 +13,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.adapters.SliderItemsAdapter; import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.drawee.AnimatedZoomableController; import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
import awais.instagrabber.customviews.drawee.DoubleTapGestureListener;
import awais.instagrabber.databinding.ItemSliderPhotoBinding; import awais.instagrabber.databinding.ItemSliderPhotoBinding;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
@ -24,13 +23,10 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
private static final String TAG = "FeedSliderPhotoViewHolder"; private static final String TAG = "FeedSliderPhotoViewHolder";
private final ItemSliderPhotoBinding binding; private final ItemSliderPhotoBinding binding;
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding, public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding) {
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
super(binding.getRoot()); super(binding.getRoot());
this.binding = binding; this.binding = binding;
this.onVerticalDragListener = onVerticalDragListener;
} }
public void bind(@NonNull final Media model, public void bind(@NonNull final Media model,
@ -62,74 +58,19 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
}) })
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model))) .setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
.build()); .build());
// binding.getRoot().setOnClickListener(v -> { final DoubleTapGestureListener tapListener = new DoubleTapGestureListener(binding.getRoot()) {
// if (sliderCallback != null) {
// sliderCallback.onItemClicked(position);
// }
// });
binding.getRoot().setTapListener(new GestureDetector.SimpleOnGestureListener() {
@Override @Override
public boolean onSingleTapUp(final MotionEvent e) { public boolean onSingleTapConfirmed(final MotionEvent e) {
if (sliderCallback != null) { if (sliderCallback != null) {
sliderCallback.onItemClicked(position); sliderCallback.onItemClicked(position, model, binding.getRoot());
return true;
} }
return false; return super.onSingleTapConfirmed(e);
} }
}); };
binding.getRoot().setTapListener(tapListener);
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance(); final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
zoomableController.setMaxScaleFactor(3f); zoomableController.setMaxScaleFactor(3f);
binding.getRoot().setZoomableController(zoomableController); binding.getRoot().setZoomableController(zoomableController);
if (onVerticalDragListener != null) { binding.getRoot().setZoomingEnabled(true);
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
} }
}
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
// final int spanWidth = deviceWidth / spanCount;
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
// if (animate) {
// Animation animation = AnimationUtils.expand(
// binding.imageViewer,
// layoutParams.width,
// layoutParams.height,
// width,
// height,
// new Animation.AnimationListener() {
// @Override
// public void onAnimationStart(final Animation animation) {
// showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationEnd(final Animation animation) {
// // showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationRepeat(final Animation animation) {
//
// }
// });
// binding.imageViewer.startAnimation(animation);
// } else {
// layoutParams.width = width;
// layoutParams.height = height;
// binding.imageViewer.requestLayout();
// }
// }
//
// private void showOrHideDetails(final int spanCount) {
// if (spanCount == 1) {
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
// } else {
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
// }
// }
} }

View File

@ -7,10 +7,11 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import java.util.List; import java.util.List;
import awais.instagrabber.adapters.SliderItemsAdapter; import awais.instagrabber.adapters.SliderItemsAdapter;
import awais.instagrabber.customviews.VerticalDragHelper;
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter; import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
import awais.instagrabber.customviews.VideoPlayerViewHelper; import awais.instagrabber.customviews.VideoPlayerViewHelper;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
@ -27,40 +28,23 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
private static final String TAG = "SliderVideoViewHolder"; private static final String TAG = "SliderVideoViewHolder";
private final LayoutVideoPlayerWithThumbnailBinding binding; private final LayoutVideoPlayerWithThumbnailBinding binding;
// private final LayoutExoCustomControlsBinding controlsBinding;
private final boolean loadVideoOnItemClick; private final boolean loadVideoOnItemClick;
private final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
private VideoPlayerViewHelper videoPlayerViewHelper;
@SuppressLint("ClickableViewAccessibility")
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
final boolean loadVideoOnItemClick) {
super(binding.getRoot());
this.binding = binding;
this.loadVideoOnItemClick = loadVideoOnItemClick;
final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override @Override
public boolean onSingleTapConfirmed(final MotionEvent e) { public boolean onSingleTapConfirmed(final MotionEvent e) {
binding.playerView.performClick(); binding.playerView.performClick();
return true; return true;
} }
}; };
private VideoPlayerViewHelper videoPlayerViewHelper;
@SuppressLint("ClickableViewAccessibility")
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
// final LayoutExoCustomControlsBinding controlsBinding,
final boolean loadVideoOnItemClick) {
super(binding.getRoot());
this.binding = binding;
// this.controlsBinding = controlsBinding;
this.loadVideoOnItemClick = loadVideoOnItemClick;
// if (onVerticalDragListener != null) {
// final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent);
// final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView);
// thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
// playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
// binding.thumbnailParent.setOnTouchListener((v, event) -> {
// final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event);
// if (onDragTouch) {
// return true;
// }
// return thumbnailVerticalDragHelper.onGestureTouchEvent(event);
// });
// }
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener); final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
binding.playerView.setOnTouchListener((v, event) -> { binding.playerView.setOnTouchListener((v, event) -> {
gestureDetector.onTouchEvent(event); gestureDetector.onTouchEvent(event);
@ -77,7 +61,7 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
@Override @Override
public void onThumbnailClick() { public void onThumbnailClick() {
if (sliderCallback != null) { if (sliderCallback != null) {
sliderCallback.onItemClicked(position); sliderCallback.onItemClicked(position, media, binding.getRoot());
} }
} }
@ -120,6 +104,21 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
sliderCallback.onPlayerRelease(position); sliderCallback.onPlayerRelease(position);
} }
} }
@Override
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {
if (sliderCallback != null) {
sliderCallback.onFullScreenModeChanged(isFullScreen, playerView);
}
}
@Override
public boolean isInFullScreen() {
if (sliderCallback != null) {
return sliderCallback.isInFullScreen();
}
return false;
}
}; };
final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight(); final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight();
String videoUrl = null; String videoUrl = null;
@ -138,16 +137,10 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
aspectRatio, aspectRatio,
ResponseBodyUtils.getThumbUrl(media), ResponseBodyUtils.getThumbUrl(media),
loadVideoOnItemClick, loadVideoOnItemClick,
// controlsBinding,
videoPlayerCallback); videoPlayerCallback);
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
// final float newVol = videoPlayerViewHelper.toggleMute();
// setMuteIcon(newVol);
// Utils.sessionVolumeFull = newVol == 1f;
// });
binding.playerView.setOnClickListener(v -> { binding.playerView.setOnClickListener(v -> {
if (sliderCallback != null) { if (sliderCallback != null) {
sliderCallback.onItemClicked(position); sliderCallback.onItemClicked(position, media, binding.getRoot());
} }
}); });
} }
@ -161,62 +154,4 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
if (videoPlayerViewHelper == null) return; if (videoPlayerViewHelper == null) return;
videoPlayerViewHelper.releasePlayer(); videoPlayerViewHelper.releasePlayer();
} }
// public void resetPlayerTimeline() {
// if (videoPlayerViewHelper == null) return;
// videoPlayerViewHelper.resetTimeline();
// }
//
// public void removeCallbacks() {
// if (videoPlayerViewHelper == null) return;
// videoPlayerViewHelper.removeCallbacks();
// }
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
// final int deviceWidth = Utils.displayMetrics.widthPixels;
// final int spanWidth = deviceWidth / spanCount;
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
// if (animate) {
// Animation animation = AnimationUtils.expand(
// binding.imageViewer,
// layoutParams.width,
// layoutParams.height,
// width,
// height,
// new Animation.AnimationListener() {
// @Override
// public void onAnimationStart(final Animation animation) {
// showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationEnd(final Animation animation) {
// // showOrHideDetails(spanCount);
// }
//
// @Override
// public void onAnimationRepeat(final Animation animation) {
//
// }
// });
// binding.imageViewer.startAnimation(animation);
// } else {
// layoutParams.width = width;
// layoutParams.height = height;
// binding.imageViewer.requestLayout();
// }
// }
//
// private void showOrHideDetails(final int spanCount) {
// if (spanCount == 1) {
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
// } else {
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
// }
// }
} }

View File

@ -20,7 +20,6 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
} }
public void bind(final FeedStoryModel model, public void bind(final FeedStoryModel model,
final int position,
final OnFeedStoryClickListener notificationClickListener) { final OnFeedStoryClickListener notificationClickListener) {
if (model == null) return; if (model == null) return;
@ -53,7 +52,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder {
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return; if (notificationClickListener == null) return;
notificationClickListener.onFeedStoryClick(model, position); notificationClickListener.onFeedStoryClick(model);
}); });
} }

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
@ -23,6 +22,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia; import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -48,7 +48,7 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight(); final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
if (fixedHeight == null) return; if (fixedHeight == null) return;
final String url = fixedHeight.getWebp(); final String url = fixedHeight.getWebp();
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight( final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
fixedHeight.getHeight(), fixedHeight.getHeight(),
fixedHeight.getWidth(), fixedHeight.getWidth(),
mediaImageMaxHeight, mediaImageMaxHeight,
@ -56,8 +56,8 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
); );
binding.ivAnimatedMessage.setVisibility(View.VISIBLE); binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
final int width = widthHeight.first != null ? widthHeight.first : 0; final int width = widthHeight.first;
final int height = widthHeight.second != null ? widthHeight.second : 0; final int height = widthHeight.second;
layoutParams.width = width; layoutParams.width = width;
layoutParams.height = height; layoutParams.height = height;
binding.ivAnimatedMessage.requestLayout(); binding.ivAnimatedMessage.requestLayout();

View File

@ -6,7 +6,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.drawable.ScalingUtils;
@ -31,6 +30,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemClip; import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare; import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
@ -103,15 +103,15 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP) .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.setRoundingParams(roundingParams) .setRoundingParams(roundingParams)
.build()); .build());
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight( final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
media.getOriginalHeight(), media.getOriginalHeight(),
media.getOriginalWidth(), media.getOriginalWidth(),
mediaImageMaxHeight, mediaImageMaxHeight,
mediaImageMaxWidth mediaImageMaxWidth
); );
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0; layoutParams.width = widthHeight.first;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; layoutParams.height = widthHeight.second;
binding.mediaPreview.requestLayout(); binding.mediaPreview.requestLayout();
binding.mediaPreview.setTag(url); binding.mediaPreview.setTag(url);
binding.mediaPreview.setImageURI(url); binding.mediaPreview.setImageURI(url);

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
@ -14,11 +13,11 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmBaseBinding;
import awais.instagrabber.databinding.LayoutDmMediaBinding; import awais.instagrabber.databinding.LayoutDmMediaBinding;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
@ -53,16 +52,16 @@ public class DirectItemMediaViewHolder extends DirectItemViewHolder {
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
? View.VISIBLE ? View.VISIBLE
: View.GONE); : View.GONE);
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight( final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
media.getOriginalHeight(), media.getOriginalHeight(),
media.getOriginalWidth(), media.getOriginalWidth(),
mediaImageMaxHeight, mediaImageMaxHeight,
mediaImageMaxWidth mediaImageMaxWidth
); );
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
final int width = widthHeight.first != null ? widthHeight.first : 0; final int width = widthHeight.first;
layoutParams.width = width; layoutParams.width = width;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; layoutParams.height = widthHeight.second;
binding.mediaPreview.requestLayout(); binding.mediaPreview.requestLayout();
binding.bgTime.getLayoutParams().width = width; binding.bgTime.getLayoutParams().width = width;
binding.bgTime.requestLayout(); binding.bgTime.requestLayout();

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
@ -21,6 +20,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia; import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
@ -170,15 +170,15 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
? View.VISIBLE ? View.VISIBLE
: View.GONE); : View.GONE);
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight( final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
media.getOriginalHeight(), media.getOriginalHeight(),
media.getOriginalWidth(), media.getOriginalWidth(),
mediaImageMaxHeight, mediaImageMaxHeight,
maxWidth maxWidth
); );
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0; layoutParams.width = widthHeight.first;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; layoutParams.height = widthHeight.second;
binding.preview.requestLayout(); binding.preview.requestLayout();
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media); final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
binding.preview.setImageURI(thumbUrl); binding.preview.setImageURI(thumbUrl);

View File

@ -5,7 +5,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.drawable.ScalingUtils;
@ -17,12 +16,12 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmBaseBinding;
import awais.instagrabber.databinding.LayoutDmStoryShareBinding; import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare; import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
@ -76,15 +75,15 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
.setRoundingParams(roundingParams) .setRoundingParams(roundingParams)
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP) .setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.build()); .build());
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight( final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
storyShareMedia.getOriginalHeight(), storyShareMedia.getOriginalHeight(),
storyShareMedia.getOriginalWidth(), storyShareMedia.getOriginalWidth(),
mediaImageMaxHeight, mediaImageMaxHeight,
mediaImageMaxWidth mediaImageMaxWidth
); );
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0; layoutParams.width = widthHeight.first;
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0; layoutParams.height = widthHeight.second;
binding.ivMediaPreview.requestLayout(); binding.ivMediaPreview.requestLayout();
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia); final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
binding.ivMediaPreview.setImageURI(thumbUrl); binding.ivMediaPreview.setImageURI(thumbUrl);

View File

@ -551,6 +551,10 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
menu.setOnDismissListener(() -> setSelected(false)); menu.setOnDismissListener(() -> setSelected(false));
menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji)); menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji));
menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb)); menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb));
menu.setOnAddReactionListener(() -> {
menu.dismiss();
itemView.postDelayed(() -> callback.onAddReactionListener(item), 300);
});
menu.show(itemView, location); menu.show(itemView, location);
} }

View File

@ -4,7 +4,6 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
@ -16,6 +15,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemXma; import awais.instagrabber.repositories.responses.directmessages.DirectItemXma;
import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.utils.NullSafePair;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
public class DirectItemXmaViewHolder extends DirectItemViewHolder { public class DirectItemXmaViewHolder extends DirectItemViewHolder {
@ -43,7 +43,7 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
} }
final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo; final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo;
final String url = urlInfo.getUrl(); final String url = urlInfo.getUrl();
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight( final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
urlInfo.getHeight(), urlInfo.getHeight(),
urlInfo.getWidth(), urlInfo.getWidth(),
mediaImageMaxHeight, mediaImageMaxHeight,
@ -51,8 +51,8 @@ public class DirectItemXmaViewHolder extends DirectItemViewHolder {
); );
binding.ivAnimatedMessage.setVisibility(View.VISIBLE); binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
final int width = widthHeight.first != null ? widthHeight.first : 0; final int width = widthHeight.first;
final int height = widthHeight.second != null ? widthHeight.second : 0; final int height = widthHeight.second;
layoutParams.width = width; layoutParams.width = width;
layoutParams.height = height; layoutParams.height = height;
binding.ivAnimatedMessage.requestLayout(); binding.ivAnimatedMessage.requestLayout();

View File

@ -45,9 +45,9 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
final String text = "1/" + sliderItemLen; final String text = "1/" + sliderItemLen;
binding.mediaCounter.setText(text); binding.mediaCounter.setText(text);
binding.mediaList.setOffscreenPageLimit(1); binding.mediaList.setOffscreenPageLimit(1);
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, false, new SliderCallbackAdapter() { final SliderItemsAdapter adapter = new SliderItemsAdapter(false, new SliderCallbackAdapter() {
@Override @Override
public void onItemClicked(final int position) { public void onItemClicked(final int position, final Media media, final View view) {
feedItemCallback.onSliderClick(feedModel, position); feedItemCallback.onSliderClick(feedModel, position);
} }
}); });

View File

@ -105,12 +105,15 @@ public class ChatMessageLayout extends FrameLayout {
viewPartMainLastLineWidth = viewPartMainLineCount > 0 viewPartMainLastLineWidth = viewPartMainLineCount > 0
? ((TextView) firstChild).getLayout().getLineWidth(viewPartMainLineCount - 1) ? ((TextView) firstChild).getLayout().getLineWidth(viewPartMainLineCount - 1)
: 0; : 0;
// also include start left padding
viewPartMainLastLineWidth += firstChild.getPaddingLeft();
} }
if (viewPartMainLineCount > 1 && !(viewPartMainLastLineWidth + viewPartInfoWidth > viewPartMain.getMeasuredWidth())) { final float lastLineWithInfoWidth = viewPartMainLastLineWidth + viewPartInfoWidth;
if (viewPartMainLineCount > 1 && lastLineWithInfoWidth <= viewPartMain.getMeasuredWidth()) {
widthSize += viewPartMainWidth; widthSize += viewPartMainWidth;
heightSize += viewPartMainHeight; heightSize += viewPartMainHeight;
} else if (viewPartMainLineCount > 1 && (viewPartMainLastLineWidth + viewPartInfoWidth > availableWidth)) { } else if (viewPartMainLineCount > 1 && (lastLineWithInfoWidth > availableWidth)) {
widthSize += viewPartMainWidth; widthSize += viewPartMainWidth;
heightSize += viewPartMainHeight + viewPartInfoHeight; heightSize += viewPartMainHeight + viewPartInfoHeight;
} else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartInfoWidth > availableWidth)) { } else if (viewPartMainLineCount == 1 && (viewPartMainWidth + viewPartInfoWidth > availableWidth)) {
@ -120,6 +123,16 @@ public class ChatMessageLayout extends FrameLayout {
heightSize += viewPartMainHeight; heightSize += viewPartMainHeight;
widthSize += viewPartMainWidth + viewPartInfoWidth; widthSize += viewPartMainWidth + viewPartInfoWidth;
} }
// if (isInEditMode()) {
// TextView wDebugView = (TextView) ((ViewGroup) this.getParent()).findViewWithTag("debug");
// wDebugView.setText(lastLineWithInfoWidth
// + "\n" + availableWidth
// + "\n" + viewPartMain.getMeasuredWidth()
// + "\n" + (lastLineWithInfoWidth <= viewPartMain.getMeasuredWidth())
// + "\n" + (lastLineWithInfoWidth > availableWidth)
// + "\n" + (viewPartMainWidth + viewPartInfoWidth > availableWidth));
// }
} }
setMeasuredDimension(widthSize, heightSize); setMeasuredDimension(widthSize, heightSize);
super.onMeasure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY)); super.onMeasure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY));

View File

@ -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));
return;
}
setText(String.valueOf(number));
if (autoToggleToAbbreviation) {
getHandler().postDelayed(() -> setShowAbbreviation(true), autoToggleTimeoutMs);
}
});
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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()));
}
}

View File

@ -3,6 +3,8 @@ package awais.instagrabber.customviews;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -25,6 +27,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function;
import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
@ -60,14 +63,17 @@ public class PostsRecyclerView extends RecyclerView {
private FeedAdapterV2.FeedItemCallback feedItemCallback; private FeedAdapterV2.FeedItemCallback feedItemCallback;
private boolean shouldScrollToTop; private boolean shouldScrollToTop;
private FeedAdapterV2.SelectionModeCallback selectionModeCallback; private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
private Function<ViewGroup, View> headerViewCreator;
private Function<View, Void> headerBinder;
private boolean refresh = true;
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>(); private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
private final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() { private final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
@Override @Override
public void onResult(final List<Media> result) { public void onResult(final List<Media> result) {
final int currentPage = lazyLoader.getCurrentPage(); if (refresh) {
if (currentPage == 0) { refresh = false;
mediaViewModel.getList().postValue(result); mediaViewModel.getList().postValue(result);
shouldScrollToTop = true; shouldScrollToTop = true;
dispatchFetchStatus(); dispatchFetchStatus();
@ -198,21 +204,19 @@ public class PostsRecyclerView extends RecyclerView {
Log.e(TAG, "initSelf: ", e); Log.e(TAG, "initSelf: ", e);
} }
if (mediaViewModel == null) return; if (mediaViewModel == null) return;
mediaViewModel.getList().observe(lifeCycleOwner, list -> { mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
if (list.size() <= 0) return;
feedAdapter.submitList(list, () -> {
// postDelayed(this::fetchMoreIfPossible, 1000); // postDelayed(this::fetchMoreIfPossible, 1000);
if (!shouldScrollToTop) return; if (!shouldScrollToTop) return;
smoothScrollToPosition(0);
shouldScrollToTop = false; shouldScrollToTop = false;
}); post(() -> smoothScrollToPosition(0));
}); }));
postFetcher = new PostFetcher(postFetchService, fetchListener); postFetcher = new PostFetcher(postFetchService, fetchListener);
if (layoutPreferences.getHasGap()) { if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration); addItemDecoration(gridSpacingItemDecoration);
} }
setHasFixedSize(true); setHasFixedSize(true);
setNestedScrollingEnabled(true); setNestedScrollingEnabled(true);
setItemAnimator(null);
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> { lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
if (postFetcher.hasMore()) { if (postFetcher.hasMore()) {
postFetcher.fetch(); postFetcher.fetch();
@ -316,11 +320,12 @@ public class PostsRecyclerView extends RecyclerView {
} }
public void refresh() { public void refresh() {
refresh = true;
if (lazyLoader != null) { if (lazyLoader != null) {
lazyLoader.resetState(); lazyLoader.resetState();
} }
if (postFetcher != null) { if (postFetcher != null) {
mediaViewModel.getList().postValue(Collections.emptyList()); // mediaViewModel.getList().postValue(Collections.emptyList());
postFetcher.reset(); postFetcher.reset();
postFetcher.fetch(); postFetcher.fetch();
} }

View File

@ -9,20 +9,20 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewPropertyAnimator; import android.view.ViewPropertyAnimator;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatTextView; import androidx.appcompat.widget.AppCompatTextView;
import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.ViewUtils; import awais.instagrabber.utils.ViewUtils;
public class Tooltip extends AppCompatTextView { public class Tooltip extends AppCompatTextView {
private View anchor; private View anchor;
private ViewPropertyAnimator animator; private ViewPropertyAnimator animator;
private boolean showing; private boolean showing;
private final AppExecutors appExecutors; private final AppExecutors appExecutors = AppExecutors.getInstance();
private final Runnable dismissRunnable = () -> { private final Runnable dismissRunnable = () -> {
animator = animate().alpha(0).setListener(new AnimatorListenerAdapter() { animator = animate().alpha(0).setListener(new AnimatorListenerAdapter() {
@Override @Override
@ -33,7 +33,7 @@ public class Tooltip extends AppCompatTextView {
animator.start(); animator.start();
}; };
public Tooltip(Context context, ViewGroup parentView, int backgroundColor, int textColor) { public Tooltip(@NonNull Context context, @NonNull ViewGroup parentView, int backgroundColor, int textColor) {
super(context); super(context);
setBackgroundDrawable(ViewUtils.createRoundRectDrawable(Utils.convertDpToPx(3), backgroundColor)); setBackgroundDrawable(ViewUtils.createRoundRectDrawable(Utils.convertDpToPx(3), backgroundColor));
setTextColor(textColor); setTextColor(textColor);
@ -43,7 +43,6 @@ public class Tooltip extends AppCompatTextView {
parentView.addView(this, ViewUtils.createFrame( parentView.addView(this, ViewUtils.createFrame(
ViewUtils.WRAP_CONTENT, ViewUtils.WRAP_CONTENT, Gravity.START | Gravity.TOP, 5, 0, 5, 3)); ViewUtils.WRAP_CONTENT, ViewUtils.WRAP_CONTENT, Gravity.START | Gravity.TOP, 5, 0, 5, 3));
setVisibility(GONE); setVisibility(GONE);
appExecutors = AppExecutors.getInstance();
} }
@Override @Override

View File

@ -1,5 +1,7 @@
package awais.instagrabber.customviews; package awais.instagrabber.customviews;
import com.google.android.exoplayer2.ui.StyledPlayerView;
public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPlayerCallback { public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPlayerCallback {
@Override @Override
public void onThumbnailLoaded() {} public void onThumbnailLoaded() {}
@ -18,4 +20,12 @@ public class VideoPlayerCallbackAdapter implements VideoPlayerViewHelper.VideoPl
@Override @Override
public void onRelease() {} public void onRelease() {}
@Override
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
@Override
public boolean isInFullScreen() {
return false;
}
} }

View File

@ -1,14 +1,19 @@
package awais.instagrabber.customviews; package awais.instagrabber.customviews;
import android.content.Context; import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.net.Uri; import android.net.Uri;
import android.os.Looper; import android.os.Looper;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.PopupMenu; import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
@ -22,16 +27,23 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.audio.AudioListener; import com.google.android.exoplayer2.audio.AudioListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
import com.google.android.exoplayer2.ui.StyledPlayerControlView;
import com.google.android.exoplayer2.ui.StyledPlayerView;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import com.google.android.material.button.MaterialButton;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding; import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
import awais.instagrabber.utils.Utils;
public class VideoPlayerViewHelper implements Player.EventListener { public class VideoPlayerViewHelper implements Player.EventListener {
private static final String TAG = "VideoPlayerViewHelper"; private static final String TAG = VideoPlayerViewHelper.class.getSimpleName();
// private static final long INITIAL_DELAY = 0;
// private static final long RECURRING_DELAY = 60;
private final Context context; private final Context context;
private final LayoutVideoPlayerWithThumbnailBinding binding; private final LayoutVideoPlayerWithThumbnailBinding binding;
@ -39,74 +51,20 @@ public class VideoPlayerViewHelper implements Player.EventListener {
private final float thumbnailAspectRatio; private final float thumbnailAspectRatio;
private final String thumbnailUrl; private final String thumbnailUrl;
private final boolean loadPlayerOnClick; private final boolean loadPlayerOnClick;
// private final LayoutExoCustomControlsBinding controlsBinding;
private final VideoPlayerCallback videoPlayerCallback; private final VideoPlayerCallback videoPlayerCallback;
private final String videoUrl; private final String videoUrl;
private final DefaultDataSourceFactory dataSourceFactory; private final DefaultDataSourceFactory dataSourceFactory;
private SimpleExoPlayer player; private SimpleExoPlayer player;
private PopupMenu speedPopup; private AppCompatImageButton mute;
// private PositionCheckRunnable positionChecker;
// private Handler positionUpdateHandler;
// private final Player.EventListener listener = new Player.EventListener() {
// @Override
// public void onPlaybackStateChanged(final int state) {
// // switch (state) {
// // case Player.STATE_BUFFERING:
// // case STATE_IDLE:
// // case STATE_ENDED:
// // positionUpdateHandler.removeCallbacks(positionChecker);
// // return;
// // case STATE_READY:
// // setupTimeline();
// // positionUpdateHandler.postDelayed(positionChecker, INITIAL_DELAY);
// // break;
// // }
// }
//
// @Override
// public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
// // updatePlayPauseDrawable(playWhenReady);
// // if (positionUpdateHandler == null || positionChecker == null) return;
// // if (playWhenReady) {
// // positionUpdateHandler.removeCallbacks(positionChecker);
// // positionUpdateHandler.postDelayed(positionChecker, INITIAL_DELAY);
// // }
// }
// };
private final AudioListener audioListener = new AudioListener() { private final AudioListener audioListener = new AudioListener() {
@Override @Override
public void onVolumeChanged(final float volume) { public void onVolumeChanged(final float volume) {
updateMuteIcon(volume); updateMuteIcon(volume);
} }
}; };
// private final Slider.OnChangeListener onChangeListener = (slider, value, fromUser) -> {
// if (!fromUser) return;
// long actualValue = (long) value;
// if (actualValue < 0) {
// actualValue = 0;
// } else if (actualValue > player.getDuration()) {
// actualValue = player.getDuration();
// }
// player.seekTo(actualValue);
// };
// private final View.OnClickListener onClickListener = v -> player.setPlayWhenReady(!player.getPlayWhenReady());
// // private final LabelFormatter labelFormatter = value -> TextUtils.millisToTimeString((long) value);
private final View.OnClickListener muteOnClickListener = v -> toggleMute(); private final View.OnClickListener muteOnClickListener = v -> toggleMute();
private MaterialButton mute; private Object layoutManager;
// private final View.OnClickListener rewOnClickListener = v -> {
// final long positionMs = player.getCurrentPosition() - 5000;
// player.seekTo(positionMs < 0 ? 0 : positionMs);
// };
// private final View.OnClickListener ffOnClickListener = v -> {
// long positionMs = player.getCurrentPosition() + 5000;
// long duration = player.getDuration();
// if (duration == TIME_UNSET) {
// duration = 0;
// }
// player.seekTo(Math.min(positionMs, duration));
// };
// private final View.OnClickListener showMenu = this::showMenu;
public VideoPlayerViewHelper(@NonNull final Context context, public VideoPlayerViewHelper(@NonNull final Context context,
@NonNull final LayoutVideoPlayerWithThumbnailBinding binding, @NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
@ -115,7 +73,6 @@ public class VideoPlayerViewHelper implements Player.EventListener {
final float thumbnailAspectRatio, final float thumbnailAspectRatio,
final String thumbnailUrl, final String thumbnailUrl,
final boolean loadPlayerOnClick, final boolean loadPlayerOnClick,
// final LayoutExoCustomControlsBinding controlsBinding,
final VideoPlayerCallback videoPlayerCallback) { final VideoPlayerCallback videoPlayerCallback) {
this.context = context; this.context = context;
this.binding = binding; this.binding = binding;
@ -123,7 +80,6 @@ public class VideoPlayerViewHelper implements Player.EventListener {
this.thumbnailAspectRatio = thumbnailAspectRatio; this.thumbnailAspectRatio = thumbnailAspectRatio;
this.thumbnailUrl = thumbnailUrl; this.thumbnailUrl = thumbnailUrl;
this.loadPlayerOnClick = loadPlayerOnClick; this.loadPlayerOnClick = loadPlayerOnClick;
// this.controlsBinding = controlsBinding;
this.videoPlayerCallback = videoPlayerCallback; this.videoPlayerCallback = videoPlayerCallback;
this.videoUrl = videoUrl; this.videoUrl = videoUrl;
this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram"); this.dataSourceFactory = new DefaultDataSourceFactory(binding.getRoot().getContext(), "instagram");
@ -140,7 +96,6 @@ public class VideoPlayerViewHelper implements Player.EventListener {
} }
}); });
setThumbnail(); setThumbnail();
// setupControls();
} }
private void setThumbnail() { private void setThumbnail() {
@ -149,11 +104,11 @@ public class VideoPlayerViewHelper implements Player.EventListener {
if (thumbnailUrl != null) { if (thumbnailUrl != null) {
thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)).build(); thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)).build();
} }
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder() final PipelineDraweeControllerBuilder builder = Fresco
.newDraweeControllerBuilder()
.setControllerListener(new BaseControllerListener<ImageInfo>() { .setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override @Override
public void onFailure(final String id, public void onFailure(final String id, final Throwable throwable) {
final Throwable throwable) {
if (videoPlayerCallback != null) { if (videoPlayerCallback != null) {
videoPlayerCallback.onThumbnailLoaded(); videoPlayerCallback.onThumbnailLoaded();
} }
@ -176,8 +131,8 @@ public class VideoPlayerViewHelper implements Player.EventListener {
private void loadPlayer() { private void loadPlayer() {
if (videoUrl == null) return; if (videoUrl == null) return;
if (binding.root.getDisplayedChild() == 0) { if (binding.getRoot().getDisplayedChild() == 0) {
binding.root.showNext(); binding.getRoot().showNext();
} }
if (videoPlayerCallback != null) { if (videoPlayerCallback != null) {
videoPlayerCallback.onPlayerViewLoaded(); videoPlayerCallback.onPlayerViewLoaded();
@ -186,14 +141,13 @@ public class VideoPlayerViewHelper implements Player.EventListener {
if (player != null) { if (player != null) {
player.release(); player.release();
} }
final ViewGroup.LayoutParams playerViewLayoutParams = binding.playerView.getLayoutParams();
if (playerViewLayoutParams.height > Utils.displayMetrics.heightPixels * 0.8) {
playerViewLayoutParams.height = (int) (Utils.displayMetrics.heightPixels * 0.8);
}
player = new SimpleExoPlayer.Builder(context) player = new SimpleExoPlayer.Builder(context)
.setLooper(Looper.getMainLooper()) .setLooper(Looper.getMainLooper())
.build(); .build();
// positionUpdateHandler = new Handler();
// positionChecker = new PositionCheckRunnable(positionUpdateHandler,
// player,
// controlsBinding.timeline,
// controlsBinding.fromTime);
player.addListener(this); player.addListener(this);
player.addAudioListener(audioListener); player.addAudioListener(audioListener);
player.setVolume(initialVolume); player.setVolume(initialVolume);
@ -203,135 +157,118 @@ public class VideoPlayerViewHelper implements Player.EventListener {
final MediaItem mediaItem = MediaItem.fromUri(videoUrl); final MediaItem mediaItem = MediaItem.fromUri(videoUrl);
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem); final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
player.setMediaSource(mediaSource); player.setMediaSource(mediaSource);
// setupControls();
player.prepare(); player.prepare();
binding.playerView.setPlayer(player); binding.playerView.setPlayer(player);
binding.playerView.setShowFastForwardButton(false); binding.playerView.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
binding.playerView.setShowRewindButton(false); binding.playerView.setShowNextButton(false);
// binding.controls.setPlayer(player); binding.playerView.setShowPreviousButton(false);
mute = binding.playerView.findViewById(R.id.mute); binding.playerView.setControllerOnFullScreenModeChangedListener(isFullScreen -> {
// mute = binding.controls.findViewById(R.id.mute); if (videoPlayerCallback == null) return;
if (mute != null) { videoPlayerCallback.onFullScreenModeChanged(isFullScreen, binding.playerView);
mute.setOnClickListener(muteOnClickListener); });
setupControllerView();
} }
private void setupControllerView() {
try {
final StyledPlayerControlView controllerView = getStyledPlayerControlView();
if (controllerView == null) return;
layoutManager = setControlViewLayoutManager(controllerView);
if (videoPlayerCallback != null && videoPlayerCallback.isInFullScreen()) {
setControllerViewToFullScreenMode(controllerView);
}
final ViewGroup exoBasicControls = controllerView.findViewById(R.id.exo_basic_controls);
if (exoBasicControls == null) return;
mute = new AppCompatImageButton(context);
final Resources resources = context.getResources();
if (resources == null) return;
final int width = resources.getDimensionPixelSize(R.dimen.exo_small_icon_width);
final int height = resources.getDimensionPixelSize(R.dimen.exo_small_icon_height);
final int margin = resources.getDimensionPixelSize(R.dimen.exo_small_icon_horizontal_margin);
final int paddingHorizontal = resources.getDimensionPixelSize(R.dimen.exo_small_icon_padding_horizontal);
final int paddingVertical = resources.getDimensionPixelSize(R.dimen.exo_small_icon_padding_vertical);
final ViewGroup.MarginLayoutParams layoutParams = new ViewGroup.MarginLayoutParams(width, height);
layoutParams.setMargins(margin, 0, margin, 0);
mute.setLayoutParams(layoutParams);
mute.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
mute.setScaleType(ImageView.ScaleType.FIT_XY);
mute.setBackgroundResource(Utils.getAttrResId(context, android.R.attr.selectableItemBackground));
mute.setImageTintList(ColorStateList.valueOf(resources.getColor(R.color.white)));
updateMuteIcon(player.getVolume()); updateMuteIcon(player.getVolume());
exoBasicControls.addView(mute, 0);
mute.setOnClickListener(muteOnClickListener);
} catch (Exception e) {
Log.e(TAG, "loadPlayer: ", e);
}
} }
// private void setupControls() { @Nullable
// if (controlsBinding == null) return; private Object setControlViewLayoutManager(@NonNull final StyledPlayerControlView controllerView)
// // binding.playerView.setUseController(false); throws NoSuchFieldException, IllegalAccessException {
// if (player == null) { final Field controlViewLayoutManagerField = controllerView.getClass().getDeclaredField("controlViewLayoutManager");
// // enableControls(false); controlViewLayoutManagerField.setAccessible(true);
// // controlsBinding.playPause.setEnabled(true); return controlViewLayoutManagerField.get(controllerView);
// // controlsBinding.playPause.setOnClickListener(new NoPlayerPlayPauseClickListener(binding.thumbnailParent)); }
// return;
// }
// // enableControls(true);
// // updatePlayPauseDrawable(player.getPlayWhenReady());
// // updateMuteIcon(player.getVolume());
// player.addListener(listener);
// // player.addAudioListener(audioListener);
// // controlsBinding.timeline.addOnChangeListener(onChangeListener);
// // controlsBinding.timeline.setLabelFormatter(labelFormatter);
// // controlsBinding.playPause.setOnClickListener(onClickListener);
// // controlsBinding.mute.setOnClickListener(muteOnClickListener);
// // controlsBinding.rewWithAmount.setOnClickListener(rewOnClickListener);
// // controlsBinding.ffWithAmount.setOnClickListener(ffOnClickListener);
// // controlsBinding.speed.setOnClickListener(showMenu);
// }
// private void setupTimeline() { private void setControllerViewToFullScreenMode(@NonNull final StyledPlayerControlView controllerView)
// final long duration = player.getDuration(); throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
// controlsBinding.timeline.setEnabled(true); // Exoplayer doesn't expose the fullscreen state, so using reflection
// controlsBinding.timeline.setValueFrom(0); final Field fullScreenButtonField = controllerView.getClass().getDeclaredField("fullScreenButton");
// controlsBinding.timeline.setValueTo(duration); fullScreenButtonField.setAccessible(true);
// controlsBinding.fromTime.setText(TextUtils.millisToTimeString(0)); final ImageView fullScreenButton = (ImageView) fullScreenButtonField.get(controllerView);
// controlsBinding.toTime.setText(TextUtils.millisToTimeString(duration)); final Field isFullScreen = controllerView.getClass().getDeclaredField("isFullScreen");
// } isFullScreen.setAccessible(true);
isFullScreen.set(controllerView, true);
final Method updateFullScreenButtonForState = controllerView
.getClass()
.getDeclaredMethod("updateFullScreenButtonForState", ImageView.class, boolean.class);
updateFullScreenButtonForState.setAccessible(true);
updateFullScreenButtonForState.invoke(controllerView, fullScreenButton, true);
// private void enableControls(final boolean enable) { }
// controlsBinding.speed.setEnabled(enable);
// controlsBinding.speed.setClickable(enable);
// controlsBinding.mute.setEnabled(enable);
// controlsBinding.mute.setClickable(enable);
// controlsBinding.ffWithAmount.setEnabled(enable);
// controlsBinding.ffWithAmount.setClickable(enable);
// controlsBinding.rewWithAmount.setEnabled(enable);
// controlsBinding.rewWithAmount.setClickable(enable);
// // controlsBinding.fromTime.setEnabled(enable);
// // controlsBinding.toTime.setEnabled(enable);
// controlsBinding.playPause.setEnabled(enable);
// controlsBinding.playPause.setClickable(enable);
// // controlsBinding.timeline.setEnabled(enable);
// }
// public void showMenu(View anchor) { @Nullable
// PopupMenu popup = getPopupMenu(anchor); private StyledPlayerControlView getStyledPlayerControlView() throws NoSuchFieldException, IllegalAccessException {
// popup.show(); final Field controller = binding.playerView.getClass().getDeclaredField("controller");
// } controller.setAccessible(true);
return (StyledPlayerControlView) controller.get(binding.playerView);
}
// @NonNull @Override
// private PopupMenu getPopupMenu(final View anchor) { public void onTracksChanged(@NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) {
// if (speedPopup != null) { if (trackGroups.isEmpty()) {
// return speedPopup; setHasAudio(false);
// } return;
// final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle); }
// // final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_PopupMenu_Exoplayer); boolean hasAudio = false;
// speedPopup = new PopupMenu(themeWrapper, anchor); for (int i = 0; i < trackGroups.length; i++) {
// speedPopup.getMenuInflater().inflate(R.menu.speed_menu, speedPopup.getMenu()); for (int g = 0; g < trackGroups.get(i).length; g++) {
// speedPopup.setOnMenuItemClickListener(item -> { final String sampleMimeType = trackGroups.get(i).getFormat(g).sampleMimeType;
// float nextSpeed; if (sampleMimeType != null && sampleMimeType.contains("audio")) {
// int textResId; hasAudio = true;
// int itemId = item.getItemId(); break;
// if (itemId == R.id.pt_two_five_x) { }
// nextSpeed = 0.25f; }
// textResId = R.string.pt_two_five_x; }
// } else if (itemId == R.id.pt_five_x) { setHasAudio(hasAudio);
// nextSpeed = 0.5f; }
// textResId = R.string.pt_five_x;
// } else if (itemId == R.id.pt_seven_five_x) { private void setHasAudio(final boolean hasAudio) {
// nextSpeed = 0.75f; if (mute == null) return;
// textResId = R.string.pt_seven_five_x; mute.setEnabled(hasAudio);
// } else if (itemId == R.id.one_x) { mute.setAlpha(hasAudio ? 1f : 0.5f);
// nextSpeed = 1f; updateMuteIcon(hasAudio ? 1f : 0f);
// textResId = R.string.one_x; }
// } else if (itemId == R.id.one_pt_two_five_x) {
// nextSpeed = 1.25f;
// textResId = R.string.one_pt_two_five_x;
// } else if (itemId == R.id.one_pt_five_x) {
// nextSpeed = 1.5f;
// textResId = R.string.one_pt_five_x;
// } else if (itemId == R.id.two_x) {
// nextSpeed = 2f;
// textResId = R.string.two_x;
// } else {
// nextSpeed = 1;
// textResId = R.string.one_x;
// }
// player.setPlaybackParameters(new PlaybackParameters(nextSpeed));
// controlsBinding.speed.setText(textResId);
// return true;
// });
// return speedPopup;
// }
private void updateMuteIcon(final float volume) { private void updateMuteIcon(final float volume) {
if (mute == null) return; if (mute == null) return;
if (volume == 0) { if (volume == 0) {
mute.setIconResource(R.drawable.ic_volume_off_24_states); mute.setImageResource(R.drawable.ic_volume_off_24);
return; return;
} }
mute.setIconResource(R.drawable.ic_volume_up_24_states); mute.setImageResource(R.drawable.ic_volume_up_24);
} }
// private void updatePlayPauseDrawable(final boolean playWhenReady) {
// if (playWhenReady) {
// controlsBinding.playPause.setIconResource(R.drawable.ic_pause_24);
// return;
// }
// controlsBinding.playPause.setIconResource(R.drawable.ic_play_states);
// }
@Override @Override
public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) { public void onPlayWhenReadyChanged(final boolean playWhenReady, final int reason) {
if (videoPlayerCallback == null) return; if (videoPlayerCallback == null) return;
@ -349,18 +286,18 @@ public class VideoPlayerViewHelper implements Player.EventListener {
private void toggleMute() { private void toggleMute() {
if (player == null) return; if (player == null) return;
if (layoutManager != null) {
try {
final Method resetHideCallbacks = layoutManager.getClass().getDeclaredMethod("resetHideCallbacks");
resetHideCallbacks.invoke(layoutManager);
} catch (Exception e) {
Log.e(TAG, "toggleMute: ", e);
}
}
final float vol = player.getVolume() == 0f ? 1f : 0f; final float vol = player.getVolume() == 0f ? 1f : 0f;
player.setVolume(vol); player.setVolume(vol);
} }
// public void togglePlayback() {
// if (player == null) return;
// final int playbackState = player.getPlaybackState();
// if (playbackState == STATE_IDLE || playbackState == STATE_ENDED) return;
// final boolean playWhenReady = player.getPlayWhenReady();
// player.setPlayWhenReady(!playWhenReady);
// }
public void releasePlayer() { public void releasePlayer() {
if (videoPlayerCallback != null) { if (videoPlayerCallback != null) {
videoPlayerCallback.onRelease(); videoPlayerCallback.onRelease();
@ -369,86 +306,14 @@ public class VideoPlayerViewHelper implements Player.EventListener {
player.release(); player.release();
player = null; player = null;
} }
// if (positionUpdateHandler != null) {
// if (positionChecker != null) {
// positionUpdateHandler.removeCallbacks(positionChecker);
// positionChecker = null;
// }
// positionUpdateHandler = null;
// }
} }
public void pause() { public void pause() {
if (player != null) { if (player != null) {
player.pause(); player.pause();
} }
// if (positionUpdateHandler != null) {
// if (positionChecker != null) {
// positionUpdateHandler.removeCallbacks(positionChecker);
// }
// }
} }
// public void resetTimeline() {
// if (player == null) {
// enableControls(false);
// return;
// }
// setupTimeline();
// final long currentPosition = player.getCurrentPosition();
// controlsBinding.timeline.setValue(Math.min(currentPosition, player.getDuration()));
// setupControls();
// }
// public void removeCallbacks() {
// if (player != null) {
// player.removeListener(listener);
// player.removeAudioListener(audioListener);
// }
// controlsBinding.timeline.removeOnChangeListener(onChangeListener);
// controlsBinding.timeline.setLabelFormatter(null);
// controlsBinding.playPause.setOnClickListener(null);
// controlsBinding.mute.setOnClickListener(null);
// controlsBinding.rewWithAmount.setOnClickListener(null);
// controlsBinding.ffWithAmount.setOnClickListener(null);
// controlsBinding.speed.setOnClickListener(null);
// }
// private static class PositionCheckRunnable implements Runnable {
// private final Handler positionUpdateHandler;
// private final SimpleExoPlayer player;
// private final Slider timeline;
// private final AppCompatTextView fromTime;
//
// public PositionCheckRunnable(final Handler positionUpdateHandler,
// final SimpleExoPlayer simpleExoPlayer,
// final Slider slider,
// final AppCompatTextView fromTime) {
// this.positionUpdateHandler = positionUpdateHandler;
// this.player = simpleExoPlayer;
// this.timeline = slider;
// this.fromTime = fromTime;
// }
//
// @Override
// public void run() {
// if (positionUpdateHandler == null) return;
// positionUpdateHandler.removeCallbacks(this);
// if (player == null) return;
// final long currentPosition = player.getCurrentPosition();
// final long duration = player.getDuration();
// if (duration == TIME_UNSET) {
// timeline.setValueFrom(0);
// timeline.setValueTo(0);
// timeline.setEnabled(false);
// return;
// }
// timeline.setValue(Math.min(currentPosition, duration));
// fromTime.setText(TextUtils.millisToTimeString(currentPosition));
// positionUpdateHandler.postDelayed(this, RECURRING_DELAY);
// }
// }
public interface VideoPlayerCallback { public interface VideoPlayerCallback {
void onThumbnailLoaded(); void onThumbnailLoaded();
@ -461,5 +326,9 @@ public class VideoPlayerViewHelper implements Player.EventListener {
void onPause(); void onPause();
void onRelease(); void onRelease();
void onFullScreenModeChanged(boolean isFullScreen, final StyledPlayerView playerView);
boolean isInFullScreen();
} }
} }

View File

@ -228,7 +228,7 @@ public class ZoomableDraweeView extends DraweeView<GenericDraweeHierarchy>
public void setZoomingEnabled(boolean zoomingEnabled) { public void setZoomingEnabled(boolean zoomingEnabled) {
mZoomingEnabled = zoomingEnabled; mZoomingEnabled = zoomingEnabled;
mZoomableController.setEnabled(false); mZoomableController.setEnabled(zoomingEnabled);
} }
/** /**

View File

@ -0,0 +1,100 @@
package awais.instagrabber.customviews.emoji;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import awais.instagrabber.R;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.utils.Utils;
public class EmojiBottomSheetDialog extends BottomSheetDialogFragment {
public static final String TAG = EmojiBottomSheetDialog.class.getSimpleName();
private RecyclerView grid;
private EmojiPicker.OnEmojiClickListener callback;
@NonNull
public static EmojiBottomSheetDialog newInstance() {
// Bundle args = new Bundle();
// fragment.setArguments(args);
return new EmojiBottomSheetDialog();
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
final Context context = getContext();
if (context == null) return null;
grid = new RecyclerView(context);
return grid;
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
if (bottomSheetInternal == null) return;
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomSheetInternal.requestLayout();
}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
final Fragment parentFragment = getParentFragment();
if (parentFragment instanceof EmojiPicker.OnEmojiClickListener) {
callback = (EmojiPicker.OnEmojiClickListener) parentFragment;
}
}
@Override
public void onDestroyView() {
grid = null;
super.onDestroyView();
}
private void init() {
final Context context = getContext();
if (context == null) return;
final GridLayoutManager gridLayoutManager = new GridLayoutManager(context, 9);
grid.setLayoutManager(gridLayoutManager);
grid.setHasFixedSize(true);
grid.setClipToPadding(false);
grid.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(8)));
final EmojiGridAdapter adapter = new EmojiGridAdapter(null, (view, emoji) -> {
if (callback != null) {
callback.onClick(view, emoji);
}
dismiss();
}, null);
grid.setAdapter(adapter);
}
}

View File

@ -43,7 +43,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
private final EmojiVariantManager emojiVariantManager; private final EmojiVariantManager emojiVariantManager;
private final AppExecutors appExecutors; private final AppExecutors appExecutors;
public EmojiGridAdapter(@NonNull final EmojiCategoryType emojiCategoryType, public EmojiGridAdapter(final EmojiCategoryType emojiCategoryType,
final OnEmojiClickListener onEmojiClickListener, final OnEmojiClickListener onEmojiClickListener,
final OnEmojiLongClickListener onEmojiLongClickListener) { final OnEmojiLongClickListener onEmojiLongClickListener) {
this.onEmojiClickListener = onEmojiClickListener; this.onEmojiClickListener = onEmojiClickListener;
@ -55,6 +55,11 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
emojiVariantManager = EmojiVariantManager.getInstance(); emojiVariantManager = EmojiVariantManager.getInstance();
appExecutors = AppExecutors.getInstance(); appExecutors = AppExecutors.getInstance();
setHasStableIds(true); setHasStableIds(true);
if (emojiCategoryType == null) {
// show all if type is null
differ.submitList(ImmutableList.copyOf(emojiParser.getAllEmojis().values()));
return;
}
final EmojiCategory emojiCategory = categoryMap.get(emojiCategoryType); final EmojiCategory emojiCategory = categoryMap.get(emojiCategoryType);
if (emojiCategory == null) { if (emojiCategory == null) {
differ.submitList(Collections.emptyList()); differ.submitList(Collections.emptyList());
@ -105,7 +110,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
} }
public static class EmojiViewHolder extends RecyclerView.ViewHolder { public static class EmojiViewHolder extends RecyclerView.ViewHolder {
private final AppExecutors appExecutors = AppExecutors.getInstance(); // private final AppExecutors appExecutors = AppExecutors.getInstance();
private final ItemEmojiGridBinding binding; private final ItemEmojiGridBinding binding;
private final OnEmojiClickListener onEmojiClickListener; private final OnEmojiClickListener onEmojiClickListener;
private final OnEmojiLongClickListener onEmojiLongClickListener; private final OnEmojiLongClickListener onEmojiLongClickListener;
@ -123,7 +128,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
binding.image.setImageDrawable(null); binding.image.setImageDrawable(null);
binding.indicator.setVisibility(View.GONE); binding.indicator.setVisibility(View.GONE);
itemView.setOnLongClickListener(null); itemView.setOnLongClickListener(null);
itemView.post(() -> { // itemView.post(() -> {
binding.image.setImageDrawable(emoji.getDrawable()); binding.image.setImageDrawable(emoji.getDrawable());
final boolean hasVariants = !parent.getVariants().isEmpty(); final boolean hasVariants = !parent.getVariants().isEmpty();
binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE); binding.indicator.setVisibility(hasVariants ? View.VISIBLE : View.GONE);
@ -133,7 +138,7 @@ public class EmojiGridAdapter extends RecyclerView.Adapter<EmojiGridAdapter.Emoj
if (hasVariants && onEmojiLongClickListener != null) { if (hasVariants && onEmojiLongClickListener != null) {
itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent)); itemView.setOnLongClickListener(v -> onEmojiLongClickListener.onLongClick(position, v, parent));
} }
}); // });
} }
} }

View File

@ -1,163 +0,0 @@
package awais.instagrabber.customviews.emoji;
import android.content.Context;
import android.graphics.Rect;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;
import awais.instagrabber.R;
import awais.instagrabber.customviews.emoji.EmojiPicker.OnBackspaceClickListener;
import awais.instagrabber.customviews.emoji.EmojiPicker.OnEmojiClickListener;
import awais.instagrabber.utils.Utils;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
/**
* https://stackoverflow.com/a/33897583/1436766
*/
public class EmojiPopupWindow extends PopupWindow {
private int keyBoardHeight = 0;
private Boolean pendingOpen = false;
private Boolean isOpened = false;
private final View rootView;
private final Context context;
private final OnEmojiClickListener onEmojiClickListener;
private final OnBackspaceClickListener onBackspaceClickListener;
private OnSoftKeyboardOpenCloseListener onSoftKeyboardOpenCloseListener;
/**
* Constructor
*
* @param rootView The top most layout in your view hierarchy. The difference of this view and the screen height will be used to calculate the keyboard height.
*/
public EmojiPopupWindow(final View rootView,
final OnEmojiClickListener onEmojiClickListener,
final OnBackspaceClickListener onBackspaceClickListener) {
super(rootView.getContext());
this.rootView = rootView;
this.context = rootView.getContext();
this.onEmojiClickListener = onEmojiClickListener;
this.onBackspaceClickListener = onBackspaceClickListener;
View customView = createCustomView();
setContentView(customView);
setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
//default size
setSize((int) context.getResources().getDimension(R.dimen.keyboard_height), MATCH_PARENT);
}
/**
* Set the listener for the event of keyboard opening or closing.
*/
public void setOnSoftKeyboardOpenCloseListener(OnSoftKeyboardOpenCloseListener listener) {
this.onSoftKeyboardOpenCloseListener = listener;
}
/**
* Use this function to show the emoji popup.
* NOTE: Since, the soft keyboard sizes are variable on different android devices, the
* library needs you to open the soft keyboard atleast once before calling this function.
* If that is not possible see showAtBottomPending() function.
*/
public void showAtBottom() {
showAtLocation(rootView, Gravity.BOTTOM, 0, 0);
}
/**
* Use this function when the soft keyboard has not been opened yet. This
* will show the emoji popup after the keyboard is up next time.
* Generally, you will be calling InputMethodManager.showSoftInput function after
* calling this function.
*/
public void showAtBottomPending() {
if (isKeyBoardOpen())
showAtBottom();
else
pendingOpen = true;
}
/**
* @return Returns true if the soft keyboard is open, false otherwise.
*/
public Boolean isKeyBoardOpen() {
return isOpened;
}
/**
* Dismiss the popup
*/
@Override
public void dismiss() {
super.dismiss();
}
/**
* Call this function to resize the emoji popup according to your soft keyboard size
*/
public void setSizeForSoftKeyboard() {
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
Rect r = new Rect();
rootView.getWindowVisibleDisplayFrame(r);
int screenHeight = getUsableScreenHeight();
int heightDifference = screenHeight - (r.bottom - r.top);
int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
heightDifference -= context.getResources()
.getDimensionPixelSize(resourceId);
}
if (heightDifference > 100) {
keyBoardHeight = heightDifference;
setSize(MATCH_PARENT, keyBoardHeight);
if (!isOpened) {
if (onSoftKeyboardOpenCloseListener != null)
onSoftKeyboardOpenCloseListener.onKeyboardOpen(keyBoardHeight);
}
isOpened = true;
if (pendingOpen) {
showAtBottom();
pendingOpen = false;
}
} else {
isOpened = false;
if (onSoftKeyboardOpenCloseListener != null)
onSoftKeyboardOpenCloseListener.onKeyboardClose();
}
});
}
private int getUsableScreenHeight() {
return Utils.displayMetrics.heightPixels;
}
/**
* Manually set the popup window size
*
* @param width Width of the popup
* @param height Height of the popup
*/
public void setSize(int width, int height) {
setWidth(width);
setHeight(height);
}
private View createCustomView() {
final EmojiPicker emojiPicker = new EmojiPicker(context);
final LayoutParams layoutParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT);
emojiPicker.setLayoutParams(layoutParams);
emojiPicker.init(rootView, onEmojiClickListener, onBackspaceClickListener);
return emojiPicker;
}
public interface OnSoftKeyboardOpenCloseListener {
void onKeyboardOpen(int keyBoardHeight);
void onKeyboardClose();
}
}

View File

@ -25,7 +25,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Spanned; import android.text.Spanned;
import android.text.TextPaint; import android.text.TextPaint;
import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.emoji.text.EmojiCompat; import androidx.emoji.text.EmojiCompat;

View File

@ -0,0 +1,320 @@
package awais.instagrabber.customviews.helpers;
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.graphics.Color;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.transition.Transition;
import androidx.transition.TransitionListenerAdapter;
import androidx.transition.TransitionValues;
import java.util.Map;
import java.util.Objects;
import awais.instagrabber.BuildConfig;
/**
* This transition tracks changes to the text in TextView targets. If the text
* changes between the start and end scenes, the transition ensures that the
* starting text stays until the transition ends, at which point it changes
* to the end text. This is useful in situations where you want to resize a
* text view to its new size before displaying the text that goes there.
*/
public class ChangeText extends Transition {
private static final String LOG_TAG = "TextChange";
private static final String PROPNAME_TEXT = "android:textchange:text";
private static final String PROPNAME_TEXT_SELECTION_START =
"android:textchange:textSelectionStart";
private static final String PROPNAME_TEXT_SELECTION_END =
"android:textchange:textSelectionEnd";
private static final String PROPNAME_TEXT_COLOR = "android:textchange:textColor";
private int mChangeBehavior = CHANGE_BEHAVIOR_KEEP;
private boolean crossFade;
/**
* Flag specifying that the text in affected/changing TextView targets will keep
* their original text during the transition, setting it to the final text when
* the transition ends. This is the default behavior.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_KEEP = 0;
/**
* Flag specifying that the text changing animation should first fade
* out the original text completely. The new text is set on the target
* view at the end of the fade-out animation. This transition is typically
* used with a later {@link #CHANGE_BEHAVIOR_IN} transition, allowing more
* flexibility than the {@link #CHANGE_BEHAVIOR_OUT_IN} by allowing other
* transitions to be run sequentially or in parallel with these fades.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_OUT = 1;
/**
* Flag specifying that the text changing animation should fade in the
* end text into the affected target view(s). This transition is typically
* used in conjunction with an earlier {@link #CHANGE_BEHAVIOR_OUT}
* transition, possibly with other transitions running as well, such as
* a sequence to fade out, then resize the view, then fade in.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_IN = 2;
/**
* Flag specifying that the text changing animation should first fade
* out the original text completely and then fade in the
* new text.
*
* @see #setChangeBehavior(int)
*/
public static final int CHANGE_BEHAVIOR_OUT_IN = 3;
private static final String[] sTransitionProperties = {
PROPNAME_TEXT,
PROPNAME_TEXT_SELECTION_START,
PROPNAME_TEXT_SELECTION_END
};
/**
* Sets the type of changing animation that will be run, one of
* {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT},
* {@link #CHANGE_BEHAVIOR_IN}, and {@link #CHANGE_BEHAVIOR_OUT_IN}.
*
* @param changeBehavior The type of fading animation to use when this
* transition is run.
* @return this textChange object.
*/
public ChangeText setChangeBehavior(int changeBehavior) {
if (changeBehavior >= CHANGE_BEHAVIOR_KEEP && changeBehavior <= CHANGE_BEHAVIOR_OUT_IN) {
mChangeBehavior = changeBehavior;
}
return this;
}
public ChangeText setCrossFade(final boolean crossFade) {
this.crossFade = crossFade;
return this;
}
@Override
public String[] getTransitionProperties() {
return sTransitionProperties;
}
/**
* Returns the type of changing animation that will be run.
*
* @return either {@link #CHANGE_BEHAVIOR_KEEP}, {@link #CHANGE_BEHAVIOR_OUT},
* {@link #CHANGE_BEHAVIOR_IN}, or {@link #CHANGE_BEHAVIOR_OUT_IN}.
*/
public int getChangeBehavior() {
return mChangeBehavior;
}
private void captureValues(TransitionValues transitionValues) {
if (transitionValues.view instanceof TextView) {
TextView textview = (TextView) transitionValues.view;
transitionValues.values.put(PROPNAME_TEXT, textview.getText());
if (textview instanceof EditText) {
transitionValues.values.put(PROPNAME_TEXT_SELECTION_START,
textview.getSelectionStart());
transitionValues.values.put(PROPNAME_TEXT_SELECTION_END,
textview.getSelectionEnd());
}
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
transitionValues.values.put(PROPNAME_TEXT_COLOR, textview.getCurrentTextColor());
}
}
}
@Override
public void captureStartValues(@NonNull TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public void captureEndValues(@NonNull TransitionValues transitionValues) {
captureValues(transitionValues);
}
@Override
public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
TransitionValues endValues) {
if (startValues == null || endValues == null ||
!(startValues.view instanceof TextView) || !(endValues.view instanceof TextView)) {
return null;
}
final TextView view = (TextView) endValues.view;
Map<String, Object> startVals = startValues.values;
Map<String, Object> endVals = endValues.values;
final CharSequence startText = startVals.get(PROPNAME_TEXT) != null ?
(CharSequence) startVals.get(PROPNAME_TEXT) : "";
final CharSequence endText = endVals.get(PROPNAME_TEXT) != null ?
(CharSequence) endVals.get(PROPNAME_TEXT) : "";
final int startSelectionStart, startSelectionEnd, endSelectionStart, endSelectionEnd;
if (view instanceof EditText) {
startSelectionStart = startVals.get(PROPNAME_TEXT_SELECTION_START) != null ?
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_START) : -1;
startSelectionEnd = startVals.get(PROPNAME_TEXT_SELECTION_END) != null ?
(Integer) startVals.get(PROPNAME_TEXT_SELECTION_END) : startSelectionStart;
endSelectionStart = endVals.get(PROPNAME_TEXT_SELECTION_START) != null ?
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_START) : -1;
endSelectionEnd = endVals.get(PROPNAME_TEXT_SELECTION_END) != null ?
(Integer) endVals.get(PROPNAME_TEXT_SELECTION_END) : endSelectionStart;
} else {
startSelectionStart = startSelectionEnd = endSelectionStart = endSelectionEnd = -1;
}
if (!Objects.equals(startText, endText)) {
final int startColor;
final int endColor;
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
view.setText(startText);
if (view instanceof EditText) {
setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
}
}
Animator anim;
if (mChangeBehavior == CHANGE_BEHAVIOR_KEEP) {
startColor = endColor = 0;
anim = ValueAnimator.ofFloat(0, 1);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (Objects.equals(startText, view.getText())) {
// Only set if it hasn't been changed since anim started
view.setText(endText);
if (view instanceof EditText) {
setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
}
}
}
});
} else {
startColor = (Integer) startVals.get(PROPNAME_TEXT_COLOR);
endColor = (Integer) endVals.get(PROPNAME_TEXT_COLOR);
// Fade out start text
ValueAnimator outAnim = null, inAnim = null;
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN ||
mChangeBehavior == CHANGE_BEHAVIOR_OUT) {
outAnim = ValueAnimator.ofInt(Color.alpha(startColor), 0);
outAnim.addUpdateListener(animation -> {
int currAlpha = (Integer) animation.getAnimatedValue();
view.setTextColor(currAlpha << 24 | startColor & 0xffffff);
});
outAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
if (Objects.equals(startText, view.getText())) {
// Only set if it hasn't been changed since anim started
view.setText(endText);
if (view instanceof EditText) {
setSelection(((EditText) view), endSelectionStart,
endSelectionEnd);
}
}
// restore opaque alpha and correct end color
view.setTextColor(endColor);
}
});
}
if (mChangeBehavior == CHANGE_BEHAVIOR_OUT_IN ||
mChangeBehavior == CHANGE_BEHAVIOR_IN) {
inAnim = ValueAnimator.ofInt(0, Color.alpha(endColor));
inAnim.addUpdateListener(animation -> {
int currAlpha = (Integer) animation.getAnimatedValue();
view.setTextColor(currAlpha << 24 | endColor & 0xffffff);
});
inAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
// restore opaque alpha and correct end color
view.setTextColor(endColor);
}
});
}
if (outAnim != null && inAnim != null) {
anim = new AnimatorSet();
final AnimatorSet animatorSet = (AnimatorSet) anim;
if (crossFade) {
animatorSet.playTogether(outAnim, inAnim);
} else {
animatorSet.playSequentially(outAnim, inAnim);
}
} else if (outAnim != null) {
anim = outAnim;
} else {
// Must be an in-only animation
anim = inAnim;
}
}
TransitionListener transitionListener = new TransitionListenerAdapter() {
int mPausedColor = 0;
@Override
public void onTransitionPause(@NonNull Transition transition) {
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
view.setText(endText);
if (view instanceof EditText) {
setSelection(((EditText) view), endSelectionStart, endSelectionEnd);
}
}
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
mPausedColor = view.getCurrentTextColor();
view.setTextColor(endColor);
}
}
@Override
public void onTransitionResume(@NonNull Transition transition) {
if (mChangeBehavior != CHANGE_BEHAVIOR_IN) {
view.setText(startText);
if (view instanceof EditText) {
setSelection(((EditText) view), startSelectionStart, startSelectionEnd);
}
}
if (mChangeBehavior > CHANGE_BEHAVIOR_KEEP) {
view.setTextColor(mPausedColor);
}
}
@Override
public void onTransitionEnd(Transition transition) {
transition.removeListener(this);
}
};
addListener(transitionListener);
if (BuildConfig.DEBUG) {
Log.d(LOG_TAG, "createAnimator returning " + anim);
}
return anim;
}
return null;
}
private void setSelection(EditText editText, int start, int end) {
if (start >= 0 && end >= 0) {
editText.setSelection(start, end);
}
}
}

View File

@ -0,0 +1,87 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package awais.instagrabber.customviews.helpers;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A [WindowInsetsAnimationCompat.Callback] which will request and clear focus on the given view,
* depending on the [WindowInsetsCompat.Type.ime] visibility state when an IME
* [WindowInsetsAnimationCompat] has finished.
* <p>
* This is primarily used when animating the [WindowInsetsCompat.Type.ime], so that the
* appropriate view is focused for accepting input from the IME.
*/
public class ControlFocusInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback {
private final View view;
public ControlFocusInsetsAnimationCallback(@NonNull final View view) {
this(view, DISPATCH_MODE_STOP);
}
/**
* @param view the view to request/clear focus
* @param dispatchMode The dispatch mode for this callback.
* @see WindowInsetsAnimationCompat.Callback.DispatchMode
*/
public ControlFocusInsetsAnimationCallback(@NonNull final View view, final int dispatchMode) {
super(dispatchMode);
this.view = view;
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnimations) {
// no-op and return the insets
return insets;
}
@Override
public void onEnd(final WindowInsetsAnimationCompat animation) {
if ((animation.getTypeMask() & WindowInsetsCompat.Type.ime()) != 0) {
// The animation has now finished, so we can check the view's focus state.
// We post the check because the rootWindowInsets has not yet been updated, but will
// be in the next message traversal
view.post(this::checkFocus);
}
}
private void checkFocus() {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
boolean imeVisible = false;
if (rootWindowInsets != null) {
imeVisible = rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
}
if (imeVisible && view.getRootView().findFocus() == null) {
// If the IME will be visible, and there is not a currently focused view in
// the hierarchy, request focus on our view
view.requestFocus();
} else if (!imeVisible && view.isFocused()) {
// If the IME will not be visible and our view is currently focused, clear the focus
view.clearFocus();
}
}
}

View File

@ -1,5 +1,7 @@
package awais.instagrabber.customviews.helpers; package awais.instagrabber.customviews.helpers;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -12,6 +14,13 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
public class CustomHideBottomViewOnScrollBehavior extends HideBottomViewOnScrollBehavior<BottomNavigationView> { public class CustomHideBottomViewOnScrollBehavior extends HideBottomViewOnScrollBehavior<BottomNavigationView> {
private static final String TAG = "CustomHideBottomView"; private static final String TAG = "CustomHideBottomView";
public CustomHideBottomViewOnScrollBehavior() {
}
public CustomHideBottomViewOnScrollBehavior(final Context context, final AttributeSet attrs) {
super(context, attrs);
}
@Override @Override
public boolean onStartNestedScroll(@NonNull final CoordinatorLayout coordinatorLayout, public boolean onStartNestedScroll(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final BottomNavigationView child, @NonNull final BottomNavigationView child,
@ -23,7 +32,13 @@ public class CustomHideBottomViewOnScrollBehavior extends HideBottomViewOnScroll
} }
@Override @Override
public void onNestedPreScroll(@NonNull final CoordinatorLayout coordinatorLayout, @NonNull final BottomNavigationView child, @NonNull final View target, final int dx, final int dy, @NonNull final int[] consumed, final int type) { public void onNestedPreScroll(@NonNull final CoordinatorLayout coordinatorLayout,
@NonNull final BottomNavigationView child,
@NonNull final View target,
final int dx,
final int dy,
@NonNull final int[] consumed,
final int type) {
if (dy > 0) { if (dy > 0) {
slideDown(child); slideDown(child);
} else if (dy < 0) { } else if (dy < 0) {

View File

@ -0,0 +1,117 @@
package awais.instagrabber.customviews.helpers;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A customized {@link TranslateDeferringInsetsAnimationCallback} for the emoji picker
*/
public class EmojiPickerInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback {
private static final String TAG = EmojiPickerInsetsAnimationCallback.class.getSimpleName();
private final View view;
private final int persistentInsetTypes;
private final int deferredInsetTypes;
private int kbHeight;
private onKbVisibilityChangeListener listener;
private boolean shouldTranslate;
public EmojiPickerInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes) {
this(view, persistentInsetTypes, deferredInsetTypes, DISPATCH_MODE_STOP);
}
public EmojiPickerInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes,
final int dispatchMode) {
super(dispatchMode);
if ((persistentInsetTypes & deferredInsetTypes) != 0) {
throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " +
"any of same WindowInsetsCompat.Type values");
}
this.view = view;
this.persistentInsetTypes = persistentInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnimations) {
// onProgress() is called when any of the running animations progress...
// First we get the insets which are potentially deferred
final Insets typesInset = insets.getInsets(deferredInsetTypes);
// Then we get the persistent inset types which are applied as padding during layout
final Insets otherInset = insets.getInsets(persistentInsetTypes);
// Now that we subtract the two insets, to calculate the difference. We also coerce
// the insets to be >= 0, to make sure we don't use negative insets.
final Insets subtract = Insets.subtract(typesInset, otherInset);
final Insets diff = Insets.max(subtract, Insets.NONE);
// The resulting `diff` insets contain the values for us to apply as a translation
// to the view
view.setTranslationX(diff.left - diff.right);
view.setTranslationY(shouldTranslate ? diff.top - diff.bottom : -kbHeight);
return insets;
}
@Override
public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) {
try {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
if (kbHeight == 0) {
if (rootWindowInsets == null) return;
final Insets imeInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.ime());
final Insets navBarInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
kbHeight = imeInsets.bottom - navBarInsets.bottom;
final ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
if (layoutParams != null) {
layoutParams.height = kbHeight;
layoutParams.setMargins(layoutParams.leftMargin, layoutParams.topMargin, layoutParams.rightMargin, -kbHeight);
}
}
view.setTranslationX(0f);
final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
float translationY = 0;
if (!shouldTranslate) {
translationY = -kbHeight;
if (visible) {
translationY = 0;
}
}
view.setTranslationY(translationY);
if (listener != null && rootWindowInsets != null) {
listener.onChange(visible);
}
} finally {
shouldTranslate = true;
}
}
public void setShouldTranslate(final boolean shouldTranslate) {
this.shouldTranslate = shouldTranslate;
}
public void setKbVisibilityListener(final onKbVisibilityChangeListener listener) {
this.listener = listener;
}
public interface onKbVisibilityChangeListener {
void onChange(boolean isVisible);
}
}

View File

@ -7,17 +7,24 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private final int spacing; private final int halfSpace;
private boolean hasHeader;
public GridSpacingItemDecoration(int spacing) { public GridSpacingItemDecoration(int spacing) {
this.spacing = spacing; halfSpace = spacing / 2;
} }
@Override @Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
final int halfSpace = spacing / 2; if (hasHeader && parent.getChildAdapterPosition(view) == 0) {
outRect.bottom = halfSpace;
outRect.left = -halfSpace;
outRect.right = -halfSpace;
return;
}
if (parent.getPaddingLeft() != halfSpace) { if (parent.getPaddingLeft() != halfSpace) {
parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace); parent.setPadding(halfSpace, hasHeader ? 0 : halfSpace, halfSpace, halfSpace);
parent.setClipToPadding(false); parent.setClipToPadding(false);
} }
outRect.top = halfSpace; outRect.top = halfSpace;
@ -25,4 +32,8 @@ public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
outRect.left = halfSpace; outRect.left = halfSpace;
outRect.right = halfSpace; outRect.right = halfSpace;
} }
public void setHasHeader(final boolean hasHeader) {
this.hasHeader = hasHeader;
}
} }

View File

@ -0,0 +1,139 @@
package awais.instagrabber.customviews.helpers;/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.OnApplyWindowInsetsListener;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A class which extends/implements both [WindowInsetsAnimationCompat.Callback] and
* [View.OnApplyWindowInsetsListener], which should be set on the root view in your layout.
* <p>
* This class enables the root view is selectively defer handling any insets which match
* [deferredInsetTypes], to enable better looking [WindowInsetsAnimationCompat]s.
* <p>
* An example is the following: when a [WindowInsetsAnimationCompat] is started, the system will dispatch
* a [WindowInsetsCompat] instance which contains the end state of the animation. For the scenario of
* the IME being animated in, that means that the insets contains the IME height. If the view's
* [View.OnApplyWindowInsetsListener] simply always applied the combination of
* [WindowInsetsCompat.Type.ime] and [WindowInsetsCompat.Type.systemBars] using padding, the viewport of any
* child views would then be smaller. This results in us animating a smaller (padded-in) view into
* a larger viewport. Visually, this results in the views looking clipped.
* <p>
* This class allows us to implement a different strategy for the above scenario, by selectively
* deferring the [WindowInsetsCompat.Type.ime] insets until the [WindowInsetsAnimationCompat] is ended.
* For the above example, you would create a [RootViewDeferringInsetsCallback] like so:
* <p>
* ```
* val callback = RootViewDeferringInsetsCallback(
* persistentInsetTypes = WindowInsetsCompat.Type.systemBars(),
* deferredInsetTypes = WindowInsetsCompat.Type.ime()
* )
* ```
* <p>
* This class is not limited to just IME animations, and can work with any [WindowInsetsCompat.Type]s.
*/
public class RootViewDeferringInsetsCallback extends WindowInsetsAnimationCompat.Callback implements OnApplyWindowInsetsListener {
private final int persistentInsetTypes;
private final int deferredInsetTypes;
@Nullable
private View view = null;
@Nullable
private WindowInsetsCompat lastWindowInsets = null;
private boolean deferredInsets = false;
/**
* @param persistentInsetTypes the bitmask of any inset types which should always be handled
* through padding the attached view
* @param deferredInsetTypes the bitmask of insets types which should be deferred until after
* any related [WindowInsetsAnimationCompat]s have ended
*/
public RootViewDeferringInsetsCallback(final int persistentInsetTypes, final int deferredInsetTypes) {
super(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
if ((persistentInsetTypes & deferredInsetTypes) != 0) {
throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " +
"any of same WindowInsetsCompat.Type values");
}
this.persistentInsetTypes = persistentInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
}
@Override
public WindowInsetsCompat onApplyWindowInsets(@NonNull final View v, @NonNull final WindowInsetsCompat windowInsets) {
// Store the view and insets for us in onEnd() below
view = v;
lastWindowInsets = windowInsets;
final int types = deferredInsets
// When the deferred flag is enabled, we only use the systemBars() insets
? persistentInsetTypes
// Otherwise we handle the combination of the the systemBars() and ime() insets
: persistentInsetTypes | deferredInsetTypes;
// Finally we apply the resolved insets by setting them as padding
final Insets typeInsets = windowInsets.getInsets(types);
v.setPadding(typeInsets.left, typeInsets.top, typeInsets.right, typeInsets.bottom);
// We return the new WindowInsetsCompat.CONSUMED to stop the insets being dispatched any
// further into the view hierarchy. This replaces the deprecated
// WindowInsetsCompat.consumeSystemWindowInsets() and related functions.
return WindowInsetsCompat.CONSUMED;
}
@Override
public void onPrepare(WindowInsetsAnimationCompat animation) {
if ((animation.getTypeMask() & deferredInsetTypes) != 0) {
// We defer the WindowInsetsCompat.Type.ime() insets if the IME is currently not visible.
// This results in only the WindowInsetsCompat.Type.systemBars() being applied, allowing
// the scrolling view to remain at it's larger size.
deferredInsets = true;
}
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnims) {
// This is a no-op. We don't actually want to handle any WindowInsetsAnimations
return insets;
}
@Override
public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) {
if (deferredInsets && (animation.getTypeMask() & deferredInsetTypes) != 0) {
// If we deferred the IME insets and an IME animation has finished, we need to reset
// the flag
deferredInsets = false;
// And finally dispatch the deferred insets to the view now.
// Ideally we would just call view.requestApplyInsets() and let the normal dispatch
// cycle happen, but this happens too late resulting in a visual flicker.
// Instead we manually dispatch the most recent WindowInsets to the view.
if (lastWindowInsets != null && view != null) {
ViewCompat.dispatchApplyWindowInsets(view, lastWindowInsets);
}
}
}
}

View File

@ -0,0 +1,443 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package awais.instagrabber.customviews.helpers;
import android.os.CancellationSignal;
import android.util.Log;
import android.view.View;
import android.view.animation.LinearInterpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationControlListenerCompat;
import androidx.core.view.WindowInsetsAnimationControllerCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import awais.instagrabber.utils.ViewUtils;
/**
* A wrapper around the [WindowInsetsAnimationControllerCompat] APIs in AndroidX Core, to simplify
* the implementation of common use-cases around the IME.
* <p>
* See [InsetsAnimationLinearLayout] and [InsetsAnimationTouchListener] for examples of how
* to use this class.
*/
public class SimpleImeAnimationController {
private static final String TAG = SimpleImeAnimationController.class.getSimpleName();
/**
* Scroll threshold for determining whether to animating to the end state, or to the start state.
* Currently 15% of the total swipe distance distance
*/
private static final float SCROLL_THRESHOLD = 0.15f;
@Nullable
private WindowInsetsAnimationControllerCompat insetsAnimationController = null;
@Nullable
private CancellationSignal pendingRequestCancellationSignal = null;
@Nullable
private OnRequestReadyListener pendingRequestOnReadyListener;
/**
* True if the IME was shown at the start of the current animation.
*/
private boolean isImeShownAtStart = false;
@Nullable
private SpringAnimation currentSpringAnimation = null;
private WindowInsetsAnimationControlListenerCompat fwdListener;
/**
* A LinearInterpolator instance we can re-use across listeners.
*/
private final LinearInterpolator linearInterpolator = new LinearInterpolator();
/* To take control of the an WindowInsetsAnimation, we need to pass in a listener to
controlWindowInsetsAnimation() in startControlRequest(). The listener created here
keeps track of the current WindowInsetsAnimationController and resets our state. */
private final WindowInsetsAnimationControlListenerCompat animationControlListener = new WindowInsetsAnimationControlListenerCompat() {
/**
* Once the request is ready, call our [onRequestReady] function
*/
@Override
public void onReady(@NonNull final WindowInsetsAnimationControllerCompat controller, final int types) {
onRequestReady(controller);
if (fwdListener != null) {
fwdListener.onReady(controller, types);
}
}
/**
* If the request is finished, we should reset our internal state
*/
@Override
public void onFinished(@NonNull final WindowInsetsAnimationControllerCompat controller) {
reset();
if (fwdListener != null) {
fwdListener.onFinished(controller);
}
}
/**
* If the request is cancelled, we should reset our internal state
*/
@Override
public void onCancelled(@Nullable final WindowInsetsAnimationControllerCompat controller) {
reset();
if (fwdListener != null) {
fwdListener.onCancelled(controller);
}
}
};
/**
* Start a control request to the [view]s [android.view.WindowInsetsController]. This should
* be called once the view is in a position to take control over the position of the IME.
*
* @param view The view which is triggering this request
* @param onRequestReadyListener optional listener which will be called when the request is ready and
* the animation can proceed
*/
public void startControlRequest(@NonNull final View view,
@Nullable final OnRequestReadyListener onRequestReadyListener) {
if (isInsetAnimationInProgress()) {
Log.w(TAG, "startControlRequest: Animation in progress. Can not start a new request to controlWindowInsetsAnimation()");
return;
}
// Keep track of the IME insets, and the IME visibility, at the start of the request
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
if (rootWindowInsets != null) {
isImeShownAtStart = rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
}
// Create a cancellation signal, which we pass to controlWindowInsetsAnimation() below
pendingRequestCancellationSignal = new CancellationSignal();
// Keep reference to the onReady callback
pendingRequestOnReadyListener = onRequestReadyListener;
// Finally we make a controlWindowInsetsAnimation() request:
final WindowInsetsControllerCompat windowInsetsController = ViewCompat.getWindowInsetsController(view);
if (windowInsetsController != null) {
windowInsetsController.controlWindowInsetsAnimation(
// We're only catering for IME animations in this listener
WindowInsetsCompat.Type.ime(),
// Animation duration. This is not used by the system, and is only passed to any
// WindowInsetsAnimation.Callback set on views. We pass in -1 to indicate that we're
// not starting a finite animation, and that this is completely controlled by
// the user's touch.
-1,
// The time interpolator used in calculating the animation progress. The fraction value
// we passed into setInsetsAndAlpha() which be passed into this interpolator before
// being used by the system to inset the IME. LinearInterpolator is a good type
// to use for scrolling gestures.
linearInterpolator,
// A cancellation signal, which allows us to cancel the request to control
pendingRequestCancellationSignal,
// The WindowInsetsAnimationControlListener
animationControlListener
);
}
}
/**
* Start a control request to the [view]s [android.view.WindowInsetsController], similar to
* [startControlRequest], but immediately fling to a finish using [velocityY] once ready.
* <p>
* This function is useful for fire-and-forget operations to animate the IME.
*
* @param view The view which is triggering this request
* @param velocityY the velocity of the touch gesture which caused this call
*/
public void startAndFling(@NonNull final View view, final float velocityY) {
startControlRequest(view, null);
animateToFinish(velocityY);
}
/**
* Update the inset position of the IME by the given [dy] value. This value will be coerced
* into the hidden and shown inset values.
* <p>
* This function should only be called if [isInsetAnimationInProgress] returns true.
*
* @return the amount of [dy] consumed by the inset animation, in pixels
*/
public int insetBy(final int dy) {
if (insetsAnimationController == null) {
throw new IllegalStateException("Current WindowInsetsAnimationController is null." +
"This should only be called if isAnimationInProgress() returns true");
}
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
// Call updateInsetTo() with the new inset value
return insetTo(controller.getCurrentInsets().bottom - dy);
}
/**
* Update the inset position of the IME to be the given [inset] value. This value will be
* coerced into the hidden and shown inset values.
* <p>
* This function should only be called if [isInsetAnimationInProgress] returns true.
*
* @return the distance moved by the inset animation, in pixels
*/
public int insetTo(final int inset) {
if (insetsAnimationController == null) {
throw new IllegalStateException("Current WindowInsetsAnimationController is null." +
"This should only be called if isAnimationInProgress() returns true");
}
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
final int hiddenBottom = controller.getHiddenStateInsets().bottom;
final int shownBottom = controller.getShownStateInsets().bottom;
final int startBottom = isImeShownAtStart ? shownBottom : hiddenBottom;
final int endBottom = isImeShownAtStart ? hiddenBottom : shownBottom;
// We coerce the given inset within the limits of the hidden and shown insets
final int coercedBottom = coerceIn(inset, hiddenBottom, shownBottom);
final int consumedDy = controller.getCurrentInsets().bottom - coercedBottom;
// Finally update the insets in the WindowInsetsAnimationController using
// setInsetsAndAlpha().
controller.setInsetsAndAlpha(
// Here we update the animating insets. This is what controls where the IME is displayed.
// It is also passed through to views via their WindowInsetsAnimation.Callback.
Insets.of(0, 0, 0, coercedBottom),
// This controls the alpha value. We don't want to alter the alpha so use 1f
1f,
// Finally we calculate the animation progress fraction. This value is passed through
// to any WindowInsetsAnimation.Callbacks, but it is not used by the system.
(coercedBottom - startBottom) / (float) (endBottom - startBottom)
);
return consumedDy;
}
/**
* Return `true` if an inset animation is in progress.
*/
public boolean isInsetAnimationInProgress() {
return insetsAnimationController != null;
}
/**
* Return `true` if an inset animation is currently finishing.
*/
public boolean isInsetAnimationFinishing() {
return currentSpringAnimation != null;
}
/**
* Return `true` if a request to control an inset animation is in progress.
*/
public boolean isInsetAnimationRequestPending() {
return pendingRequestCancellationSignal != null;
}
/**
* Cancel the current [WindowInsetsAnimationControllerCompat]. We immediately finish
* the animation, reverting back to the state at the start of the gesture.
*/
public void cancel() {
if (insetsAnimationController != null) {
insetsAnimationController.finish(isImeShownAtStart);
}
if (pendingRequestCancellationSignal != null) {
pendingRequestCancellationSignal.cancel();
}
if (currentSpringAnimation != null) {
// Cancel the current spring animation
currentSpringAnimation.cancel();
}
reset();
}
/**
* Finish the current [WindowInsetsAnimationControllerCompat] immediately.
*/
public void finish() {
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
if (controller == null) {
// If we don't currently have a controller, cancel any pending request and return
if (pendingRequestCancellationSignal != null) {
pendingRequestCancellationSignal.cancel();
}
return;
}
final int current = controller.getCurrentInsets().bottom;
final int shown = controller.getShownStateInsets().bottom;
final int hidden = controller.getHiddenStateInsets().bottom;
// The current inset matches either the shown/hidden inset, finish() immediately
if (current == shown) {
controller.finish(true);
} else if (current == hidden) {
controller.finish(false);
} else {
// Otherwise, we'll look at the current position...
if (controller.getCurrentFraction() >= SCROLL_THRESHOLD) {
// If the IME is past the 'threshold' we snap to the toggled state
controller.finish(!isImeShownAtStart);
} else {
// ...otherwise, we snap back to the original visibility
controller.finish(isImeShownAtStart);
}
}
}
/**
* Finish the current [WindowInsetsAnimationControllerCompat]. We finish the animation,
* animating to the end state if necessary.
*
* @param velocityY the velocity of the touch gesture which caused this call to [animateToFinish].
* Can be `null` if velocity is not available.
*/
public void animateToFinish(@Nullable final Float velocityY) {
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
if (controller == null) {
// If we don't currently have a controller, cancel any pending request and return
if (pendingRequestCancellationSignal != null) {
pendingRequestCancellationSignal.cancel();
}
return;
}
final int current = controller.getCurrentInsets().bottom;
final int shown = controller.getShownStateInsets().bottom;
final int hidden = controller.getHiddenStateInsets().bottom;
if (velocityY != null) {
// If we have a velocity, we can use it's direction to determine
// the visibility. Upwards == visible
animateImeToVisibility(velocityY > 0, velocityY);
} else if (current == shown) {
// The current inset matches either the shown/hidden inset, finish() immediately
controller.finish(true);
} else if (current == hidden) {
controller.finish(false);
} else {
// Otherwise, we'll look at the current position...
if (controller.getCurrentFraction() >= SCROLL_THRESHOLD) {
// If the IME is past the 'threshold' we animate it to the toggled state
animateImeToVisibility(!isImeShownAtStart, null);
} else {
// ...otherwise, we animate it back to the original visibility
animateImeToVisibility(isImeShownAtStart, null);
}
}
}
private void onRequestReady(@NonNull final WindowInsetsAnimationControllerCompat controller) {
// The request is ready, so clear out the pending cancellation signal
pendingRequestCancellationSignal = null;
// Store the current WindowInsetsAnimationController
insetsAnimationController = controller;
// Call any pending callback
if (pendingRequestOnReadyListener != null) {
pendingRequestOnReadyListener.onRequestReady(controller);
}
pendingRequestOnReadyListener = null;
}
/**
* Resets all of our internal state.
*/
private void reset() {
// Clear all of our internal state
insetsAnimationController = null;
pendingRequestCancellationSignal = null;
isImeShownAtStart = false;
if (currentSpringAnimation != null) {
currentSpringAnimation.cancel();
}
currentSpringAnimation = null;
pendingRequestOnReadyListener = null;
}
/**
* Animate the IME to a given visibility.
*
* @param visible `true` to animate the IME to it's fully shown state, `false` to it's
* fully hidden state.
* @param velocityY the velocity of the touch gesture which caused this call. Can be `null`
* if velocity is not available.
*/
private void animateImeToVisibility(final boolean visible, @Nullable final Float velocityY) {
if (insetsAnimationController == null) {
throw new IllegalStateException("Controller should not be null");
}
final WindowInsetsAnimationControllerCompat controller = insetsAnimationController;
final FloatPropertyCompat<Object> property = new FloatPropertyCompat<Object>("property") {
@Override
public float getValue(final Object object) {
return controller.getCurrentInsets().bottom;
}
@Override
public void setValue(final Object object, final float value) {
if (insetsAnimationController == null) {
return;
}
insetTo((int) value);
}
};
final float finalPosition = visible ? controller.getShownStateInsets().bottom
: controller.getHiddenStateInsets().bottom;
final SpringForce force = new SpringForce(finalPosition)
// Tweak the damping value, to remove any bounciness.
.setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
// The stiffness value controls the strength of the spring animation, which
// controls the speed. Medium (the default) is a good value, but feel free to
// play around with this value.
.setStiffness(SpringForce.STIFFNESS_MEDIUM);
ViewUtils.springAnimationOf(this, property, finalPosition)
.setSpring(force)
.setStartVelocity(velocityY != null ? velocityY : 0)
.addEndListener((animation, canceled, value, velocity) -> {
if (animation == currentSpringAnimation) {
currentSpringAnimation = null;
}
// Once the animation has ended, finish the controller
finish();
}).start();
}
private int coerceIn(final int v, final int min, final int max) {
if (v >= min && v <= max) {
return v;
}
if (v < min) {
return min;
}
return max;
}
public void setAnimationControlListener(final WindowInsetsAnimationControlListenerCompat listener) {
fwdListener = listener;
}
public interface OnRequestReadyListener {
void onRequestReady(WindowInsetsAnimationControllerCompat windowInsetsAnimationControllerCompat);
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package awais.instagrabber.customviews.helpers;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.List;
/**
* A [WindowInsetsAnimationCompat.Callback] which will translate/move the given view during any
* inset animations of the given inset type.
* <p>
* This class works in tandem with [RootViewDeferringInsetsCallback] to support the deferring of
* certain [WindowInsetsCompat.Type] values during a [WindowInsetsAnimationCompat], provided in
* [deferredInsetTypes]. The values passed into this constructor should match those which
* the [RootViewDeferringInsetsCallback] is created with.
*/
public class TranslateDeferringInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback {
private final View view;
private final int persistentInsetTypes;
private final int deferredInsetTypes;
private boolean shouldTranslate = true;
private int kbHeight;
public TranslateDeferringInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes) {
this(view, persistentInsetTypes, deferredInsetTypes, DISPATCH_MODE_STOP);
}
/**
* @param view the view to translate from it's start to end state
* @param persistentInsetTypes the bitmask of any inset types which were handled as part of the
* layout
* @param deferredInsetTypes the bitmask of insets types which should be deferred until after
* any [WindowInsetsAnimationCompat]s have ended
* @param dispatchMode The dispatch mode for this callback.
* See [WindowInsetsAnimationCompat.Callback.getDispatchMode].
*/
public TranslateDeferringInsetsAnimationCallback(final View view,
final int persistentInsetTypes,
final int deferredInsetTypes,
final int dispatchMode) {
super(dispatchMode);
if ((persistentInsetTypes & deferredInsetTypes) != 0) {
throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " +
"any of same WindowInsetsCompat.Type values");
}
this.view = view;
this.persistentInsetTypes = persistentInsetTypes;
this.deferredInsetTypes = deferredInsetTypes;
}
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets,
@NonNull final List<WindowInsetsAnimationCompat> runningAnimations) {
// onProgress() is called when any of the running animations progress...
// First we get the insets which are potentially deferred
final Insets typesInset = insets.getInsets(deferredInsetTypes);
// Then we get the persistent inset types which are applied as padding during layout
final Insets otherInset = insets.getInsets(persistentInsetTypes);
// Now that we subtract the two insets, to calculate the difference. We also coerce
// the insets to be >= 0, to make sure we don't use negative insets.
final Insets subtract = Insets.subtract(typesInset, otherInset);
final Insets diff = Insets.max(subtract, Insets.NONE);
// The resulting `diff` insets contain the values for us to apply as a translation
// to the view
view.setTranslationX(diff.left - diff.right);
view.setTranslationY(shouldTranslate ? diff.top - diff.bottom : -kbHeight);
return insets;
}
@Override
public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) {
try {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view);
if (kbHeight == 0) {
if (rootWindowInsets == null) return;
final Insets imeInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.ime());
final Insets navBarInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.navigationBars());
kbHeight = imeInsets.bottom - navBarInsets.bottom;
}
// Once the animation has ended, reset the translation values
view.setTranslationX(0f);
final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
float translationY = 0;
if (!shouldTranslate) {
translationY = -kbHeight;
if (visible) {
translationY = 0;
}
}
view.setTranslationY(translationY);
} finally {
shouldTranslate = true;
}
}
public void setShouldTranslate(final boolean shouldTranslate) {
this.shouldTranslate = shouldTranslate;
}
}

View File

@ -134,7 +134,14 @@ public class ProfilePicDialogFragment extends DialogFragment {
@Override @Override
public void onSuccess(final User result) { public void onSuccess(final User result) {
if (result != null) { if (result != null) {
setupPhoto(result.getHDProfilePicUrl()); final String url = result.getHDProfilePicUrl();
if (url == null) {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, R.string.no_profile_pic_found, Toast.LENGTH_LONG).show();
return;
}
setupPhoto(url);
} }
} }

View File

@ -190,16 +190,15 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
final View profilePicView, final View profilePicView,
final View mainPostImage, final View mainPostImage,
final int position) { final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(CollectionPostsFragment.this);
.builder(feedModel); final Bundle bundle = new Bundle();
if (position >= 0) { bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
builder.setPosition(position); bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
} }
}; };
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@ -243,8 +242,10 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
final TransitionSet transitionSet = new TransitionSet(); final TransitionSet transitionSet = new TransitionSet();
final Context context = getContext();
if (context == null) return;
transitionSet.addTransition(new ChangeBounds()) transitionSet.addTransition(new ChangeBounds())
.addTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move)) .addTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.move))
.setDuration(200); .setDuration(200);
setSharedElementEnterTransition(transitionSet); setSharedElementEnterTransition(transitionSet);
postponeEnterTransition(); postponeEnterTransition();
@ -280,7 +281,8 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.collection_posts_menu, menu); // delaying to make toolbar resume animation smooth, otherwise lags
binding.getRoot().postDelayed(() -> inflater.inflate(R.menu.collection_posts_menu, menu), 500);
} }
@Override @Override
@ -288,14 +290,13 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
if (item.getItemId() == R.id.layout) { if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences(); showPostsLayoutPreferences();
return true; return true;
} } else if (item.getItemId() == R.id.delete) {
else if (item.getItemId() == R.id.delete) {
final Context context = getContext(); final Context context = getContext();
if (context == null) return false;
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(R.string.delete_collection) .setTitle(R.string.delete_collection)
.setMessage(R.string.delete_collection_note) .setMessage(R.string.delete_collection_note)
.setPositiveButton(R.string.confirm, (d, w) -> { .setPositiveButton(R.string.confirm, (d, w) -> collectionService.deleteCollection(
collectionService.deleteCollection(
savedCollection.getId(), savedCollection.getId(),
new ServiceCallback<String>() { new ServiceCallback<String>() {
@Override @Override
@ -308,23 +309,22 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
Log.e(TAG, "Error deleting collection", t); Log.e(TAG, "Error deleting collection", t);
try { try {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
} }
catch(final Throwable e) {} }))
}
});
})
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
} } else if (item.getItemId() == R.id.edit) {
else if (item.getItemId() == R.id.edit) {
final Context context = getContext(); final Context context = getContext();
if (context == null) return false;
final EditText input = new EditText(context); final EditText input = new EditText(context);
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(R.string.edit_collection) .setTitle(R.string.edit_collection)
.setView(input) .setView(input)
.setPositiveButton(R.string.confirm, (d, w) -> { .setPositiveButton(R.string.confirm, (d, w) -> collectionService.editCollectionName(
collectionService.editCollectionName(
savedCollection.getId(), savedCollection.getId(),
input.getText().toString(), input.getText().toString(),
new ServiceCallback<String>() { new ServiceCallback<String>() {
@ -338,12 +338,12 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
Log.e(TAG, "Error editing collection", t); Log.e(TAG, "Error editing collection", t);
try { try {
final Context context = getContext();
if (context == null) return;
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
} catch (final Throwable ignored) {}
} }
catch(final Throwable e) {} }))
}
});
})
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show(); .show();
} }

View File

@ -2,6 +2,7 @@ package awais.instagrabber.fragments;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -102,8 +103,13 @@ public class FavoritesFragment extends Fragment {
// Log.d(TAG, "locationId: " + locationId); // Log.d(TAG, "locationId: " + locationId);
final NavController navController = NavHostFragment.findNavController(this); final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle(); final Bundle bundle = new Bundle();
bundle.putString("locationId", locationId); try {
bundle.putLong("locationId", Long.parseLong(locationId));
navController.navigate(R.id.action_global_locationFragment, bundle); navController.navigate(R.id.action_global_locationFragment, bundle);
} catch (Exception e) {
Log.e(TAG, "init: ", e);
return;
}
break; break;
} }
case HASHTAG: { case HASHTAG: {

View File

@ -1,5 +1,6 @@
package awais.instagrabber.fragments; package awais.instagrabber.fragments;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -23,13 +24,14 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.motion.widget.MotionScene;
import androidx.core.content.PermissionChecker; import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.BaseTransientBottomBar;
@ -76,9 +78,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "HashTagFragment"; private static final String TAG = "HashTagFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
@ -88,7 +87,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private FragmentHashtagBinding binding; private FragmentHashtagBinding binding;
private CoordinatorLayout root; private MotionLayout root;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private boolean hasStories = false; private boolean hasStories = false;
private boolean opening = false; private boolean opening = false;
@ -227,17 +226,15 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
return; return;
} }
opening = true; opening = true;
final PostViewV2Fragment.Builder builder = PostViewV2Fragment.builder(feedModel); final NavController navController = NavHostFragment.findNavController(HashTagFragment.this);
if (position >= 0) { final Bundle bundle = new Bundle();
builder.setPosition(position); bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
final FragmentManager fragmentManager = getChildFragmentManager();
if (fragmentManager.isDestroyed()) return;
builder.build().show(fragmentManager, "post_view");
opening = false; opening = false;
} }
}; };
@ -307,13 +304,11 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
if (root != null) { if (root != null) {
shouldRefresh = false; shouldRefresh = false;
fragmentActivity.setCollapsingView(hashtagDetailsBinding.getRoot());
return root; return root;
} }
binding = FragmentHashtagBinding.inflate(inflater, container, false); binding = FragmentHashtagBinding.inflate(inflater, container, false);
root = binding.getRoot(); root = binding.getRoot();
hashtagDetailsBinding = LayoutHashtagDetailsBinding.inflate(inflater, fragmentActivity.getCollapsingToolbarView(), false); hashtagDetailsBinding = binding.header;
fragmentActivity.setCollapsingView(hashtagDetailsBinding.getRoot());
return root; return root;
} }
@ -370,14 +365,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (hashtagDetailsBinding != null) {
fragmentActivity.removeCollapsingView(hashtagDetailsBinding.getRoot());
}
}
private void init() { private void init() {
if (getArguments() == null) return; if (getArguments() == null) return;
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments()); final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
@ -402,6 +389,17 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setSelectionModeCallback(selectionModeCallback) .setSelectionModeCallback(selectionModeCallback)
.init(); .init();
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
binding.posts.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
final boolean canScrollVertically = recyclerView.canScrollVertically(-1);
final MotionScene.Transition transition = root.getTransition(R.id.transition);
if (transition != null) {
transition.setEnable(!canScrollVertically);
}
}
});
} }
private void setHashtagDetails() { private void setHashtagDetails() {
@ -428,9 +426,10 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
final long userId = CookieUtils.getUserIdFromCookie(cookie); final long userId = CookieUtils.getUserIdFromCookie(cookie);
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID); final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
if (csrfToken != null && userId != 0 && deviceUuid != null) { if (csrfToken != null && userId != 0) {
hashtagDetailsBinding.btnFollowTag.setClickable(false); hashtagDetailsBinding.btnFollowTag.setClickable(false);
tagsService.changeFollow(hashtagModel.getFollowing() == FollowingType.FOLLOWING ? "unfollow" : "follow", tagsService.changeFollow(
hashtagModel.getFollowing() == FollowingType.FOLLOWING ? "unfollow" : "follow",
hashtag, hashtag,
csrfToken, csrfToken,
userId, userId,
@ -454,21 +453,22 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
hashtagDetailsBinding.btnFollowTag.setClickable(true); hashtagDetailsBinding.btnFollowTag.setClickable(true);
Log.e(TAG, "onFailure: ", t); Log.e(TAG, "onFailure: ", t);
final String message = t.getMessage(); final String message = t.getMessage();
Snackbar.make(root, Snackbar.make(
message != null ? message root,
: getString(R.string.downloader_unknown_error), message != null ? message : getString(R.string.downloader_unknown_error),
BaseTransientBottomBar.LENGTH_LONG) BaseTransientBottomBar.LENGTH_LONG)
.show(); .show();
} }
}); });
return;
} }
}); });
} else { } else {
hashtagDetailsBinding.btnFollowTag.setVisibility(View.GONE); hashtagDetailsBinding.btnFollowTag.setVisibility(View.GONE);
} }
hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE); hashtagDetailsBinding.favChip.setVisibility(View.VISIBLE);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); final Context context = getContext();
if (context == null) return;
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
favoriteRepository.getFavorite(hashtag, FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() { favoriteRepository.getFavorite(hashtag, FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() {
@Override @Override
public void onSuccess(final Favorite result) { public void onSuccess(final Favorite result) {
@ -557,7 +557,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void showSnackbar(final String message) { private void showSnackbar(final String message) {
final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); @SuppressLint("ShowToast") final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG);
snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss())
.setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE)
.setAnchorView(fragmentActivity.getBottomNavView()) .setAnchorView(fragmentActivity.getBottomNavView())

View File

@ -22,13 +22,14 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.motion.widget.MotionScene;
import androidx.core.content.PermissionChecker; import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController; import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.BaseTransientBottomBar;
@ -73,9 +74,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment"; private static final String TAG = "LocationFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
@ -83,7 +81,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private FragmentLocationBinding binding; private FragmentLocationBinding binding;
private CoordinatorLayout root; private MotionLayout root;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private boolean hasStories = false; private boolean hasStories = false;
private boolean opening = false; private boolean opening = false;
@ -219,18 +217,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
return; return;
} }
opening = true; opening = true;
final PostViewV2Fragment.Builder builder = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(LocationFragment.this);
.builder(feedModel); final Bundle bundle = new Bundle();
if (position >= 0) { bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
builder.setPosition(position); bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
final FragmentManager fragmentManager = getChildFragmentManager();
if (fragmentManager.isDestroyed()) return;
builder.build().show(fragmentManager, "post_view");
opening = false; opening = false;
} }
}; };
@ -302,13 +297,11 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
@Nullable final Bundle savedInstanceState) { @Nullable final Bundle savedInstanceState) {
if (root != null) { if (root != null) {
shouldRefresh = false; shouldRefresh = false;
fragmentActivity.setCollapsingView(locationDetailsBinding.getRoot());
return root; return root;
} }
binding = FragmentLocationBinding.inflate(inflater, container, false); binding = FragmentLocationBinding.inflate(inflater, container, false);
root = binding.getRoot(); root = binding.getRoot();
locationDetailsBinding = LayoutLocationDetailsBinding.inflate(inflater, fragmentActivity.getCollapsingToolbarView(), false); locationDetailsBinding = binding.header;
fragmentActivity.setCollapsingView(locationDetailsBinding.getRoot());
return root; return root;
} }
@ -365,14 +358,6 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (locationDetailsBinding != null) {
fragmentActivity.removeCollapsingView(locationDetailsBinding.getRoot());
}
}
private void init() { private void init() {
if (getArguments() == null) return; if (getArguments() == null) return;
final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments()); final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments());
@ -393,6 +378,17 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
.setSelectionModeCallback(selectionModeCallback) .setSelectionModeCallback(selectionModeCallback)
.init(); .init();
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
binding.posts.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
final boolean canScrollVertically = recyclerView.canScrollVertically(-1);
final MotionScene.Transition transition = root.getTransition(R.id.transition);
if (transition != null) {
transition.setEnable(!canScrollVertically);
}
}
});
} }
private void fetchLocationModel() { private void fetchLocationModel() {

View File

@ -20,6 +20,7 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -108,11 +109,14 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
mediaService.fetch(mediaId, new ServiceCallback<Media>() { mediaService.fetch(mediaId, new ServiceCallback<Media>() {
@Override @Override
public void onSuccess(final Media feedModel) { public void onSuccess(final Media feedModel) {
final PostViewV2Fragment fragment = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(NotificationsViewerFragment.this);
.builder(feedModel) final Bundle bundle = new Bundle();
.build(); bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
fragment.setOnShowListener(dialog -> alertDialog.dismiss()); try {
fragment.show(getChildFragmentManager(), "post_view"); navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "onSuccess: ", e);
}
} }
@Override @Override

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -50,6 +51,7 @@ import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = SavedViewerFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
@ -171,16 +173,15 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
final View profilePicView, final View profilePicView,
final View mainPostImage, final View mainPostImage,
final int position) { final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(SavedViewerFragment.this);
.builder(feedModel); final Bundle bundle = new Bundle();
if (position >= 0) { bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
builder.setPosition(position); bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
} }
}; };
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {

View File

@ -23,9 +23,12 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.common.collect.Iterables;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.FeedStoriesListAdapter; import awais.instagrabber.adapters.FeedStoriesListAdapter;
@ -58,15 +61,17 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
private StoriesService storiesService; private StoriesService storiesService;
private Context context; private Context context;
private String type; private String type;
private String currentQuery;
private String endCursor = null; private String endCursor = null;
private FeedStoriesListAdapter adapter; private FeedStoriesListAdapter adapter;
private MenuItem menuSearch;
private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() { private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() {
@Override @Override
public void onFeedStoryClick(final FeedStoryModel model, final int position) { public void onFeedStoryClick(final FeedStoryModel model) {
if (model == null) return; if (model == null) return;
final List<FeedStoryModel> feedStoryModels = feedStoriesViewModel.getList().getValue();
if (feedStoryModels == null) return;
final int position = Iterables.indexOf(feedStoryModels, feedStoryModel -> feedStoryModel != null
&& Objects.equals(feedStoryModel.getStoryMediaId(), model.getStoryMediaId()));
final NavDirections action = StoryListViewerFragmentDirections final NavDirections action = StoryListViewerFragmentDirections
.actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); .actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position));
NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action);
@ -153,7 +158,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.search, menu); inflater.inflate(R.menu.search, menu);
menuSearch = menu.findItem(R.id.action_search); final MenuItem menuSearch = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) menuSearch.getActionView(); final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setQueryHint(getResources().getString(R.string.action_search)); searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@ -166,7 +171,6 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr
@Override @Override
public boolean onQueryTextChange(final String query) { public boolean onQueryTextChange(final String query) {
if (adapter != null) { if (adapter != null) {
currentQuery = query;
adapter.getFilter().filter(query); adapter.getFilter().filter(query);
} }
return true; return true;

View File

@ -36,6 +36,7 @@ import androidx.core.view.GestureDetectorCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -119,6 +120,7 @@ public class StoryViewerFragment extends Fragment {
private View root; private View root;
private FragmentStoryViewerBinding binding; private FragmentStoryViewerBinding binding;
private String currentStoryUsername; private String currentStoryUsername;
private String highlightTitle;
private StoriesAdapter storiesAdapter; private StoriesAdapter storiesAdapter;
private SwipeEvent swipeEvent; private SwipeEvent swipeEvent;
private GestureDetectorCompat gestureDetector; private GestureDetectorCompat gestureDetector;
@ -274,7 +276,9 @@ public class StoryViewerFragment extends Fragment {
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
releasePlayer(); if (player != null) {
player.pause();
}
} }
@Override @Override
@ -463,11 +467,14 @@ public class StoryViewerFragment extends Fragment {
mediaService.fetch(Long.parseLong(mediaId), new ServiceCallback<Media>() { mediaService.fetch(Long.parseLong(mediaId), new ServiceCallback<Media>() {
@Override @Override
public void onSuccess(final Media feedModel) { public void onSuccess(final Media feedModel) {
final PostViewV2Fragment fragment = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(StoryViewerFragment.this);
.builder(feedModel) final Bundle bundle = new Bundle();
.build(); bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
fragment.setOnShowListener(dialog -> alertDialog.dismiss()); try {
fragment.show(getChildFragmentManager(), "post_view"); navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
}
} }
@Override @Override
@ -482,16 +489,16 @@ public class StoryViewerFragment extends Fragment {
if (tag instanceof PollModel) { if (tag instanceof PollModel) {
poll = (PollModel) tag; poll = (PollModel) tag;
if (poll.getMyChoice() > -1) { if (poll.getMyChoice() > -1) {
new AlertDialog.Builder(context).setTitle(R.string.voted_story_poll) new AlertDialog.Builder(context)
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, .setTitle(R.string.voted_story_poll)
.setAdapter(new ArrayAdapter<>(
context,
android.R.layout.simple_list_item_1,
new String[]{ new String[]{
(poll.getMyChoice() == 0 ? "" : "") + poll (poll.getMyChoice() == 0 ? "" : "") + poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
.getLeftChoice() + " (" + poll (poll.getMyChoice() == 1 ? "" : "") + poll.getRightChoice() + " (" + poll.getRightCount() + ")"
.getLeftCount() + ")", }),
(poll.getMyChoice() == 1 ? "" : "") + poll null)
.getRightChoice() + " (" + poll
.getRightCount() + ")"
}), null)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show(); .show();
} else { } else {
@ -724,7 +731,7 @@ public class StoryViewerFragment extends Fragment {
final HighlightModel model = models.get(currentFeedStoryIndex); final HighlightModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId(); currentStoryMediaId = model.getId();
fetchOptions = StoryViewerOptions.forHighlight(model.getId()); fetchOptions = StoryViewerOptions.forHighlight(model.getId());
currentStoryUsername = model.getTitle(); highlightTitle = model.getTitle();
break; break;
} }
case FEED_STORY_POSITION: { case FEED_STORY_POSITION: {
@ -824,8 +831,8 @@ public class StoryViewerFragment extends Fragment {
if (type == Type.HIGHLIGHT) { if (type == Type.HIGHLIGHT) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBarTitle = options.getName(); actionBarTitle = highlightTitle;
actionBar.setTitle(options.getName()); actionBar.setTitle(highlightTitle);
} }
} else if (hasUsername) { } else if (hasUsername) {
currentStoryUsername = currentStoryUsername.replace("@", ""); currentStoryUsername = currentStoryUsername.replace("@", "");

View File

@ -10,6 +10,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -51,8 +52,8 @@ import awais.instagrabber.databinding.FragmentTopicPostsBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.DiscoverFragmentDirections; import awais.instagrabber.fragments.main.DiscoverFragmentDirections;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.repositories.responses.discover.TopicCluster;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.discover.TopicCluster;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
@ -63,6 +64,7 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = TopicPostsFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
@ -182,16 +184,15 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O
final View profilePicView, final View profilePicView,
final View mainPostImage, final View mainPostImage,
final int position) { final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(TopicPostsFragment.this);
.builder(feedModel); final Bundle bundle = new Bundle();
if (position >= 0) { bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
builder.setPosition(position); bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
} }
}; };
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {

View File

@ -16,6 +16,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
@ -27,6 +28,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.badge.BadgeDrawable; import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils; import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.internal.ToolbarUtils;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import java.util.List; import java.util.List;
@ -96,13 +98,15 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
} }
} }
@SuppressLint("UnsafeExperimentalUsageError") @SuppressLint({"UnsafeExperimentalUsageError", "UnsafeOptInUsageError"})
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
unregisterReceiver(); unregisterReceiver();
isPendingRequestTotalBadgeAttached = false; isPendingRequestTotalBadgeAttached = false;
if (pendingRequestTotalBadgeDrawable != null) { @SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
if (pendingRequestTotalBadgeDrawable != null && menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId()); BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
pendingRequestTotalBadgeDrawable = null; pendingRequestTotalBadgeDrawable = null;
} }
@ -176,7 +180,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
if (inboxAdapter == null) return; if (inboxAdapter == null) return;
inboxAdapter.submitList(list, () -> { inboxAdapter.submitList(list, () -> {
if (!scrollToTop) return; if (!scrollToTop) return;
binding.inboxList.smoothScrollToPosition(0); binding.inboxList.post(() -> binding.inboxList.smoothScrollToPosition(0));
scrollToTop = false; scrollToTop = false;
}); });
}; };
@ -204,7 +208,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge); viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge);
} }
@SuppressLint("UnsafeExperimentalUsageError") @SuppressLint({"UnsafeExperimentalUsageError", "UnsafeOptInUsageError"})
private void attachPendingRequestsBadge(@Nullable final Integer count) { private void attachPendingRequestsBadge(@Nullable final Integer count) {
if (pendingRequestsMenuItem == null) { if (pendingRequestsMenuItem == null) {
final Handler handler = new Handler(); final Handler handler = new Handler();
@ -217,7 +221,11 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context); pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context);
} }
if (count == null || count == 0) { if (count == null || count == 0) {
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId()); BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
}
isPendingRequestTotalBadgeAttached = false; isPendingRequestTotalBadgeAttached = false;
pendingRequestTotalBadgeDrawable.setNumber(0); pendingRequestTotalBadgeDrawable.setNumber(0);
pendingRequestsMenuItem.setVisible(false); pendingRequestsMenuItem.setVisible(false);

View File

@ -26,14 +26,20 @@ import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsAnimationControlListenerCompat;
import androidx.core.view.WindowInsetsAnimationControllerCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@ -51,6 +57,7 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.material.badge.BadgeDrawable; import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils; import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.internal.ToolbarUtils;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -72,14 +79,21 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemOrHeader;
import awais.instagrabber.adapters.DirectReactionsAdapter; import awais.instagrabber.adapters.DirectReactionsAdapter;
import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder;
import awais.instagrabber.animations.CubicBezierInterpolator; import awais.instagrabber.animations.CubicBezierInterpolator;
import awais.instagrabber.customviews.InsetsAnimationLinearLayout;
import awais.instagrabber.customviews.KeyNotifyingEmojiEditText;
import awais.instagrabber.customviews.RecordView; import awais.instagrabber.customviews.RecordView;
import awais.instagrabber.customviews.Tooltip; import awais.instagrabber.customviews.Tooltip;
import awais.instagrabber.customviews.emoji.Emoji; import awais.instagrabber.customviews.emoji.Emoji;
import awais.instagrabber.customviews.emoji.EmojiBottomSheetDialog;
import awais.instagrabber.customviews.emoji.EmojiPicker;
import awais.instagrabber.customviews.helpers.ControlFocusInsetsAnimationCallback;
import awais.instagrabber.customviews.helpers.EmojiPickerInsetsAnimationCallback;
import awais.instagrabber.customviews.helpers.HeaderItemDecoration; import awais.instagrabber.customviews.helpers.HeaderItemDecoration;
import awais.instagrabber.customviews.helpers.HeightProvider;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge; import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.customviews.helpers.SimpleImeAnimationController;
import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCallback; import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCallback;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter; import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.customviews.helpers.TranslateDeferringInsetsAnimationCallback;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding; import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment; import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
import awais.instagrabber.dialogs.GifPickerBottomDialogFragment; import awais.instagrabber.dialogs.GifPickerBottomDialogFragment;
@ -111,10 +125,8 @@ import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectThreadViewModel; import awais.instagrabber.viewmodels.DirectThreadViewModel;
import awais.instagrabber.viewmodels.factories.DirectThreadViewModelFactory; import awais.instagrabber.viewmodels.factories.DirectThreadViewModelFactory;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; public class DirectMessageThreadFragment extends Fragment implements DirectReactionsAdapter.OnReactionClickListener,
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN; EmojiPicker.OnEmojiClickListener {
public class DirectMessageThreadFragment extends Fragment implements DirectReactionsAdapter.OnReactionClickListener {
private static final String TAG = DirectMessageThreadFragment.class.getSimpleName(); private static final String TAG = DirectMessageThreadFragment.class.getSimpleName();
private static final int STORAGE_PERM_REQUEST_CODE = 8020; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000; private static final int AUDIO_RECORD_PERM_REQUEST_CODE = 1000;
@ -124,7 +136,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private DirectItemsAdapter itemsAdapter; private DirectItemsAdapter itemsAdapter;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private DirectThreadViewModel viewModel; private DirectThreadViewModel viewModel;
private ConstraintLayout root; private InsetsAnimationLinearLayout root;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private List<DirectItemOrHeader> itemOrHeaders; private List<DirectItemOrHeader> itemOrHeaders;
private List<User> users; private List<User> users;
@ -134,14 +146,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private ActionBar actionBar; private ActionBar actionBar;
private AppStateViewModel appStateViewModel; private AppStateViewModel appStateViewModel;
private Runnable prevTitleRunnable; private Runnable prevTitleRunnable;
private int originalSoftInputMode;
private AnimatorSet animatorSet; private AnimatorSet animatorSet;
private boolean isEmojiPickerShown;
private boolean isKbShown;
private HeightProvider heightProvider;
private boolean isRecording; private boolean isRecording;
private boolean wasKbShowing;
private int keyboardHeight = Utils.convertDpToPx(250);
private DirectItemReactionDialogFragment reactionDialogFragment; private DirectItemReactionDialogFragment reactionDialogFragment;
private DirectItem itemToForward; private DirectItem itemToForward;
private MutableLiveData<Object> backStackSavedStateResultLiveData; private MutableLiveData<Object> backStackSavedStateResultLiveData;
@ -159,6 +165,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private LiveData<Integer> pendingRequestsCountLiveData; private LiveData<Integer> pendingRequestsCountLiveData;
private LiveData<List<User>> usersLiveData; private LiveData<List<User>> usersLiveData;
private boolean autoMarkAsSeen = false; private boolean autoMarkAsSeen = false;
private MenuItem markAsSeenMenuItem;
private Media tempMedia;
private DirectItem addReactionItem;
private TranslateDeferringInsetsAnimationCallback inputHolderAnimationCallback;
private TranslateDeferringInsetsAnimationCallback chatsAnimationCallback;
private EmojiPickerInsetsAnimationCallback emojiPickerAnimationCallback;
private boolean hasKbOpenedOnce;
private boolean wasToggled;
private final AppExecutors appExecutors = AppExecutors.getInstance(); private final AppExecutors appExecutors = AppExecutors.getInstance();
private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() { private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() {
@ -224,8 +238,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
return; return;
} }
final PostViewV2Fragment.Builder builder = PostViewV2Fragment.builder(media); final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this);
builder.build().show(getChildFragmentManager(), "post_view"); final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
}
} }
@Override @Override
@ -285,8 +305,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
cb.apply(item); cb.apply(item);
} }
} }
};
@Override
public void onAddReactionListener(final DirectItem item) {
if (item == null) return;
addReactionItem = item;
final EmojiBottomSheetDialog emojiBottomSheetDialog = EmojiBottomSheetDialog.newInstance();
emojiBottomSheetDialog.show(getChildFragmentManager(), EmojiBottomSheetDialog.TAG);
}
};
private final DirectItemLongClickListener directItemLongClickListener = position -> { private final DirectItemLongClickListener directItemLongClickListener = position -> {
// viewModel.setSelectedPosition(position); // viewModel.setSelectedPosition(position);
}; };
@ -315,8 +342,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
backStackSavedStateResultLiveData.postValue(null); backStackSavedStateResultLiveData.postValue(null);
}; };
private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0); private final MutableLiveData<Integer> inputLength = new MutableLiveData<>(0);
private MenuItem markAsSeenMenuItem; private final MutableLiveData<Boolean> emojiPickerVisible = new MutableLiveData<>(false);
private Media tempMedia; private final MutableLiveData<Boolean> kbVisible = new MutableLiveData<>(false);
private final OnBackPressedCallback onEmojiPickerBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
emojiPickerVisible.postValue(false);
}
};
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -327,11 +360,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
final Bundle arguments = getArguments(); final Bundle arguments = getArguments();
if (arguments == null) return; if (arguments == null) return;
final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(arguments); final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(arguments);
final User currentUser = appStateViewModel.getCurrentUser();
if (currentUser == null) return;
final DirectThreadViewModelFactory viewModelFactory = new DirectThreadViewModelFactory( final DirectThreadViewModelFactory viewModelFactory = new DirectThreadViewModelFactory(
fragmentActivity.getApplication(), fragmentActivity.getApplication(),
fragmentArgs.getThreadId(), fragmentArgs.getThreadId(),
fragmentArgs.getPending(), fragmentArgs.getPending(),
appStateViewModel.getCurrentUser() currentUser
); );
viewModel = new ViewModelProvider(this, viewModelFactory).get(DirectThreadViewModel.class); viewModel = new ViewModelProvider(this, viewModelFactory).get(DirectThreadViewModel.class);
setHasOptionsMenu(true); setHasOptionsMenu(true);
@ -353,13 +388,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
return root; return root;
} }
tooltip = new Tooltip(context, root, getResources().getColor(R.color.grey_400), getResources().getColor(R.color.black)); tooltip = new Tooltip(context, root, getResources().getColor(R.color.grey_400), getResources().getColor(R.color.black));
originalSoftInputMode = fragmentActivity.getWindow().getAttributes().softInputMode;
// todo check has camera and remove view // todo check has camera and remove view
return root; return root;
} }
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
// WindowCompat.setDecorFitsSystemWindows(fragmentActivity.getWindow(), false);
if (!shouldRefresh) return; if (!shouldRefresh) return;
init(); init();
binding.send.post(() -> initialSendX = binding.send.getX()); binding.send.post(() -> initialSendX = binding.send.getX());
@ -386,7 +421,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
final DirectMessageThreadFragmentDirections.ActionThreadToSettings directions = DirectMessageThreadFragmentDirections final DirectMessageThreadFragmentDirections.ActionThreadToSettings directions = DirectMessageThreadFragmentDirections
.actionThreadToSettings(viewModel.getThreadId(), null); .actionThreadToSettings(viewModel.getThreadId(), null);
final Boolean pending = viewModel.isPending().getValue(); final Boolean pending = viewModel.isPending().getValue();
directions.setPending(pending == null ? false : pending); directions.setPending(pending != null && pending);
NavHostFragment.findNavController(this).navigate(directions); NavHostFragment.findNavController(this).navigate(directions);
return true; return true;
} }
@ -414,14 +449,10 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
case SUCCESS: case SUCCESS:
Toast.makeText(context, R.string.marked_as_seen, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.marked_as_seen, Toast.LENGTH_SHORT).show();
case LOADING: case LOADING:
if (item != null) {
item.setEnabled(false); item.setEnabled(false);
}
break; break;
case ERROR: case ERROR:
if (item != null) {
item.setEnabled(true); item.setEnabled(true);
}
if (resource.message != null) { if (resource.message != null) {
Snackbar.make(context, binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show(); Snackbar.make(context, binding.getRoot(), resource.message, Snackbar.LENGTH_LONG).show();
return; return;
@ -476,10 +507,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (isRecording) { if (isRecording) {
binding.recordView.cancelRecording(binding.send); binding.recordView.cancelRecording(binding.send);
} }
if (isKbShown) { emojiPickerVisible.postValue(false);
wasKbShowing = true; kbVisible.postValue(false);
binding.emojiPicker.setAlpha(0); binding.inputHolder.setTranslationY(0);
} binding.chats.setTranslationY(0);
binding.emojiPicker.setTranslationY(0);
removeObservers(); removeObservers();
super.onPause(); super.onPause();
} }
@ -487,16 +519,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
fragmentActivity.getWindow().setSoftInputMode(SOFT_INPUT_ADJUST_NOTHING | SOFT_INPUT_STATE_HIDDEN);
if (wasKbShowing) {
binding.input.requestFocus();
binding.input.post(this::showKeyboard);
wasKbShowing = false;
}
if (initialSendX != 0) { if (initialSendX != 0) {
binding.send.setX(initialSendX); binding.send.setX(initialSendX);
} }
binding.send.stopScale(); binding.send.stopScale();
final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
onBackPressedDispatcher.addCallback(onEmojiPickerBackPressedCallback);
setupBackStackResultObserver(); setupBackStackResultObserver();
setObservers(); setObservers();
// attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue()); // attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue());
@ -519,13 +547,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (prevTitleRunnable != null) { if (prevTitleRunnable != null) {
appExecutors.mainThread().cancel(prevTitleRunnable); appExecutors.mainThread().cancel(prevTitleRunnable);
} }
if (heightProvider != null) {
// need to close the height provider popup before navigating back to prevent leak
heightProvider.dismiss();
}
if (originalSoftInputMode != 0) {
fragmentActivity.getWindow().setSoftInputMode(originalSoftInputMode);
}
for (int childCount = binding.chats.getChildCount(), i = 0; i < childCount; ++i) { for (int childCount = binding.chats.getChildCount(), i = 0; i < childCount; ++i) {
final RecyclerView.ViewHolder holder = binding.chats.getChildViewHolder(binding.chats.getChildAt(i)); final RecyclerView.ViewHolder holder = binding.chats.getChildViewHolder(binding.chats.getChildAt(i));
if (holder == null) continue; if (holder == null) continue;
@ -535,7 +556,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
} }
isPendingRequestCountBadgeAttached = false; isPendingRequestCountBadgeAttached = false;
if (pendingRequestCountBadgeDrawable != null) { if (pendingRequestCountBadgeDrawable != null) {
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), R.id.info);
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info); BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info);
}
pendingRequestCountBadgeDrawable = null; pendingRequestCountBadgeDrawable = null;
} }
} }
@ -547,37 +572,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
actionBar = fragmentActivity.getSupportActionBar(); actionBar = fragmentActivity.getSupportActionBar();
setupList(); setupList();
root.post(this::setupInput); root.post(this::setupInput);
// root.post(this::getInitialData);
} }
// private void getInitialData() {
// final Bundle arguments = getArguments();
// if (arguments == null) return;
// final DirectMessageThreadFragmentArgs args = DirectMessageThreadFragmentArgs.fromBundle(arguments);
// final boolean pending = args.getPending();
// final NavController navController = NavHostFragment.findNavController(this);
// final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph);
// final List<DirectThread> threads;
// if (!pending) {
// final DirectInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class);
// threads = threadListViewModel.getThreads().getValue();
// } else {
// final DirectPendingInboxViewModel threadListViewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class);
// threads = threadListViewModel.getThreads().getValue();
// }
// final Optional<DirectThread> first = threads != null
// ? threads.stream()
// .filter(thread -> thread.getThreadId().equals(viewModel.getThreadId()))
// .findFirst()
// : Optional.empty();
// if (first.isPresent()) {
// final DirectThread thread = first.get();
// viewModel.setThread(thread);
// return;
// }
// viewModel.fetchChats();
// }
private void setupList() { private void setupList() {
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
@ -848,7 +844,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
pendingRequestCountBadgeDrawable = BadgeDrawable.create(context); pendingRequestCountBadgeDrawable = BadgeDrawable.create(context);
} }
if (count == null || count == 0) { if (count == null || count == 0) {
@SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), R.id.info);
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info); BadgeUtils.detachBadgeDrawable(pendingRequestCountBadgeDrawable, fragmentActivity.getToolbar(), R.id.info);
}
isPendingRequestCountBadgeAttached = false; isPendingRequestCountBadgeAttached = false;
pendingRequestCountBadgeDrawable.setNumber(0); pendingRequestCountBadgeDrawable.setNumber(0);
return; return;
@ -1086,23 +1086,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
final int length = s.length(); final int length = s.length();
inputLength.postValue(length); inputLength.postValue(length);
// boolean showExtraInputOptionsChanged = false;
// if (prevLength != 0 && length == 0) {
// inputLength.postValue(true);
// showExtraInputOptionsChanged = true;
// binding.send.setListenForRecord(true);
// startIconAnimation();
// }
// if (prevLength == 0 && length != 0) {
// inputLength.postValue(false);
// showExtraInputOptionsChanged = true;
// binding.send.setListenForRecord(false);
// startIconAnimation();
// }
// if (!showExtraInputOptionsChanged) {
// showExtraInputOptions.postValue(length == 0);
// }
// prevLength = length;
} }
}); });
binding.send.setOnRecordClickListener(v -> { binding.send.setOnRecordClickListener(v -> {
@ -1117,30 +1100,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
Log.d(TAG, "setOnRecordLongClickListener"); Log.d(TAG, "setOnRecordLongClickListener");
return true; return true;
}); });
binding.input.setShowSoftInputOnFocus(false); binding.input.setOnFocusChangeListener((v, hasFocus) -> {
binding.input.requestFocus(); if (!hasFocus) return;
binding.input.setOnKeyEventListener((keyCode, keyEvent) -> { final Boolean emojiPickerVisibleValue = emojiPickerVisible.getValue();
if (keyCode != KeyEvent.KEYCODE_BACK) return false; if (emojiPickerVisibleValue == null || !emojiPickerVisibleValue) return;
// We'll close the keyboard/emoji picker only when user releases the back button inputHolderAnimationCallback.setShouldTranslate(false);
// return true so that system doesn't handle the event chatsAnimationCallback.setShouldTranslate(false);
if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true; emojiPickerAnimationCallback.setShouldTranslate(false);
if (!isKbShown && !isEmojiPickerShown) {
// if both keyboard and emoji picker are hidden, navigate back
if (heightProvider != null) {
// need to close the height provider popup before navigating back to prevent leak
heightProvider.dismiss();
}
NavHostFragment.findNavController(this).navigateUp();
return true;
}
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
hideKeyboard(true);
return true;
});
binding.input.setOnClickListener(v -> {
if (isKbShown) return;
showKeyboard();
}); });
setupInsetsCallback();
setupEmojiPicker(); setupEmojiPicker();
binding.gallery.setOnClickListener(v -> { binding.gallery.setOnClickListener(v -> {
final MediaPickerBottomDialogFragment mediaPicker = MediaPickerBottomDialogFragment.newInstance(); final MediaPickerBottomDialogFragment mediaPicker = MediaPickerBottomDialogFragment.newInstance();
@ -1149,10 +1117,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (!isAdded()) return; if (!isAdded()) return;
if (!entry.isVideo) { if (!entry.isVideo) {
navigateToImageEditFragment(entry.path); navigateToImageEditFragment(entry.path);
return;
} }
handleSentMessage(viewModel.sendUri(entry));
}); });
mediaPicker.show(getChildFragmentManager(), "MediaPicker"); mediaPicker.show(getChildFragmentManager(), "MediaPicker");
hideKeyboard(true);
}); });
binding.gif.setOnClickListener(v -> { binding.gif.setOnClickListener(v -> {
final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance(); final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance();
@ -1162,7 +1131,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
handleSentMessage(viewModel.sendAnimatedMedia(giphyGif)); handleSentMessage(viewModel.sendAnimatedMedia(giphyGif));
}); });
gifPicker.show(getChildFragmentManager(), "GifPicker"); gifPicker.show(getChildFragmentManager(), "GifPicker");
hideKeyboard(true);
}); });
binding.camera.setOnClickListener(v -> { binding.camera.setOnClickListener(v -> {
final Intent intent = new Intent(context, CameraActivity.class); final Intent intent = new Intent(context, CameraActivity.class);
@ -1170,6 +1138,73 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
}); });
} }
private void setupInsetsCallback() {
inputHolderAnimationCallback = new TranslateDeferringInsetsAnimationCallback(
binding.inputHolder,
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime(),
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE
);
ViewCompat.setWindowInsetsAnimationCallback(binding.inputHolder, inputHolderAnimationCallback);
chatsAnimationCallback = new TranslateDeferringInsetsAnimationCallback(
binding.chats,
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
ViewCompat.setWindowInsetsAnimationCallback(binding.chats, chatsAnimationCallback);
emojiPickerAnimationCallback = new EmojiPickerInsetsAnimationCallback(
binding.emojiPicker,
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
emojiPickerAnimationCallback.setKbVisibilityListener(this::onKbVisibilityChange);
ViewCompat.setWindowInsetsAnimationCallback(binding.emojiPicker, emojiPickerAnimationCallback);
ViewCompat.setWindowInsetsAnimationCallback(
binding.input,
new ControlFocusInsetsAnimationCallback(binding.input)
);
final SimpleImeAnimationController imeAnimController = root.getImeAnimController();
if (imeAnimController != null) {
imeAnimController.setAnimationControlListener(new WindowInsetsAnimationControlListenerCompat() {
@Override
public void onReady(@NonNull final WindowInsetsAnimationControllerCompat controller, final int types) {}
@Override
public void onFinished(@NonNull final WindowInsetsAnimationControllerCompat controller) {
checkKbVisibility();
}
@Override
public void onCancelled(@Nullable final WindowInsetsAnimationControllerCompat controller) {
checkKbVisibility();
}
private void checkKbVisibility() {
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(binding.getRoot());
final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime());
onKbVisibilityChange(visible);
}
});
}
}
private void onKbVisibilityChange(final boolean kbVisible) {
this.kbVisible.postValue(kbVisible);
if (wasToggled) {
emojiPickerVisible.postValue(!kbVisible);
wasToggled = false;
return;
}
final Boolean emojiPickerVisibleValue = emojiPickerVisible.getValue();
if (kbVisible && emojiPickerVisibleValue != null && emojiPickerVisibleValue) {
emojiPickerVisible.postValue(false);
return;
}
if (!kbVisible) {
emojiPickerVisible.postValue(false);
}
}
private void startIconAnimation() { private void startIconAnimation() {
final Drawable icon = binding.send.getIcon(); final Drawable icon = binding.send.getIcon();
if (icon instanceof Animatable) { if (icon instanceof Animatable) {
@ -1216,15 +1251,87 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void setupEmojiPicker() { private void setupEmojiPicker() {
root.post(() -> binding.emojiPicker.init( root.post(() -> binding.emojiPicker.init(
root, root,
(view, emoji) -> binding.input.append(emoji.getUnicode()), (view, emoji) -> {
final KeyNotifyingEmojiEditText input = binding.input;
final int start = input.getSelectionStart();
final int end = input.getSelectionEnd();
if (start < 0) {
input.append(emoji.getUnicode());
return;
}
input.getText().replace(
Math.min(start, end),
Math.max(start, end),
emoji.getUnicode(),
0,
emoji.getUnicode().length()
);
},
() -> binding.input.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) () -> binding.input.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
)); ));
setupKbHeightProvider(); binding.emojiToggle.setOnClickListener(v -> {
if (keyboardHeight == 0) { Boolean isEmojiPickerVisible = emojiPickerVisible.getValue();
keyboardHeight = Utils.convertDpToPx(250); if (isEmojiPickerVisible == null) isEmojiPickerVisible = false;
Boolean isKbVisible = kbVisible.getValue();
if (isKbVisible == null) isKbVisible = false;
wasToggled = isEmojiPickerVisible || isKbVisible;
if (isEmojiPickerVisible) {
if (hasKbOpenedOnce && binding.emojiPicker.getTranslationY() != 0) {
inputHolderAnimationCallback.setShouldTranslate(false);
chatsAnimationCallback.setShouldTranslate(false);
emojiPickerAnimationCallback.setShouldTranslate(false);
} }
setEmojiPickerBounds(); // trigger ime.
binding.emojiToggle.setOnClickListener(v -> toggleEmojiPicker()); // Since the kb visibility listener will toggle the emojiPickerVisible live data, we do not explicitly toggle it here
showKeyboard();
return;
}
if (isKbVisible) {
// hide the keyboard, but don't translate the views
// Since the kb visibility listener will toggle the emojiPickerVisible live data, we do not explicitly toggle it here
inputHolderAnimationCallback.setShouldTranslate(false);
chatsAnimationCallback.setShouldTranslate(false);
emojiPickerAnimationCallback.setShouldTranslate(false);
hideKeyboard();
}
emojiPickerVisible.postValue(true);
});
final LiveData<Pair<Boolean, Boolean>> emojiKbVisibilityLD = Utils.zipLiveData(emojiPickerVisible, kbVisible);
emojiKbVisibilityLD.observe(getViewLifecycleOwner(), pair -> {
Boolean isEmojiPickerVisible = pair.first;
Boolean isKbVisible = pair.second;
if (isEmojiPickerVisible == null) isEmojiPickerVisible = false;
if (isKbVisible == null) isKbVisible = false;
root.setScrollImeOffScreenWhenVisible(!isEmojiPickerVisible);
root.setScrollImeOnScreenWhenNotVisible(!isEmojiPickerVisible);
onEmojiPickerBackPressedCallback.setEnabled(isEmojiPickerVisible && !isKbVisible);
if (isEmojiPickerVisible && !isKbVisible) {
animatePan(binding.emojiPicker.getMeasuredHeight(), unused -> {
binding.emojiPicker.setAlpha(1);
binding.emojiToggle.setIconResource(R.drawable.ic_keyboard_24);
return null;
}, null);
return;
}
if (!isEmojiPickerVisible && !isKbVisible) {
animatePan(0, null, unused -> {
binding.emojiPicker.setAlpha(0);
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
return null;
});
return;
}
// isKbVisible will always be true going forward
hasKbOpenedOnce = true;
if (!isEmojiPickerVisible) {
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
binding.emojiPicker.setAlpha(0);
return;
}
binding.emojiPicker.setAlpha(1);
});
} }
public void showKeyboard() { public void showKeyboard() {
@ -1232,67 +1339,21 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (context == null) return; if (context == null) return;
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return; if (imm == null) return;
if (!isEmojiPickerShown) { if (!binding.input.isFocused()) {
binding.emojiPicker.setAlpha(0); binding.input.requestFocus();
} }
final boolean shown = imm.showSoftInput(binding.input, InputMethodManager.SHOW_IMPLICIT); final boolean shown = imm.showSoftInput(binding.input, InputMethodManager.SHOW_IMPLICIT);
if (!shown) { if (!shown) {
Log.e(TAG, "showKeyboard: System did not display the keyboard"); Log.e(TAG, "showKeyboard: System did not display the keyboard");
} }
if (!isEmojiPickerShown) {
animatePan(keyboardHeight);
}
isKbShown = true;
} }
public void hideKeyboard(final boolean shouldPan) { public void hideKeyboard() {
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm == null) return; if (imm == null) return;
if (shouldPan) {
binding.emojiPicker.setAlpha(0);
}
imm.hideSoftInputFromWindow(binding.input.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); imm.hideSoftInputFromWindow(binding.input.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
if (shouldPan) {
animatePan(0);
isEmojiPickerShown = false;
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
}
isKbShown = false;
}
/**
* Toggle between emoji picker and keyboard
* If both are hidden, the emoji picker is shown first
*/
private void toggleEmojiPicker() {
if (isKbShown) {
binding.emojiToggle.setIconResource(R.drawable.ic_keyboard_24);
hideKeyboard(false);
return;
}
if (isEmojiPickerShown) {
binding.emojiToggle.setIconResource(R.drawable.ic_face_24);
showKeyboard();
return;
}
binding.emojiToggle.setIconResource(R.drawable.ic_keyboard_24);
animatePan(keyboardHeight);
isEmojiPickerShown = true;
}
/**
* Set height of the emoji picker
*/
private void setEmojiPickerBounds() {
final ViewGroup.LayoutParams layoutParams = binding.emojiPicker.getLayoutParams();
layoutParams.height = keyboardHeight;
if (!isEmojiPickerShown) {
// If emoji picker is hidden reset the translationY so that it doesn't peek from bottom
binding.emojiPicker.setTranslationY(keyboardHeight);
}
binding.emojiPicker.requestLayout();
} }
private void setSendToMicIcon() { private void setSendToMicIcon() {
@ -1352,7 +1413,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
} }
@NonNull @Nullable
private User getUser(final long userId) { private User getUser(final long userId) {
for (final User user : users) { for (final User user : users) {
if (userId != user.getPk()) continue; if (userId != user.getPk()) continue;
@ -1361,40 +1422,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
return null; return null;
} }
private void setupKbHeightProvider() {
if (heightProvider != null) return;
heightProvider = new HeightProvider(fragmentActivity).init().setHeightListener(height -> {
if (height > 100 && keyboardHeight != height) {
// save the current keyboard height to settings to use later
keyboardHeight = height;
setEmojiPickerBounds();
animatePan(keyboardHeight);
}
});
}
// Sets the translationY of views to height with animation // Sets the translationY of views to height with animation
private void animatePan(final int height) { private void animatePan(final int height,
@Nullable final Function<Void, Void> onAnimationStart,
@Nullable final Function<Void, Void> onAnimationEnd) {
if (animatorSet != null && animatorSet.isStarted()) { if (animatorSet != null && animatorSet.isStarted()) {
animatorSet.cancel(); animatorSet.cancel();
} }
final ImmutableList.Builder<Animator> builder = ImmutableList.builder(); final ImmutableList.Builder<Animator> builder = ImmutableList.builder();
builder.add( builder.add(
ObjectAnimator.ofFloat(binding.chats, TRANSLATION_Y, -height), ObjectAnimator.ofFloat(binding.chats, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.input, TRANSLATION_Y, -height), ObjectAnimator.ofFloat(binding.inputHolder, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.inputBg, TRANSLATION_Y, -height), ObjectAnimator.ofFloat(binding.emojiPicker, TRANSLATION_Y, -height)
ObjectAnimator.ofFloat(binding.recordView, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.emojiToggle, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.gif, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.gallery, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.camera, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.send, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyBg, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyInfo, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyCancel, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyPreviewImage, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.replyPreviewText, TRANSLATION_Y, -height),
ObjectAnimator.ofFloat(binding.emojiPicker, TRANSLATION_Y, keyboardHeight - height)
); );
// if (headerItemDecoration != null && headerItemDecoration.getCurrentHeader() != null) { // if (headerItemDecoration != null && headerItemDecoration.getCurrentHeader() != null) {
// builder.add(ObjectAnimator.ofFloat(headerItemDecoration.getCurrentHeader(), TRANSLATION_Y, height)); // builder.add(ObjectAnimator.ofFloat(headerItemDecoration.getCurrentHeader(), TRANSLATION_Y, height));
@ -1404,10 +1443,21 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
animatorSet.setDuration(200); animatorSet.setDuration(200);
animatorSet.setInterpolator(CubicBezierInterpolator.EASE_IN); animatorSet.setInterpolator(CubicBezierInterpolator.EASE_IN);
animatorSet.addListener(new AnimatorListenerAdapter() { animatorSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(final Animator animation) {
super.onAnimationStart(animation);
if (onAnimationStart != null) {
onAnimationStart.apply(null);
}
}
@Override @Override
public void onAnimationEnd(final Animator animation) { public void onAnimationEnd(final Animator animation) {
binding.emojiPicker.setAlpha(1); super.onAnimationEnd(animation);
animatorSet = null; animatorSet = null;
if (onAnimationEnd != null) {
onAnimationEnd.apply(null);
}
} }
}); });
animatorSet.start(); animatorSet.start();
@ -1457,25 +1507,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(direction); NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(direction);
} }
public static class ItemsAdapterDataMerger extends MediatorLiveData<Pair<User, DirectThread>> { @Override
private User user; public void onClick(final View view, final Emoji emoji) {
private DirectThread thread; if (addReactionItem == null) return;
final LiveData<Resource<Object>> resourceLiveData = viewModel.sendReaction(addReactionItem, emoji);
public ItemsAdapterDataMerger(final LiveData<User> userLiveData, if (resourceLiveData != null) {
final LiveData<DirectThread> threadLiveData) { resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData));
addSource(userLiveData, user -> {
this.user = user;
combine();
});
addSource(threadLiveData, thread -> {
this.thread = thread;
combine();
});
}
private void combine() {
if (user == null || thread == null) return;
setValue(new Pair<>(user, thread));
} }
} }
} }

View File

@ -18,7 +18,8 @@ import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher; import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.motion.widget.MotionScene;
import androidx.core.content.PermissionChecker; import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
@ -29,7 +30,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
@ -64,7 +64,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private CoordinatorLayout root; private MotionLayout root;
private FragmentFeedBinding binding; private FragmentFeedBinding binding;
private StoriesService storiesService; private StoriesService storiesService;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
@ -179,15 +179,21 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
final View profilePicView, final View profilePicView,
final View mainPostImage, final View mainPostImage,
final int position) { final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment.builder(feedModel); // ViewCompat.setTransitionName(profilePicView, "profile_pic");
if (position >= 0) { // ViewCompat.setTransitionName(mainPostImage, "post_image");
builder.setPosition(position); // final FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
// .addSharedElement(profilePicView, "profile_pic")
// .addSharedElement(mainPostImage, "post_image")
// .build();
final NavController navController = NavHostFragment.findNavController(FeedFragment.this);
final Bundle bundle = new Bundle();
bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
builder.build().show(getChildFragmentManager(), "post_view");
} }
}; };
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@ -278,9 +284,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
final Bundle savedInstanceState) { final Bundle savedInstanceState) {
if (root != null) { if (root != null) {
shouldRefresh = false; shouldRefresh = false;
if (storiesRecyclerView != null) {
fragmentActivity.setCollapsingView(storiesRecyclerView);
}
return root; return root;
} }
binding = FragmentFeedBinding.inflate(inflater, container, false); binding = FragmentFeedBinding.inflate(inflater, container, false);
@ -334,6 +337,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override
public void onResume() {
super.onResume();
binding.getRoot().postDelayed(feedStoriesAdapter::notifyDataSetChanged, 1000);
}
@Override @Override
public void onRefresh() { public void onRefresh() {
binding.feedRecyclerView.refresh(); binding.feedRecyclerView.refresh();
@ -381,6 +390,17 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
.setSelectionModeCallback(selectionModeCallback) .setSelectionModeCallback(selectionModeCallback)
.init(); .init();
binding.feedSwipeRefreshLayout.setRefreshing(true); binding.feedSwipeRefreshLayout.setRefreshing(true);
binding.feedRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
final boolean canScrollVertically = recyclerView.canScrollVertically(-1);
final MotionScene.Transition transition = root.getTransition(R.id.transition);
if (transition != null) {
transition.setEnable(!canScrollVertically);
}
}
});
// if (shouldAutoPlay) { // if (shouldAutoPlay) {
// videoAwareRecyclerScroller = new VideoAwareRecyclerScroller(); // videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
// binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller); // binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
@ -396,29 +416,24 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
storiesRecyclerView = new RecyclerView(context); storiesRecyclerView = binding.header;
final CollapsingToolbarLayout.LayoutParams params = new CollapsingToolbarLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, Utils.getActionBarHeight(context), 0, 0);
storiesRecyclerView.setLayoutParams(params);
storiesRecyclerView.setClipToPadding(false);
storiesRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)); storiesRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false));
storiesRecyclerView.setAdapter(feedStoriesAdapter); storiesRecyclerView.setAdapter(feedStoriesAdapter);
fragmentActivity.setCollapsingView(storiesRecyclerView);
feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), feedStoriesAdapter::submitList); feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), feedStoriesAdapter::submitList);
fetchStories(); fetchStories();
} }
private void fetchStories() { private void fetchStories() {
if (storiesFetching) return;
// final String cookie = settingsHelper.getString(Constants.COOKIE); // final String cookie = settingsHelper.getString(Constants.COOKIE);
storiesFetching = true; storiesFetching = true;
updateSwipeRefreshState(); updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override @Override
public void onSuccess(final List<FeedStoryModel> result) { public void onSuccess(final List<FeedStoryModel> result) {
storiesFetching = false;
feedStoriesViewModel.getList().postValue(result); feedStoriesViewModel.getList().postValue(result);
feedStoriesAdapter.submitList(result); feedStoriesAdapter.submitList(result);
storiesFetching = false;
if (storyListMenu != null) storyListMenu.setVisible(true); if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState(); updateSwipeRefreshState();
} }
@ -443,9 +458,11 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
public void scrollToTop() { public void scrollToTop() {
if (binding != null) {
binding.feedRecyclerView.smoothScrollToPosition(0); binding.feedRecyclerView.smoothScrollToPosition(0);
// binding.storiesContainer.setExpanded(true); // binding.storiesContainer.setExpanded(true);
} }
}
private boolean isSafeToNavigate(final NavController navController) { private boolean isSafeToNavigate(final NavController navController) {
return navController.getCurrentDestination() != null return navController.getCurrentDestination() != null

View File

@ -27,7 +27,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.constraintlayout.motion.widget.MotionLayout;
import androidx.constraintlayout.motion.widget.MotionScene;
import androidx.core.content.PermissionChecker; import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -108,7 +109,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private CoordinatorLayout root; private MotionLayout root;
private FragmentProfileBinding binding; private FragmentProfileBinding binding;
private boolean isLoggedIn; private boolean isLoggedIn;
private String cookie; private String cookie;
@ -250,23 +251,15 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final View profilePicView, final View profilePicView,
final View mainPostImage, final View mainPostImage,
final int position) { final int position) {
final PostViewV2Fragment.Builder builder = PostViewV2Fragment final NavController navController = NavHostFragment.findNavController(ProfileFragment.this);
.builder(feedModel); final Bundle bundle = new Bundle();
if (position >= 0) { bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel);
builder.setPosition(position); bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position);
try {
navController.navigate(R.id.action_global_post_view, bundle);
} catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e);
} }
if (!layoutPreferences.isAnimationDisabled()) {
builder.setSharedProfilePicElement(profilePicView)
.setSharedMainPostElement(mainPostImage);
}
final PostViewV2Fragment postViewV2Fragment = builder.build();
postViewV2Fragment.setOnDeleteListener(() -> {
postViewV2Fragment.dismiss();
binding.postsRecyclerView.refresh();
});
final FragmentManager fragmentManager = getChildFragmentManager();
if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) return;
postViewV2Fragment.show(fragmentManager, "post_view");
} }
}; };
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() { private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
@ -345,26 +338,22 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
final boolean isSame = ("@" + profileModelUsername).equals(this.username); final boolean isSame = ("@" + profileModelUsername).equals(this.username);
if (isSame) { if (isSame) {
setUsernameDelayed(); setUsernameDelayed();
fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot());
shouldRefresh = false; shouldRefresh = false;
return root; return root;
} }
} }
if (username == null || !username.equals(this.username)) { if (username == null || !username.equals(this.username)) {
fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot());
shouldRefresh = true; shouldRefresh = true;
return root; return root;
} }
} }
setUsernameDelayed(); setUsernameDelayed();
fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot());
shouldRefresh = false; shouldRefresh = false;
return root; return root;
} }
binding = FragmentProfileBinding.inflate(inflater, container, false); binding = FragmentProfileBinding.inflate(inflater, container, false);
root = binding.getRoot(); root = binding.getRoot();
profileDetailsBinding = LayoutProfileDetailsBinding.inflate(inflater, fragmentActivity.getCollapsingToolbarView(), false); profileDetailsBinding = binding.header;
fragmentActivity.setCollapsingView(profileDetailsBinding.getRoot());
return root; return root;
} }
@ -418,7 +407,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
chainingMenuItem = menu.findItem(R.id.chaining); chainingMenuItem = menu.findItem(R.id.chaining);
if (chainingMenuItem != null) { if (chainingMenuItem != null) {
chainingMenuItem.setVisible(isNotMe); chainingMenuItem.setVisible(isNotMe && profileModel.hasChaining());
} }
} }
@ -538,7 +527,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onRefresh() { public void onRefresh() {
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.GONE); profileDetailsBinding.countsDivider.getRoot().setVisibility(View.GONE);
profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE);
fetchProfileDetails(); fetchProfileDetails();
} }
@ -554,14 +543,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
} }
@Override
public void onDestroyView() {
super.onDestroyView();
if (profileDetailsBinding != null) {
fragmentActivity.removeCollapsingView(profileDetailsBinding.getRoot());
}
}
@Override @Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
@ -589,7 +570,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
setUsernameDelayed(); setUsernameDelayed();
} }
if (TextUtils.isEmpty(username) && !isLoggedIn) { if (TextUtils.isEmpty(username) && !isLoggedIn) {
profileDetailsBinding.infoContainer.setVisibility(View.GONE); binding.header.getRoot().setVisibility(View.GONE);
binding.swipeRefreshLayout.setEnabled(false); binding.swipeRefreshLayout.setEnabled(false);
binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24); binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24);
binding.privatePage2.setText(R.string.no_acc); binding.privatePage2.setText(R.string.no_acc);
@ -681,17 +662,21 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return; return;
} }
final long profileId = profileModel.getPk();
if (!isReallyPrivate()) {
if (!postsSetupDone) { if (!postsSetupDone) {
setupPosts(); setupPosts();
} else { }
else {
binding.postsRecyclerView.refresh(); binding.postsRecyclerView.refresh();
} }
profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE);
final long profileId = profileModel.getPk();
if (isLoggedIn) { if (isLoggedIn) {
fetchStoryAndHighlights(profileId); fetchStoryAndHighlights(profileId);
} }
}
profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE);
setupButtons(profileId); setupButtons(profileId);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() { favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@ -763,7 +748,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl()); profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl());
profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE); profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE);
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.VISIBLE); profileDetailsBinding.countsDivider.getRoot().setVisibility(View.VISIBLE);
final long followersCount = profileModel.getFollowerCount(); final long followersCount = profileModel.getFollowerCount();
final long followingCount = profileModel.getFollowingCount(); final long followingCount = profileModel.getFollowingCount();
@ -924,6 +909,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage1.setImageResource(R.drawable.lock); binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc); binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE); binding.privatePage.setVisibility(View.VISIBLE);
binding.privatePage1.setVisibility(View.VISIBLE);
binding.privatePage2.setVisibility(View.VISIBLE);
binding.postsRecyclerView.setVisibility(View.GONE); binding.postsRecyclerView.setVisibility(View.GONE);
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
} }
@ -989,7 +976,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.unmute_posts : R.string.mute_posts); mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.unmute_posts : R.string.mute_posts);
} }
if (chainingMenuItem != null) { if (chainingMenuItem != null) {
chainingMenuItem.setVisible(true); chainingMenuItem.setVisible(profileModel.hasChaining());
} }
} }
} }
@ -1209,6 +1196,17 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
.setFeedItemCallback(feedItemCallback) .setFeedItemCallback(feedItemCallback)
.setSelectionModeCallback(selectionModeCallback) .setSelectionModeCallback(selectionModeCallback)
.init(); .init();
binding.postsRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
super.onScrolled(recyclerView, dx, dy);
final boolean canScrollVertically = recyclerView.canScrollVertically(-1);
final MotionScene.Transition transition = root.getTransition(R.id.transition);
if (transition != null) {
transition.setEnable(!canScrollVertically);
}
}
});
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
postsSetupDone = true; postsSetupDone = true;
} }

View File

@ -4,8 +4,11 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -19,6 +22,7 @@ import androidx.preference.Preference;
import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceScreen;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List; import java.util.List;
@ -53,6 +57,20 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
public MorePreferencesFragment() { public MorePreferencesFragment() {
} }
@Override
public RecyclerView onCreateRecyclerView(final LayoutInflater inflater, final ViewGroup parent, final Bundle savedInstanceState) {
final RecyclerView recyclerView = super.onCreateRecyclerView(inflater, parent, savedInstanceState);
final Context context = getContext();
if (recyclerView != null && context != null) {
recyclerView.setClipToPadding(false);
recyclerView.setPadding(recyclerView.getPaddingLeft(),
recyclerView.getPaddingTop(),
recyclerView.getPaddingRight(),
Utils.getActionBarHeight(context));
}
return recyclerView;
}
@Override @Override
void setupPreferenceScreen(final PreferenceScreen screen) { void setupPreferenceScreen(final PreferenceScreen screen) {
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);

View File

@ -7,4 +7,5 @@ public final class PreferenceKeys {
public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number"; public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number";
public static final String PREF_ENABLE_SENTRY = "enable_sentry"; public static final String PREF_ENABLE_SENTRY = "enable_sentry";
public static final String PREF_TAB_ORDER = "tab_order"; public static final String PREF_TAB_ORDER = "tab_order";
public static final String PREF_SHOWN_COUNT_TOOLTIP = "shown_count_tooltip";
} }

View File

@ -585,8 +585,7 @@ public final class ThreadManager {
if (index < 0) { if (index < 0) {
temp.add(0, reaction); temp.add(0, reaction);
} else if (shouldReplaceIfAlreadyReacted) { } else if (shouldReplaceIfAlreadyReacted) {
temp.add(0, reaction); temp.set(index, reaction);
temp.remove(index);
} }
return temp; return temp;
} }
@ -736,6 +735,7 @@ public final class ThreadManager {
}); });
} }
@NonNull
public LiveData<Resource<Object>> sendReaction(final DirectItem item, final Emoji emoji) { public LiveData<Resource<Object>> sendReaction(final DirectItem item, final Emoji emoji) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>(); final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Long userId = getCurrentUserId(data); final Long userId = getCurrentUserId(data);

View File

@ -16,11 +16,15 @@ public final class FeedStoryModel implements Serializable {
private final boolean isLive, isBestie; private final boolean isLive, isBestie;
private final long timestamp; private final long timestamp;
private final int mediaCount; private final int mediaCount;
private boolean isShown = true;
public FeedStoryModel(final String storyMediaId, final User profileModel, final boolean fullyRead, public FeedStoryModel(final String storyMediaId,
final long timestamp, final StoryModel firstStoryModel, final int mediaCount, final User profileModel,
final boolean isLive, final boolean isBestie) { final boolean fullyRead,
final long timestamp,
final StoryModel firstStoryModel,
final int mediaCount,
final boolean isLive,
final boolean isBestie) {
this.storyMediaId = storyMediaId; this.storyMediaId = storyMediaId;
this.profileModel = profileModel; this.profileModel = profileModel;
this.fullyRead = fullyRead; this.fullyRead = fullyRead;
@ -52,10 +56,6 @@ public final class FeedStoryModel implements Serializable {
return profileModel; return profileModel;
} }
// public void setFirstStoryModel(final StoryModel firstStoryModel) {
// this.firstStoryModel = firstStoryModel;
// }
public StoryModel getFirstStoryModel() { public StoryModel getFirstStoryModel() {
return firstStoryModel; return firstStoryModel;
} }
@ -75,12 +75,4 @@ public final class FeedStoryModel implements Serializable {
public boolean isBestie() { public boolean isBestie() {
return isBestie; return isBestie;
} }
public boolean isShown() {
return isShown;
}
public void setShown(final boolean shown) {
isShown = shown;
}
} }

View File

@ -17,6 +17,7 @@ public class User implements Serializable {
private final boolean isUnpublished; private final boolean isUnpublished;
private final boolean isFavorite; private final boolean isFavorite;
private final boolean isDirectappInstalled; private final boolean isDirectappInstalled;
private final boolean hasChaining;
private final String reelAutoArchive; private final String reelAutoArchive;
private final String allowedCommenterType; private final String allowedCommenterType;
private final long mediaCount; private final long mediaCount;
@ -28,11 +29,10 @@ public class User implements Serializable {
private final long usertagsCount; private final long usertagsCount;
private final String publicEmail; private final String publicEmail;
private final HdProfilePicUrlInfo hdProfilePicUrlInfo; private final HdProfilePicUrlInfo hdProfilePicUrlInfo;
private final String profileContext; private final String profileContext; // "also followed by" your friends
private final List<UserProfileContextLink> profileContextLinksWithUserIds; private final List<UserProfileContextLink> profileContextLinksWithUserIds; // ^
private final String socialContext; private final String socialContext; // AYML
// if a DM member is a Facebook user, this is present private final String interopMessagingUserFbid; // in DMs only: Facebook user ID
private final String interopMessagingUserFbid;
public User(final long pk, public User(final long pk,
final String username, final String username,
@ -46,6 +46,7 @@ public class User implements Serializable {
final boolean isUnpublished, final boolean isUnpublished,
final boolean isFavorite, final boolean isFavorite,
final boolean isDirectappInstalled, final boolean isDirectappInstalled,
final boolean hasChaining,
final String reelAutoArchive, final String reelAutoArchive,
final String allowedCommenterType, final String allowedCommenterType,
final long mediaCount, final long mediaCount,
@ -73,6 +74,7 @@ public class User implements Serializable {
this.isUnpublished = isUnpublished; this.isUnpublished = isUnpublished;
this.isFavorite = isFavorite; this.isFavorite = isFavorite;
this.isDirectappInstalled = isDirectappInstalled; this.isDirectappInstalled = isDirectappInstalled;
this.hasChaining = hasChaining;
this.reelAutoArchive = reelAutoArchive; this.reelAutoArchive = reelAutoArchive;
this.allowedCommenterType = allowedCommenterType; this.allowedCommenterType = allowedCommenterType;
this.mediaCount = mediaCount; this.mediaCount = mediaCount;
@ -90,6 +92,53 @@ public class User implements Serializable {
this.interopMessagingUserFbid = interopMessagingUserFbid; this.interopMessagingUserFbid = interopMessagingUserFbid;
} }
public User(final long pk,
final String username,
final String fullName,
final boolean isPrivate,
final String profilePicUrl,
final boolean isVerified) {
this.pk = pk;
this.username = username;
this.fullName = fullName;
this.isPrivate = isPrivate;
this.profilePicUrl = profilePicUrl;
this.profilePicId = null;
this.friendshipStatus = new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
);
this.isVerified = isVerified;
this.hasAnonymousProfilePicture = false;
this.isUnpublished = false;
this.isFavorite = false;
this.isDirectappInstalled = false;
this.hasChaining = false;
this.reelAutoArchive = null;
this.allowedCommenterType = null;
this.mediaCount = 0;
this.followerCount = 0;
this.followingCount = 0;
this.followingTagCount = 0;
this.biography = null;
this.externalUrl = null;
this.usertagsCount = 0;
this.publicEmail = null;
this.hdProfilePicUrlInfo = null;
this.profileContext = null;
this.profileContextLinksWithUserIds = null;
this.socialContext = null;
this.interopMessagingUserFbid = null;
}
public long getPk() { public long getPk() {
return pk; return pk;
} }
@ -111,6 +160,9 @@ public class User implements Serializable {
} }
public String getHDProfilePicUrl() { public String getHDProfilePicUrl() {
if (hdProfilePicUrlInfo == null) {
return getProfilePicUrl();
}
return hdProfilePicUrlInfo.getUrl(); return hdProfilePicUrlInfo.getUrl();
} }
@ -146,6 +198,10 @@ public class User implements Serializable {
return isDirectappInstalled; return isDirectappInstalled;
} }
public boolean hasChaining() {
return hasChaining;
}
public String getReelAutoArchive() { public String getReelAutoArchive() {
return reelAutoArchive; return reelAutoArchive;
} }
@ -234,7 +290,7 @@ public class User implements Serializable {
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(pk, username, fullName, isPrivate, profilePicUrl, profilePicId, friendshipStatus, isVerified, hasAnonymousProfilePicture, return Objects.hash(pk, username, fullName, isPrivate, profilePicUrl, profilePicId, friendshipStatus, isVerified, hasAnonymousProfilePicture,
isUnpublished, isFavorite, isDirectappInstalled, reelAutoArchive, allowedCommenterType, mediaCount, followerCount, isUnpublished, isFavorite, isDirectappInstalled, hasChaining, reelAutoArchive, allowedCommenterType, mediaCount,
followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail); followerCount, followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail);
} }
} }

View File

@ -47,4 +47,14 @@ public class DirectItemEmojiReaction implements Serializable {
public int hashCode() { public int hashCode() {
return Objects.hash(senderId, timestamp, emoji, superReactType); return Objects.hash(senderId, timestamp, emoji, superReactType);
} }
@Override
public String toString() {
return "DirectItemEmojiReaction{" +
"senderId=" + senderId +
", timestamp=" + timestamp +
", emoji='" + emoji + '\'' +
", superReactType='" + superReactType + '\'' +
'}';
}
} }

View File

@ -51,4 +51,12 @@ public class DirectItemReactions implements Cloneable, Serializable {
public int hashCode() { public int hashCode() {
return Objects.hash(emojis, likes); return Objects.hash(emojis, likes);
} }
@Override
public String toString() {
return "DirectItemReactions{" +
"emojis=" + emojis +
", likes=" + likes +
'}';
}
} }

View File

@ -191,9 +191,7 @@ public class SearchItem {
recentSearch.getName(), recentSearch.getName(),
false, false,
recentSearch.getPicUrl(), recentSearch.getPicUrl(),
null, null, false, false, false, false, false, false
null, null, 0, 0, 0, 0, null, null,
0, null, null, null, null, null, null
); );
} }
@ -205,9 +203,7 @@ public class SearchItem {
favorite.getDisplayName(), favorite.getDisplayName(),
false, false,
favorite.getPicUrl(), favorite.getPicUrl(),
null, null, false, false, false, false, false, false
null, null, 0, 0, 0, 0, null, null,
0, null, null, null, null, null, null
); );
} }

View File

@ -36,11 +36,6 @@ public class CombinedDrawable extends Drawable implements Drawable.Callback {
} }
} }
public void setIconSize(int width, int height) {
iconWidth = width;
iconHeight = height;
}
public CombinedDrawable(Drawable backgroundDrawable, Drawable iconDrawable) { public CombinedDrawable(Drawable backgroundDrawable, Drawable iconDrawable) {
background = backgroundDrawable; background = backgroundDrawable;
icon = iconDrawable; icon = iconDrawable;
@ -49,6 +44,11 @@ public class CombinedDrawable extends Drawable implements Drawable.Callback {
} }
} }
public void setIconSize(int width, int height) {
iconWidth = width;
iconHeight = height;
}
public void setCustomSize(int width, int height) { public void setCustomSize(int width, int height) {
backWidth = width; backWidth = width;
backHeight = height; backHeight = height;
@ -82,11 +82,12 @@ public class CombinedDrawable extends Drawable implements Drawable.Callback {
} }
@Override @Override
public boolean setState(int[] stateSet) { public boolean setState(@NonNull int[] stateSet) {
icon.setState(stateSet); icon.setState(stateSet);
return true; return true;
} }
@NonNull
@Override @Override
public int[] getState() { public int[] getState() {
return icon.getState(); return icon.getState();
@ -108,7 +109,7 @@ public class CombinedDrawable extends Drawable implements Drawable.Callback {
} }
@Override @Override
public void draw(Canvas canvas) { public void draw(@NonNull Canvas canvas) {
background.setBounds(getBounds()); background.setBounds(getBounds());
background.draw(canvas); background.draw(canvas);
if (icon != null) { if (icon != null) {

View File

@ -13,6 +13,7 @@ import java.util.Optional;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.models.enums.DirectItemType; import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia; import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
@ -26,11 +27,6 @@ public final class DMUtils {
public static boolean isRead(@NonNull final DirectItem item, public static boolean isRead(@NonNull final DirectItem item,
@NonNull final Map<Long, DirectThreadLastSeenAt> lastSeenAt, @NonNull final Map<Long, DirectThreadLastSeenAt> lastSeenAt,
@NonNull final List<Long> userIdsToCheck) { @NonNull final List<Long> userIdsToCheck) {
// Further check if directStory exists
// if (read && directStory != null) {
// read = false;
// }
if (item == null) return false;
return lastSeenAt.entrySet() return lastSeenAt.entrySet()
.stream() .stream()
.filter(entry -> userIdsToCheck.contains(entry.getKey())) .filter(entry -> userIdsToCheck.contains(entry.getKey()))
@ -57,7 +53,7 @@ public final class DMUtils {
read = true; read = true;
} else { } else {
final Map<Long, DirectThreadLastSeenAt> lastSeenAtMap = thread.getLastSeenAt(); final Map<Long, DirectThreadLastSeenAt> lastSeenAtMap = thread.getLastSeenAt();
read = isRead(item, lastSeenAtMap, Collections.singletonList(viewerId)); read = item != null && isRead(item, lastSeenAtMap, Collections.singletonList(viewerId));
} }
return read; return read;
} }
@ -88,7 +84,11 @@ public final class DMUtils {
message = item.getPlaceholder().getMessage(); message = item.getPlaceholder().getMessage();
break; break;
case MEDIA_SHARE: case MEDIA_SHARE:
final User mediaShareUser = item.getMediaShare().getUser(); final Media mediaShare = item.getMediaShare();
User mediaShareUser = null;
if (mediaShare != null) {
mediaShareUser = mediaShare.getUser();
}
subtitle = resources.getString(R.string.dms_inbox_shared_post, subtitle = resources.getString(R.string.dms_inbox_shared_post,
username != null ? username : "", username != null ? username : "",
mediaShareUser == null ? "" : mediaShareUser.getUsername()); mediaShareUser == null ? "" : mediaShareUser.getUsername());
@ -120,7 +120,11 @@ public final class DMUtils {
final int format = reelType.equals("highlight_reel") final int format = reelType.equals("highlight_reel")
? R.string.dms_inbox_shared_highlight ? R.string.dms_inbox_shared_highlight
: R.string.dms_inbox_shared_story; : R.string.dms_inbox_shared_story;
final User storyShareMediaUser = item.getStoryShare().getMedia().getUser(); final Media media = item.getStoryShare().getMedia();
User storyShareMediaUser = null;
if (media != null) {
storyShareMediaUser = media.getUser();
}
subtitle = resources.getString(format, subtitle = resources.getString(format,
username != null ? username : "", username != null ? username : "",
storyShareMediaUser == null ? "" : storyShareMediaUser.getUsername()); storyShareMediaUser == null ? "" : storyShareMediaUser.getUsername());
@ -137,13 +141,21 @@ public final class DMUtils {
subtitle = item.getVideoCallEvent().getDescription(); subtitle = item.getVideoCallEvent().getDescription();
break; break;
case CLIP: case CLIP:
final User clipUser = item.getClip().getClip().getUser(); final Media clip = item.getClip().getClip();
User clipUser = null;
if (clip != null) {
clipUser = clip.getUser();
}
subtitle = resources.getString(R.string.dms_inbox_shared_clip, subtitle = resources.getString(R.string.dms_inbox_shared_clip,
username != null ? username : "", username != null ? username : "",
clipUser == null ? "" : clipUser.getUsername()); clipUser == null ? "" : clipUser.getUsername());
break; break;
case FELIX_SHARE: case FELIX_SHARE:
final User felixShareVideoUser = item.getFelixShare().getVideo().getUser(); final Media video = item.getFelixShare().getVideo();
User felixShareVideoUser = null;
if (video != null) {
felixShareVideoUser = video.getUser();
}
subtitle = resources.getString(R.string.dms_inbox_shared_igtv, subtitle = resources.getString(R.string.dms_inbox_shared_igtv,
username != null ? username : "", username != null ? username : "",
felixShareVideoUser == null ? "" : felixShareVideoUser.getUsername()); felixShareVideoUser == null ? "" : felixShareVideoUser.getUsername());

View File

@ -21,6 +21,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.customviews.NavHostFragmentWithDefaultAnimations;
import awais.instagrabber.fragments.main.FeedFragment; import awais.instagrabber.fragments.main.FeedFragment;
/** /**
@ -62,7 +63,7 @@ public class NavigationExtensions {
selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId()); selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId());
final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId); final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId);
isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag); isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag);
bottomNavigationView.setOnNavigationItemSelectedListener(item -> { bottomNavigationView.setOnItemSelectedListener(item -> {
if (fragmentManager.isStateSaved()) { if (fragmentManager.isStateSaved()) {
return false; return false;
} }
@ -139,7 +140,7 @@ public class NavigationExtensions {
if (existingFragment != null) { if (existingFragment != null) {
return existingFragment; return existingFragment;
} }
final NavHostFragment navHostFragment = NavHostFragment.create(navGraphId); final NavHostFragment navHostFragment = NavHostFragmentWithDefaultAnimations.create(navGraphId);
fragmentManager.beginTransaction() fragmentManager.beginTransaction()
.setReorderingAllowed(true) .setReorderingAllowed(true)
.add(containerId, navHostFragment, fragmentTag) .add(containerId, navHostFragment, fragmentTag)
@ -168,7 +169,7 @@ public class NavigationExtensions {
private static void setupItemReselected(final BottomNavigationView bottomNavigationView, private static void setupItemReselected(final BottomNavigationView bottomNavigationView,
final SparseArray<String> graphIdToTagMap, final SparseArray<String> graphIdToTagMap,
final FragmentManager fragmentManager) { final FragmentManager fragmentManager) {
bottomNavigationView.setOnNavigationItemReselectedListener(item -> { bottomNavigationView.setOnItemReselectedListener(item -> {
final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag); final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragmentByTag == null) { if (fragmentByTag == null) {

View File

@ -0,0 +1,91 @@
package awais.instagrabber.utils;
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.ObjectsCompat;
/**
* Container to ease passing around a tuple of two objects. This object provides a sensible
* implementation of equals(), returning true if equals() is true on each of the contained
* objects.
*/
public class NullSafePair<F, S> {
public final @NonNull
F first;
public final @NonNull
S second;
/**
* Constructor for a Pair.
*
* @param first the first object in the Pair
* @param second the second object in the pair
*/
public NullSafePair(@NonNull F first, @NonNull S second) {
this.first = first;
this.second = second;
}
/**
* Checks the two objects for equality by delegating to their respective
* {@link Object#equals(Object)} methods.
*
* @param o the {@link androidx.core.util.Pair} to which this one is to be checked for equality
* @return true if the underlying objects of the Pair are both considered
* equal
*/
@Override
public boolean equals(Object o) {
if (!(o instanceof androidx.core.util.Pair)) {
return false;
}
androidx.core.util.Pair<?, ?> p = (androidx.core.util.Pair<?, ?>) o;
return ObjectsCompat.equals(p.first, first) && ObjectsCompat.equals(p.second, second);
}
/**
* Compute a hash code using the hash codes of the underlying objects
*
* @return a hashcode of the Pair
*/
@Override
public int hashCode() {
return first.hashCode() ^ second.hashCode();
}
@NonNull
@Override
public String toString() {
return "Pair{" + first + " " + second + "}";
}
/**
* Convenience method for creating an appropriately typed pair.
*
* @param a the first object in the Pair
* @param b the second object in the pair
* @return a Pair that is templatized with the types of a and b
*/
@NonNull
public static <A, B> androidx.core.util.Pair<A, B> create(@Nullable A a, @Nullable B b) {
return new androidx.core.util.Pair<A, B>(a, b);
}
}

View File

@ -1,8 +1,9 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.util.Pair; import androidx.annotation.Nullable;
import java.util.Locale;
import java.util.Random; import java.util.Random;
public final class NumberUtils { public final class NumberUtils {
@ -56,7 +57,7 @@ public final class NumberUtils {
} }
@NonNull @NonNull
public static Pair<Integer, Integer> calculateWidthHeight(final int height, final int width, final int maxHeight, final int maxWidth) { public static NullSafePair<Integer, Integer> calculateWidthHeight(final int height, final int width, final int maxHeight, final int maxWidth) {
if (width > maxWidth) { if (width > maxWidth) {
int tempHeight = getResultingHeight(maxWidth, height, width); int tempHeight = getResultingHeight(maxWidth, height, width);
int tempWidth = maxWidth; int tempWidth = maxWidth;
@ -64,7 +65,7 @@ public final class NumberUtils {
tempWidth = getResultingWidth(maxHeight, tempHeight, tempWidth); tempWidth = getResultingWidth(maxHeight, tempHeight, tempWidth);
tempHeight = maxHeight; tempHeight = maxHeight;
} }
return new Pair<>(tempWidth, tempHeight); return new NullSafePair<>(tempWidth, tempHeight);
} }
if ((height < maxHeight && width < maxWidth) || (height > maxHeight)) { if ((height < maxHeight && width < maxWidth) || (height > maxHeight)) {
int tempWidth = getResultingWidth(maxHeight, height, width); int tempWidth = getResultingWidth(maxHeight, height, width);
@ -73,12 +74,76 @@ public final class NumberUtils {
tempHeight = getResultingHeight(maxWidth, tempHeight, tempWidth); tempHeight = getResultingHeight(maxWidth, tempHeight, tempWidth);
tempWidth = maxWidth; tempWidth = maxWidth;
} }
return new Pair<>(tempWidth, tempHeight); return new NullSafePair<>(tempWidth, tempHeight);
} }
return new Pair<>(width, height); return new NullSafePair<>(width, height);
} }
public static float roundFloat2Decimals(final float value) { public static float roundFloat2Decimals(final float value) {
return ((int) ((value + (value >= 0 ? 1 : -1) * 0.005f) * 100)) / 100f; return ((int) ((value + (value >= 0 ? 1 : -1) * 0.005f) * 100)) / 100f;
} }
@NonNull
public static String abbreviate(final long number) {
return abbreviate(number, null);
}
@NonNull
public static String abbreviate(final long number, @Nullable final AbbreviateOptions options) {
// adapted from https://stackoverflow.com/a/9769590/1436766
int threshold = 1000;
boolean addSpace = false;
if (options != null) {
threshold = options.getThreshold();
addSpace = options.addSpaceBeforePrefix();
}
if (number < threshold) return "" + number;
int exp = (int) (Math.log(number) / Math.log(threshold));
return String.format(Locale.US,
"%.1f%s%c",
number / Math.pow(threshold, exp),
addSpace ? " " : "",
"kMGTPE".charAt(exp - 1));
}
public static final class AbbreviateOptions {
private final int threshold;
private final boolean addSpaceBeforePrefix;
public static final class Builder {
private int threshold = 1000;
private boolean addSpaceBeforePrefix = false;
public Builder setThreshold(final int threshold) {
this.threshold = threshold;
return this;
}
public Builder setAddSpaceBeforePrefix(final boolean addSpaceBeforePrefix) {
this.addSpaceBeforePrefix = addSpaceBeforePrefix;
return this;
}
@NonNull
public AbbreviateOptions build() {
return new AbbreviateOptions(threshold, addSpaceBeforePrefix);
}
}
private AbbreviateOptions(final int threshold, final boolean addSpaceBeforePrefix) {
this.threshold = threshold;
this.addSpaceBeforePrefix = addSpaceBeforePrefix;
}
public int getThreshold() {
return threshold;
}
public boolean addSpaceBeforePrefix() {
return addSpaceBeforePrefix;
}
}
} }

View File

@ -767,11 +767,7 @@ public final class ResponseBodyUtils {
owner.optString("full_name"), owner.optString("full_name"),
false, false,
owner.optString("profile_pic_url"), owner.optString("profile_pic_url"),
null, owner.optBoolean("is_verified"));
friendshipStatus,
owner.optBoolean("is_verified"),
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null,
null, null, null, null);
} }
final String id = feedItem.getString(Constants.EXTRAS_ID); final String id = feedItem.getString(Constants.EXTRAS_ID);
VideoVersion videoVersion = null; VideoVersion videoVersion = null;

View File

@ -16,6 +16,7 @@ import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_D
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_SENTRY; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_SENTRY;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_TAB_ORDER; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_TAB_ORDER;
import static awais.instagrabber.utils.Constants.APP_LANGUAGE; import static awais.instagrabber.utils.Constants.APP_LANGUAGE;
import static awais.instagrabber.utils.Constants.APP_THEME; import static awais.instagrabber.utils.Constants.APP_THEME;
@ -165,7 +166,7 @@ public final class SettingsHelper {
@StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, @StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY, SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY,
CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH, CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH,
FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY, HIDE_MUTED_REELS, PLAY_IN_BACKGROUND}) FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY, HIDE_MUTED_REELS, PLAY_IN_BACKGROUND, PREF_SHOWN_COUNT_TOOLTIP})
public @interface BooleanSettings {} public @interface BooleanSettings {}
@StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER}) @StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER})

View File

@ -10,6 +10,7 @@ import android.content.Intent;
import android.content.res.Resources; import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.MediaScannerConnection; import android.media.MediaScannerConnection;
@ -22,6 +23,7 @@ import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.Display;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.Window; import android.view.Window;
@ -36,6 +38,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;
@ -288,6 +292,18 @@ public final class Utils {
return outValue.data; return outValue.data;
} }
public static int getAttrValue(@NonNull final Context context, final int attr) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(attr, outValue, true);
return outValue.data;
}
public static int getAttrResId(@NonNull final Context context, final int attr) {
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(attr, outValue, true);
return outValue.resourceId;
}
public static void transparentStatusBar(final Activity activity, public static void transparentStatusBar(final Activity activity,
final boolean enable, final boolean enable,
final boolean fullscreen) { final boolean fullscreen) {
@ -511,4 +527,76 @@ public final class Utils {
if (navRootString == null || tabOrderString == null) return false; if (navRootString == null || tabOrderString == null) return false;
return tabOrderString.contains(navRootString); return tabOrderString.contains(navRootString);
} }
@NonNull
public static Point getNavigationBarSize(@NonNull Context context) {
Point appUsableSize = getAppUsableScreenSize(context);
Point realScreenSize = getRealScreenSize(context);
// navigation bar on the right
if (appUsableSize.x < realScreenSize.x) {
return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
}
// navigation bar at the bottom
if (appUsableSize.y < realScreenSize.y) {
return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
}
// navigation bar is not present
return new Point();
}
@NonNull
public static Point getAppUsableScreenSize(@NonNull Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}
@NonNull
public static Point getRealScreenSize(@NonNull Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getRealSize(size);
return size;
}
public static <F, S> LiveData<Pair<F, S>> zipLiveData(@NonNull final LiveData<F> firstLiveData,
@NonNull final LiveData<S> secondLiveData) {
final ZippedLiveData<F, S> zippedLiveData = new ZippedLiveData<>();
zippedLiveData.addFirstSource(firstLiveData);
zippedLiveData.addSecondSource(secondLiveData);
return zippedLiveData;
}
public static class ZippedLiveData<F, S> extends MediatorLiveData<Pair<F, S>> {
private F lastF;
private S lastS;
private void update() {
F localLastF = lastF;
S localLastS = lastS;
if (localLastF != null && localLastS != null) {
setValue(new Pair<>(localLastF, localLastS));
}
}
public void addFirstSource(@NonNull final LiveData<F> firstLiveData) {
addSource(firstLiveData, f -> {
lastF = f;
update();
});
}
public void addSecondSource(@NonNull final LiveData<S> secondLiveData) {
addSource(secondLiveData, s -> {
lastS = s;
update();
});
}
}
} }

View File

@ -1,18 +1,28 @@
package awais.instagrabber.utils; package awais.instagrabber.utils;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Build;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat; import androidx.core.content.res.ResourcesCompat;
import androidx.core.util.Pair; import androidx.core.util.Pair;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import androidx.dynamicanimation.animation.SpringAnimation;
import org.jetbrains.annotations.NotNull;
import kotlin.jvm.internal.Intrinsics;
public final class ViewUtils { public final class ViewUtils {
@ -69,4 +79,43 @@ public final class ViewUtils {
public static float getTextViewValueWidth(final TextView textView, final String text) { public static float getTextViewValueWidth(final TextView textView, final String text) {
return textView.getPaint().measureText(text); return textView.getPaint().measureText(text);
} }
/**
* Creates [SpringAnimation] for object.
* If finalPosition is not [Float.NaN] then create [SpringAnimation] with
* [SpringForce.mFinalPosition].
*
* @param object Object
* @param property object's property to be animated.
* @param finalPosition [SpringForce.mFinalPosition] Final position of spring.
* @return [SpringAnimation]
*/
@NonNull
public static SpringAnimation springAnimationOf(final Object object,
final FloatPropertyCompat<Object> property,
@Nullable final Float finalPosition) {
return finalPosition == null ? new SpringAnimation(object, property) : new SpringAnimation(object, property, finalPosition);
}
public static void suppressLayoutCompat(@NotNull ViewGroup $this$suppressLayoutCompat, boolean suppress) {
Intrinsics.checkNotNullParameter($this$suppressLayoutCompat, "$this$suppressLayoutCompat");
if (Build.VERSION.SDK_INT >= 29) {
$this$suppressLayoutCompat.suppressLayout(suppress);
} else {
hiddenSuppressLayout($this$suppressLayoutCompat, suppress);
}
}
private static boolean tryHiddenSuppressLayout = true;
@SuppressLint({"NewApi"})
private static void hiddenSuppressLayout(ViewGroup group, boolean suppress) {
if (tryHiddenSuppressLayout) {
try {
group.suppressLayout(suppress);
} catch (NoSuchMethodError var3) {
tryHiddenSuppressLayout = false;
}
}
}
} }

View File

@ -15,6 +15,7 @@ import java.io.InputStream;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@ -77,7 +78,12 @@ public final class EmojiParser {
.addAll(emoji.getVariants()) .addAll(emoji.getVariants())
.build() .build()
.stream()) .stream())
.collect(Collectors.toMap(Emoji::getUnicode, Function.identity())); .collect(Collectors.toMap(
Emoji::getUnicode,
Function.identity(),
(u, v) -> u,
LinkedHashMap::new
));
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "EmojiParser: ", e); Log.e(TAG, "EmojiParser: ", e);
} }

View File

@ -238,11 +238,7 @@ public class CommentsViewerViewModel extends ViewModel {
null, null,
false, false,
owner.getString("profile_pic_url"), owner.getString("profile_pic_url"),
null, owner.optBoolean("is_verified"));
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 = commentJsonObject.optJSONObject("edge_liked_by"); final JSONObject likedBy = commentJsonObject.optJSONObject("edge_liked_by");
final String commentId = commentJsonObject.getString("id"); final String commentId = commentJsonObject.getString("id");
final JSONObject childCommentsJsonObject = commentJsonObject.optJSONObject("edge_threaded_comments"); final JSONObject childCommentsJsonObject = commentJsonObject.optJSONObject("edge_threaded_comments");

View File

@ -34,7 +34,6 @@ import retrofit2.Response;
public class GraphQLService extends BaseService { public class GraphQLService extends BaseService {
private static final String TAG = "GraphQLService"; private static final String TAG = "GraphQLService";
// private static final boolean loadFromMock = false;
private final GraphQLRepository repository; private final GraphQLRepository repository;
@ -230,39 +229,7 @@ public class GraphQLService extends BaseService {
userObject.optString("full_name"), userObject.optString("full_name"),
userObject.optBoolean("is_private"), userObject.optBoolean("is_private"),
userObject.getString("profile_pic_url"), userObject.getString("profile_pic_url"),
null, userObject.optBoolean("is_verified")
new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
),
userObject.optBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
0,
0,
0,
0,
null,
null,
0,
null,
null,
null,
null,
null,
null
)); ));
// userModels.add(new ProfileModel(userObject.optBoolean("is_private"), // userModels.add(new ProfileModel(userObject.optBoolean("is_private"),
// false, // false,
@ -357,6 +324,7 @@ public class GraphQLService extends BaseService {
false, false,
false, false,
false, false,
false,
null, null,
null, null,
timelineMedia.getLong("count"), timelineMedia.getLong("count"),

View File

@ -143,39 +143,7 @@ public class StoriesService extends BaseService {
userJson.optString("full_name"), userJson.optString("full_name"),
userJson.optBoolean("is_private"), userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"), userJson.getString("profile_pic_url"),
null, userJson.optBoolean("is_verified")
new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
),
userJson.optBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
0,
0,
0,
0,
null,
null,
0,
null,
null,
null,
null,
null,
null
); );
final long timestamp = node.getLong("latest_reel_media"); final long timestamp = node.getLong("latest_reel_media");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp; final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp;
@ -210,39 +178,7 @@ public class StoriesService extends BaseService {
userJson.optString("full_name"), userJson.optString("full_name"),
userJson.optBoolean("is_private"), userJson.optBoolean("is_private"),
userJson.getString("profile_pic_url"), userJson.getString("profile_pic_url"),
null, userJson.optBoolean("is_verified")
new FriendshipStatus(
false,
false,
false,
false,
false,
false,
false,
false,
false,
false
),
userJson.optBoolean("is_verified"),
false,
false,
false,
false,
null,
null,
0,
0,
0,
0,
null,
null,
0,
null,
null,
null,
null,
null,
null
); );
feedStoryModels.add(new FeedStoryModel( feedStoryModels.add(new FeedStoryModel(
node.getString("id"), node.getString("id"),

View File

@ -65,6 +65,7 @@ public class IgErrorsInterceptor implements Interceptor {
if (body == null) return; if (body == null) return;
try { try {
final String bodyString = body.string(); final String bodyString = body.string();
Log.d(TAG, "checkError: " + bodyString);
final JSONObject jsonObject = new JSONObject(bodyString); final JSONObject jsonObject = new JSONObject(bodyString);
String message = jsonObject.optString("message"); String message = jsonObject.optString("message");
if (!TextUtils.isEmpty(message)) { if (!TextUtils.isEmpty(message)) {

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="50%p"
android:toXDelta="0" />
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

View File

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <translate xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="300" android:duration="300"
android:fromXDelta="100%" android:fromXDelta="100%"
android:fromYDelta="0%" android:toXDelta="0%" />
android:toXDelta="0%"
android:toYDelta="0%" />
</set>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@android:integer/config_mediumAnimTime"
android:fromXDelta="0"
android:toXDelta="-50%p" />
<alpha
android:duration="@android:integer/config_mediumAnimTime"
android:fromAlpha="1.0"
android:toAlpha="0.0" />
</set>

View File

@ -1,10 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" <translate xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false">
<translate
android:duration="300" android:duration="300"
android:fromXDelta="0%" android:fromXDelta="0%"
android:fromYDelta="0%" android:toXDelta="100%" />
android:toXDelta="100%"
android:toYDelta="0%" />
</set>

View File

@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="24dp"
android:height="24dp" android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24" android:viewportHeight="24">
android:tint="#333333">
<path <path
android:fillColor="@android:color/white" android:fillColor="@android:color/white"
android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/> android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z" />
</vector> </vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M17,3L7,3c-1.1,0 -2,0.9 -2,2v16l7,-3 7,3L19,5c0,-1.1 -0.9,-2 -2,-2zM17,18l-5,-2.18L7,18L7,6c0,-0.55 0.45,-1 1,-1h8c0.55,0 1,0.45 1,1v12z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M3,17.46v3.04c0,0.28 0.22,0.5 0.5,0.5h3.04c0.13,0 0.26,-0.05 0.35,-0.15L17.81,9.94l-3.75,-3.75L3.15,17.1c-0.1,0.1 -0.15,0.22 -0.15,0.36zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" <shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"> android:shape="oval">
<solid android:color="@color/black" /> <solid android:color="@android:color/transparent" />
</shape> </shape>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_container" android:id="@+id/main_container"
@ -62,13 +62,14 @@
android:id="@+id/main_nav_host" android:id="@+id/main_nav_host"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<!--app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"-->
<com.google.android.material.bottomnavigation.BottomNavigationView <com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavView" android:id="@+id/bottomNavView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_gravity="bottom"
app:labelVisibilityMode="auto" app:labelVisibilityMode="auto" />
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" /> </awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,48 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/black_a80"> android:background="?colorSurface"
tools:context=".fragments.PostViewV2Fragment">
<awais.instagrabber.customviews.drawee.DraggableZoomableDraweeView <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/post_image" android:id="@+id/content_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
android:background="@null"
android:clickable="true"
android:focusable="true"
android:transitionName="post_image"
app:actualImageScaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:background="@mipmap/ic_launcher"
tools:visibility="visible" />
<include
android:id="@+id/video_post"
layout="@layout/layout_video_player_with_thumbnail"
android:visibility="gone" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/slider_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible" />
<View
android:id="@+id/top_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/black_a80"
app:layout_constraintBottom_toBottomOf="@id/profile_pic"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<awais.instagrabber.customviews.ProfilePicView <awais.instagrabber.customviews.ProfilePicView
android:id="@+id/profile_pic" android:id="@+id/profile_pic"
@ -50,12 +18,12 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="12dp"
android:transitionName="profile_pic" android:transitionName="profile_pic"
app:layout_constraintBottom_toBottomOf="@id/top_bg" app:layout_constraintBottom_toTopOf="@id/top_barrier"
app:layout_constraintEnd_toStartOf="@id/title" app:layout_constraintEnd_toStartOf="@id/title"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/top_bg" app:layout_constraintTop_toTopOf="parent"
app:size="regular" /> app:size="regular" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
@ -65,7 +33,6 @@
android:ellipsize="marquee" android:ellipsize="marquee"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6" android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:textColor="@color/white"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@id/subtitle" app:layout_constraintBottom_toTopOf="@id/subtitle"
app:layout_constraintEnd_toStartOf="@id/options" app:layout_constraintEnd_toStartOf="@id/options"
@ -80,12 +47,12 @@
android:ellipsize="end" android:ellipsize="end"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1" android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@id/profile_pic" app:layout_constraintBottom_toBottomOf="@id/profile_pic"
app:layout_constraintEnd_toStartOf="@id/options" app:layout_constraintEnd_toStartOf="@id/options"
app:layout_constraintStart_toStartOf="@id/title" app:layout_constraintStart_toStartOf="@id/title"
app:layout_constraintTop_toBottomOf="@id/title" app:layout_constraintTop_toBottomOf="@id/title"
tools:text="Full name Full name Full name Full name Full name Full name Full name " /> tools:text="Full name Full name Full name Full name Full name Full name Full name "
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/options" android:id="@+id/options"
@ -94,19 +61,18 @@
android:paddingStart="8dp" android:paddingStart="8dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/top_bg" app:layout_constraintBottom_toBottomOf="@id/profile_pic"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/top_bg" app:layout_constraintTop_toTopOf="@id/profile_pic"
app:srcCompat="@drawable/ic_more_vert_24" app:srcCompat="@drawable/ic_more_vert_24"
app:tint="@color/white"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Barrier
android:id="@+id/user_details_group" android:id="@+id/top_barrier"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:constraint_referenced_ids="top_bg, profile_pic,title,subtitle,options" app:barrierAllowsGoneWidgets="true"
tools:visibility="visible" /> app:barrierDirection="bottom" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/media_counter" android:id="@+id/media_counter"
@ -121,7 +87,7 @@
android:textColor="@android:color/white" android:textColor="@android:color/white"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_bg" app:layout_constraintTop_toBottomOf="@id/profile_pic"
tools:text="1/5" tools:text="1/5"
tools:visibility="visible" /> tools:visibility="visible" />
@ -151,327 +117,12 @@
app:iconSize="16dp" app:iconSize="16dp"
app:iconTint="@color/white" app:iconTint="@color/white"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/top_bg" app:layout_constraintTop_toBottomOf="@id/profile_pic"
app:rippleColor="@color/grey_600" app:rippleColor="@color/grey_600"
tools:text="Location, Location, Location, Location, " tools:text="Location, Location, Location, Location, "
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.coordinatorlayout.widget.CoordinatorLayout <include layout="@layout/layout_post_view_bottom" />
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@null"
app:layout_constraintBottom_toBottomOf="@id/bottom_bg_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<androidx.core.widget.NestedScrollView </androidx.constraintlayout.widget.ConstraintLayout>
android:id="@+id/caption_parent" </androidx.core.widget.NestedScrollView>
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black_a80"
app:behavior_hideable="true"
app:behavior_peekHeight="100dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<ScrollView
android:id="@+id/bottom_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<awais.instagrabber.customviews.RamboTextViewV2
android:id="@+id/caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@null"
android:clickable="true"
android:focusable="true"
android:padding="16dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/white"
tools:text="Text text text" />
<!--<androidx.appcompat.widget.AppCompatTextView-->
<!-- android:id="@+id/editCaption"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_marginStart="16dp"-->
<!-- android:layout_marginTop="8dp"-->
<!-- android:background="@null"-->
<!-- android:gravity="center_vertical"-->
<!-- android:text="@string/edit_caption"-->
<!-- android:textColor="?android:textColorSecondary"-->
<!-- android:textSize="16sp"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintBottom_toTopOf="@id/translatedCaption"-->
<!-- app:layout_constraintTop_toBottomOf="@id/caption"-->
<!-- tools:visibility="visible" />-->
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/translate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@null"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:text="@string/translate_caption"
android:textColor="@color/blue_600"
android:textSize="16sp"
android:visibility="visible" />
<!--<awais.instagrabber.customviews.RamboTextViewV2-->
<!-- android:id="@+id/translatedCaption"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:layout_gravity="bottom"-->
<!-- android:background="@null"-->
<!-- android:clickable="true"-->
<!-- android:focusable="true"-->
<!-- android:padding="16dp"-->
<!-- android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"-->
<!-- android:textColor="@color/white"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintTop_toBottomOf="@id/translateTitle"-->
<!-- tools:text="Text text text"-->
<!-- tools:visibility="visible" />-->
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!--<include-->
<!-- android:id="@+id/player_controls"-->
<!-- layout="@layout/layout_exo_custom_controls"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:visibility="gone"-->
<!-- app:layout_constraintBottom_toTopOf="@id/bottom_bg_barrier"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- tools:visibility="gone" />-->
<View
android:id="@+id/bottom_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/black_a80"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/bottom_bg_barrier" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/bottom_bg_barrier"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="true"
app:barrierDirection="top"
app:constraint_referenced_ids="likes_count,comments_count,views_count" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/likes_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:textColor="@color/white"
app:layout_constraintBottom_toTopOf="@id/counts_barrier"
app:layout_constraintEnd_toStartOf="@id/comments_count"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bottom_bg_barrier"
tools:text="9999999999 likes"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/comments_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:textColor="@color/white"
app:layout_constraintBottom_toTopOf="@id/counts_barrier"
app:layout_constraintEnd_toStartOf="@id/views_count"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@id/likes_count"
app:layout_constraintTop_toBottomOf="@id/bottom_bg_barrier"
tools:text="9999999 comments"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/views_count"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="8dp"
android:textColor="@color/white"
app:layout_constraintBottom_toTopOf="@id/counts_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/comments_count"
app:layout_constraintTop_toBottomOf="@id/bottom_bg_barrier"
tools:text="9999999999 views"
tools:visibility="gone" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/counts_barrier"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="true"
app:barrierDirection="top"
app:constraint_referenced_ids="date" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@id/buttons_barrier"
app:layout_constraintTop_toBottomOf="@id/counts_barrier"
tools:text="2020-11-07 11:18:55"
tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/buttons_barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierAllowsGoneWidgets="true"
app:barrierDirection="bottom"
app:constraint_referenced_ids="date" />
<com.google.android.material.button.MaterialButton
android:id="@+id/caption_toggle"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:visibility="visible"
app:icon="@drawable/ic_notes_24"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/like"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
app:rippleColor="@color/grey_300"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/like"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:visibility="visible"
app:icon="@drawable/ic_not_liked"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/comment"
app:layout_constraintStart_toEndOf="@id/caption_toggle"
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
app:rippleColor="@color/grey_300"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/comment"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:visibility="visible"
app:icon="@drawable/ic_outline_comments_24"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/save"
app:layout_constraintStart_toEndOf="@id/like"
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
app:rippleColor="@color/grey_300"
tools:visibility="visible" />
<!--<com.google.android.material.button.MaterialButton-->
<!-- android:id="@+id/player_controls_toggle"-->
<!-- style="@style/Widget.MaterialComponents.Button.TextButton"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="48dp"-->
<!-- android:visibility="gone"-->
<!-- app:icon="@drawable/ic_play_circle_outline_24"-->
<!-- app:iconGravity="textStart"-->
<!-- app:iconPadding="0dp"-->
<!-- app:iconSize="24dp"-->
<!-- app:iconTint="@color/white"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toStartOf="@id/save"-->
<!-- app:layout_constraintStart_toEndOf="@id/comment"-->
<!-- app:layout_constraintTop_toBottomOf="@id/buttons_barrier"-->
<!-- app:rippleColor="@color/grey_300"-->
<!-- tools:visibility="visible" />-->
<com.google.android.material.button.MaterialButton
android:id="@+id/save"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:visibility="visible"
app:icon="@drawable/ic_outline_class_24"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/share"
app:layout_constraintStart_toEndOf="@id/comment"
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
app:rippleColor="@color/grey_300"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/share"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:visibility="visible"
app:icon="?attr/actionModeShareDrawable"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/download"
app:layout_constraintStart_toEndOf="@id/save"
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
app:rippleColor="@color/grey_300"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/download"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="48dp"
android:visibility="visible"
app:icon="@drawable/ic_download"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/share"
app:layout_constraintTop_toBottomOf="@id/buttons_barrier"
app:rippleColor="@color/grey_300"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -15,6 +15,7 @@
android:id="@+id/collapsing_toolbar_layout" android:id="@+id/collapsing_toolbar_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:contentScrim="?attr/toolbarColor"
app:layout_scrollFlags="scroll|exitUntilCollapsed"> app:layout_scrollFlags="scroll|exitUntilCollapsed">
<FrameLayout <FrameLayout
@ -39,6 +40,7 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="@null"
app:layout_collapseMode="pin" /> app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -1,22 +1,25 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <awais.instagrabber.customviews.InsetsAnimationLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"> android:clipToPadding="false"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/chats" android:id="@+id/chats"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none" android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@id/chats_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/layout_dm_base" /> tools:listitem="@layout/layout_dm_base" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/input_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/chats_barrier" android:id="@+id/chats_barrier"
android:layout_width="0dp" android:layout_width="0dp"
@ -252,17 +255,6 @@
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
tools:visibility="visible" /> tools:visibility="visible" />
<awais.instagrabber.customviews.emoji.EmojiPicker
android:id="@+id/emoji_picker"
android:layout_width="0dp"
android:layout_height="250dp"
android:translationY="250dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question" android:id="@+id/accept_pending_request_question"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -309,4 +301,15 @@
app:layout_constraintStart_toEndOf="@id/decline" app:layout_constraintStart_toEndOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question" app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" /> tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<awais.instagrabber.customviews.emoji.EmojiPicker
android:id="@+id/emoji_picker"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_marginBottom="-250dp"
android:alpha="0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</awais.instagrabber.customviews.InsetsAnimationLinearLayout>

View File

@ -10,12 +10,15 @@
android:id="@+id/swipe_refresh_layout" android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/topics_recycler_view" android:id="@+id/topics_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingBottom="?actionBarSize"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" app:spanCount="2"
tools:itemCount="10" tools:itemCount="10"

Some files were not shown because too many files have changed in this diff Show More