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:
commit
892bfa336f
@ -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",
|
||||||
|
15
.github/workflows/github_nightly_release.yml
vendored
15
.github/workflows/github_nightly_release.yml
vendored
@ -15,18 +15,19 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '8'
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build Github unsigned apk
|
- name: Build Github unsigned apk
|
||||||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre
|
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: ammargitham/sign-android-release@v1.1.1
|
||||||
# ID used to access action output
|
# ID used to access action output
|
||||||
id: sign_app
|
id: sign_app
|
||||||
with:
|
with:
|
||||||
@ -45,7 +46,8 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: barinsta_nightly_${{ steps.date.outputs.date }}
|
name: barinsta_nightly_${{ steps.date.outputs.date }}
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
path: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send success notification
|
# Send success notification
|
||||||
- name: Send success Telegram notification
|
- name: Send success Telegram notification
|
||||||
@ -55,7 +57,8 @@ jobs:
|
|||||||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
||||||
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
||||||
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nhttps://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||||
document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
document: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send failure notification
|
# Send failure notification
|
||||||
- name: Send failure Telegram notification
|
- name: Send failure Telegram notification
|
||||||
|
15
.github/workflows/github_pre_release.yml
vendored
15
.github/workflows/github_pre_release.yml
vendored
@ -16,18 +16,19 @@ jobs:
|
|||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: set up JDK 1.8
|
- name: set up JDK 1.8
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: 1.8
|
distribution: 'zulu'
|
||||||
|
java-version: '8'
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
|
|
||||||
- name: Build Github unsigned pre-release apk
|
- name: Build Github unsigned pre-release apk
|
||||||
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre
|
run: ./gradlew assembleGithubRelease --stacktrace --project-prop pre --project-prop split
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
uses: r0adkll/sign-android-release@v1
|
uses: ammargitham/sign-android-release@v1.1.1
|
||||||
# ID used to access action output
|
# ID used to access action output
|
||||||
id: sign_app
|
id: sign_app
|
||||||
with:
|
with:
|
||||||
@ -46,7 +47,8 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: barinsta_pre-release_${{ steps.date.outputs.date }}
|
name: barinsta_pre-release_${{ steps.date.outputs.date }}
|
||||||
path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# path: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
path: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send success notification
|
# Send success notification
|
||||||
- name: Send success Telegram notification
|
- name: Send success Telegram notification
|
||||||
@ -56,7 +58,8 @@ jobs:
|
|||||||
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
to: ${{ secrets.TELEGRAM_BUILDS_CHANNEL_TO }}
|
||||||
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
token: ${{ secrets.TELEGRAM_BUILDS_BOT_TOKEN }}
|
||||||
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
message: "${{ github.workflow }} ${{ github.job }} #${{ github.run_number }} completed successfully.\nURL: https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}"
|
||||||
document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
# document: ${{steps.sign_app.outputs.signedReleaseFile}}
|
||||||
|
document: app/build/outputs/apk/github/release/*-signed.apk
|
||||||
|
|
||||||
# Send failure notification
|
# Send failure notification
|
||||||
- name: Send failure Telegram notification
|
- name: Send failure Telegram notification
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="CompilerConfiguration">
|
<component name="CompilerConfiguration">
|
||||||
<bytecodeTargetLevel target="1.8" />
|
<bytecodeTargetLevel target="11" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
@ -7,7 +7,6 @@
|
|||||||
<option name="testRunner" value="PLATFORM" />
|
<option name="testRunner" value="PLATFORM" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="1.8" />
|
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectType">
|
<component name="ProjectType">
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<component name="RunConfigurationProducerService">
|
<component name="RunConfigurationProducerService">
|
||||||
<option name="ignoredProducers">
|
<option name="ignoredProducers">
|
||||||
<set>
|
<set>
|
||||||
|
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||||
|
28
README.md
28
README.md
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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" />
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
|
||||||
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onThumbnailLoaded(final int position) {}
|
public void onThumbnailLoaded(final int position) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClicked(final int position) {}
|
public void onItemClicked(final int position, final Media media, final View view) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerPlay(final int position) {}
|
public void onPlayerPlay(final int position) {}
|
||||||
@ -15,4 +21,12 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerRelease(final int position) {}
|
public void onPlayerRelease(final int position) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInFullScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package awais.instagrabber.adapters.viewholder;
|
|||||||
|
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -14,8 +13,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
|||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
||||||
|
import awais.instagrabber.customviews.drawee.DoubleTapGestureListener;
|
||||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
@ -24,13 +23,10 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
|||||||
private static final String TAG = "FeedSliderPhotoViewHolder";
|
private static final String TAG = "FeedSliderPhotoViewHolder";
|
||||||
|
|
||||||
private final ItemSliderPhotoBinding binding;
|
private final ItemSliderPhotoBinding binding;
|
||||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
|
||||||
|
|
||||||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
|
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding) {
|
||||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
|
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.onVerticalDragListener = onVerticalDragListener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(@NonNull final Media model,
|
public void bind(@NonNull final Media model,
|
||||||
@ -62,74 +58,19 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
|||||||
})
|
})
|
||||||
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
||||||
.build());
|
.build());
|
||||||
// binding.getRoot().setOnClickListener(v -> {
|
final DoubleTapGestureListener tapListener = new DoubleTapGestureListener(binding.getRoot()) {
|
||||||
// if (sliderCallback != null) {
|
|
||||||
// sliderCallback.onItemClicked(position);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
binding.getRoot().setTapListener(new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapUp(final MotionEvent e) {
|
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||||
if (sliderCallback != null) {
|
if (sliderCallback != null) {
|
||||||
sliderCallback.onItemClicked(position);
|
sliderCallback.onItemClicked(position, model, binding.getRoot());
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return super.onSingleTapConfirmed(e);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
binding.getRoot().setTapListener(tapListener);
|
||||||
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
||||||
zoomableController.setMaxScaleFactor(3f);
|
zoomableController.setMaxScaleFactor(3f);
|
||||||
binding.getRoot().setZoomableController(zoomableController);
|
binding.getRoot().setZoomableController(zoomableController);
|
||||||
if (onVerticalDragListener != null) {
|
binding.getRoot().setZoomingEnabled(true);
|
||||||
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
|
||||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
|
||||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
|
||||||
// final int spanWidth = deviceWidth / spanCount;
|
|
||||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
|
||||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
|
||||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
|
||||||
// if (animate) {
|
|
||||||
// Animation animation = AnimationUtils.expand(
|
|
||||||
// binding.imageViewer,
|
|
||||||
// layoutParams.width,
|
|
||||||
// layoutParams.height,
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// new Animation.AnimationListener() {
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationStart(final Animation animation) {
|
|
||||||
// showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationEnd(final Animation animation) {
|
|
||||||
// // showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationRepeat(final Animation animation) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// binding.imageViewer.startAnimation(animation);
|
|
||||||
// } else {
|
|
||||||
// layoutParams.width = width;
|
|
||||||
// layoutParams.height = height;
|
|
||||||
// binding.imageViewer.requestLayout();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void showOrHideDetails(final int spanCount) {
|
|
||||||
// if (spanCount == 1) {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// } else {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
@ -7,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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco;
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
@ -23,6 +22,7 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
|||||||
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
|
final AnimatedMediaFixedHeight fixedHeight = images.getFixedHeight();
|
||||||
if (fixedHeight == null) return;
|
if (fixedHeight == null) return;
|
||||||
final String url = fixedHeight.getWebp();
|
final String url = fixedHeight.getWebp();
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
fixedHeight.getHeight(),
|
fixedHeight.getHeight(),
|
||||||
fixedHeight.getWidth(),
|
fixedHeight.getWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
@ -56,8 +56,8 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder {
|
|||||||
);
|
);
|
||||||
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams();
|
||||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
final int width = widthHeight.first;
|
||||||
final int height = widthHeight.second != null ? widthHeight.second : 0;
|
final int height = widthHeight.second;
|
||||||
layoutParams.width = width;
|
layoutParams.width = width;
|
||||||
layoutParams.height = height;
|
layoutParams.height = height;
|
||||||
binding.ivAnimatedMessage.requestLayout();
|
binding.ivAnimatedMessage.requestLayout();
|
||||||
|
@ -6,7 +6,6 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
@ -31,6 +30,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemClip;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixShare;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
@ -103,15 +103,15 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder {
|
|||||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||||
.setRoundingParams(roundingParams)
|
.setRoundingParams(roundingParams)
|
||||||
.build());
|
.build());
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
mediaImageMaxWidth
|
mediaImageMaxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.mediaPreview.requestLayout();
|
binding.mediaPreview.requestLayout();
|
||||||
binding.mediaPreview.setTag(url);
|
binding.mediaPreview.setTag(url);
|
||||||
binding.mediaPreview.setImageURI(url);
|
binding.mediaPreview.setImageURI(url);
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
@ -14,11 +13,11 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
|||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
import awais.instagrabber.databinding.LayoutDmMediaBinding;
|
import awais.instagrabber.databinding.LayoutDmMediaBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.ImageVersions2;
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
|
|
||||||
@ -53,16 +52,16 @@ public class DirectItemMediaViewHolder extends DirectItemViewHolder {
|
|||||||
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
||||||
? View.VISIBLE
|
? View.VISIBLE
|
||||||
: View.GONE);
|
: View.GONE);
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
mediaImageMaxWidth
|
mediaImageMaxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.mediaPreview.getLayoutParams();
|
||||||
final int width = widthHeight.first != null ? widthHeight.first : 0;
|
final int width = widthHeight.first;
|
||||||
layoutParams.width = width;
|
layoutParams.width = width;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.mediaPreview.requestLayout();
|
binding.mediaPreview.requestLayout();
|
||||||
binding.bgTime.getLayoutParams().width = width;
|
binding.bgTime.getLayoutParams().width = width;
|
||||||
binding.bgTime.requestLayout();
|
binding.bgTime.requestLayout();
|
||||||
|
@ -4,7 +4,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
||||||
@ -21,6 +20,7 @@ import awais.instagrabber.repositories.responses.User;
|
|||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
@ -170,15 +170,15 @@ public class DirectItemRavenMediaViewHolder extends DirectItemViewHolder {
|
|||||||
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
binding.typeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO || modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER
|
||||||
? View.VISIBLE
|
? View.VISIBLE
|
||||||
: View.GONE);
|
: View.GONE);
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
media.getOriginalHeight(),
|
media.getOriginalHeight(),
|
||||||
media.getOriginalWidth(),
|
media.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
maxWidth
|
maxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.preview.getLayoutParams();
|
||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.preview.requestLayout();
|
binding.preview.requestLayout();
|
||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(media);
|
||||||
binding.preview.setImageURI(thumbUrl);
|
binding.preview.setImageURI(thumbUrl);
|
||||||
|
@ -5,7 +5,6 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.util.Pair;
|
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
|
|
||||||
import com.facebook.drawee.drawable.ScalingUtils;
|
import com.facebook.drawee.drawable.ScalingUtils;
|
||||||
@ -17,12 +16,12 @@ import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback;
|
|||||||
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
import awais.instagrabber.databinding.LayoutDmBaseBinding;
|
||||||
import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
|
import awais.instagrabber.databinding.LayoutDmStoryShareBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.ImageVersions2;
|
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare;
|
||||||
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
|
||||||
|
import awais.instagrabber.utils.NullSafePair;
|
||||||
import awais.instagrabber.utils.NumberUtils;
|
import awais.instagrabber.utils.NumberUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
@ -76,15 +75,15 @@ public class DirectItemStoryShareViewHolder extends DirectItemViewHolder {
|
|||||||
.setRoundingParams(roundingParams)
|
.setRoundingParams(roundingParams)
|
||||||
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
|
||||||
.build());
|
.build());
|
||||||
final Pair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
final NullSafePair<Integer, Integer> widthHeight = NumberUtils.calculateWidthHeight(
|
||||||
storyShareMedia.getOriginalHeight(),
|
storyShareMedia.getOriginalHeight(),
|
||||||
storyShareMedia.getOriginalWidth(),
|
storyShareMedia.getOriginalWidth(),
|
||||||
mediaImageMaxHeight,
|
mediaImageMaxHeight,
|
||||||
mediaImageMaxWidth
|
mediaImageMaxWidth
|
||||||
);
|
);
|
||||||
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
|
final ViewGroup.LayoutParams layoutParams = binding.ivMediaPreview.getLayoutParams();
|
||||||
layoutParams.width = widthHeight.first != null ? widthHeight.first : 0;
|
layoutParams.width = widthHeight.first;
|
||||||
layoutParams.height = widthHeight.second != null ? widthHeight.second : 0;
|
layoutParams.height = widthHeight.second;
|
||||||
binding.ivMediaPreview.requestLayout();
|
binding.ivMediaPreview.requestLayout();
|
||||||
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
|
final String thumbUrl = ResponseBodyUtils.getThumbUrl(storyShareMedia);
|
||||||
binding.ivMediaPreview.setImageURI(thumbUrl);
|
binding.ivMediaPreview.setImageURI(thumbUrl);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -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));
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.navigation.NavDestination;
|
||||||
|
import androidx.navigation.NavOptions;
|
||||||
|
import androidx.navigation.Navigator;
|
||||||
|
import androidx.navigation.fragment.FragmentNavigator;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
|
||||||
|
@Navigator.Name("fragment")
|
||||||
|
public class FragmentNavigatorWithDefaultAnimations extends FragmentNavigator {
|
||||||
|
|
||||||
|
private final NavOptions emptyNavOptions = new NavOptions.Builder().build();
|
||||||
|
// private final NavOptions defaultNavOptions = new NavOptions.Builder()
|
||||||
|
// .setEnterAnim(R.animator.nav_default_enter_anim)
|
||||||
|
// .setExitAnim(R.animator.nav_default_exit_anim)
|
||||||
|
// .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
|
||||||
|
// .setPopExitAnim(R.animator.nav_default_pop_exit_anim)
|
||||||
|
// .build();
|
||||||
|
|
||||||
|
private final NavOptions defaultNavOptions = new NavOptions.Builder()
|
||||||
|
.setEnterAnim(R.anim.slide_in_right)
|
||||||
|
.setExitAnim(R.anim.slide_out_left)
|
||||||
|
.setPopEnterAnim(android.R.anim.slide_in_left)
|
||||||
|
.setPopExitAnim(android.R.anim.slide_out_right)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
public FragmentNavigatorWithDefaultAnimations(@NonNull final Context context,
|
||||||
|
@NonNull final FragmentManager manager,
|
||||||
|
final int containerId) {
|
||||||
|
super(context, manager, containerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public NavDestination navigate(@NonNull final Destination destination,
|
||||||
|
@Nullable final Bundle args,
|
||||||
|
@Nullable final NavOptions navOptions,
|
||||||
|
@Nullable final Navigator.Extras navigatorExtras) {
|
||||||
|
// this will try to fill in empty animations with defaults when no shared element transitions are set
|
||||||
|
// https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element
|
||||||
|
final boolean shouldUseTransitionsInstead = navigatorExtras != null;
|
||||||
|
final NavOptions navOptions1 = shouldUseTransitionsInstead ? navOptions : fillEmptyAnimationsWithDefaults(navOptions);
|
||||||
|
return super.navigate(destination, args, navOptions1, navigatorExtras);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NavOptions fillEmptyAnimationsWithDefaults(@Nullable final NavOptions navOptions) {
|
||||||
|
if (navOptions == null) {
|
||||||
|
return defaultNavOptions;
|
||||||
|
}
|
||||||
|
return copyNavOptionsWithDefaultAnimations(navOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private NavOptions copyNavOptionsWithDefaultAnimations(@NonNull final NavOptions navOptions) {
|
||||||
|
return new NavOptions.Builder()
|
||||||
|
.setLaunchSingleTop(navOptions.shouldLaunchSingleTop())
|
||||||
|
.setPopUpTo(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive())
|
||||||
|
.setEnterAnim(navOptions.getEnterAnim() == emptyNavOptions.getEnterAnim()
|
||||||
|
? defaultNavOptions.getEnterAnim() : navOptions.getEnterAnim())
|
||||||
|
.setExitAnim(navOptions.getExitAnim() == emptyNavOptions.getExitAnim()
|
||||||
|
? defaultNavOptions.getExitAnim() : navOptions.getExitAnim())
|
||||||
|
.setPopEnterAnim(navOptions.getPopEnterAnim() == emptyNavOptions.getPopEnterAnim()
|
||||||
|
? defaultNavOptions.getPopEnterAnim() : navOptions.getPopEnterAnim())
|
||||||
|
.setPopExitAnim(navOptions.getPopExitAnim() == emptyNavOptions.getPopExitAnim()
|
||||||
|
? defaultNavOptions.getPopExitAnim() : navOptions.getPopExitAnim())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,246 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowInsetsAnimation;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.view.NestedScrollingParent3;
|
||||||
|
import androidx.core.view.NestedScrollingParentHelper;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import awais.instagrabber.customviews.helpers.SimpleImeAnimationController;
|
||||||
|
import awais.instagrabber.utils.ViewUtils;
|
||||||
|
|
||||||
|
import static androidx.core.view.ViewCompat.TYPE_TOUCH;
|
||||||
|
|
||||||
|
public final class InsetsAnimationLinearLayout extends LinearLayout implements NestedScrollingParent3 {
|
||||||
|
private final NestedScrollingParentHelper nestedScrollingParentHelper = new NestedScrollingParentHelper(this);
|
||||||
|
private final SimpleImeAnimationController imeAnimController = new SimpleImeAnimationController();
|
||||||
|
private final int[] tempIntArray2 = new int[2];
|
||||||
|
private final int[] startViewLocation = new int[2];
|
||||||
|
|
||||||
|
private View currentNestedScrollingChild;
|
||||||
|
private int dropNextY;
|
||||||
|
private boolean scrollImeOffScreenWhenVisible = true;
|
||||||
|
private boolean scrollImeOnScreenWhenNotVisible = true;
|
||||||
|
private boolean scrollImeOffScreenWhenVisibleOnFling = false;
|
||||||
|
private boolean scrollImeOnScreenWhenNotVisibleOnFling = false;
|
||||||
|
|
||||||
|
public InsetsAnimationLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsAnimationLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean getScrollImeOffScreenWhenVisible() {
|
||||||
|
return scrollImeOffScreenWhenVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setScrollImeOffScreenWhenVisible(boolean scrollImeOffScreenWhenVisible) {
|
||||||
|
this.scrollImeOffScreenWhenVisible = scrollImeOffScreenWhenVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean getScrollImeOnScreenWhenNotVisible() {
|
||||||
|
return scrollImeOnScreenWhenNotVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void setScrollImeOnScreenWhenNotVisible(boolean scrollImeOnScreenWhenNotVisible) {
|
||||||
|
this.scrollImeOnScreenWhenNotVisible = scrollImeOnScreenWhenNotVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getScrollImeOffScreenWhenVisibleOnFling() {
|
||||||
|
return scrollImeOffScreenWhenVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollImeOffScreenWhenVisibleOnFling(final boolean scrollImeOffScreenWhenVisibleOnFling) {
|
||||||
|
this.scrollImeOffScreenWhenVisibleOnFling = scrollImeOffScreenWhenVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean getScrollImeOnScreenWhenNotVisibleOnFling() {
|
||||||
|
return scrollImeOnScreenWhenNotVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollImeOnScreenWhenNotVisibleOnFling(final boolean scrollImeOnScreenWhenNotVisibleOnFling) {
|
||||||
|
this.scrollImeOnScreenWhenNotVisibleOnFling = scrollImeOnScreenWhenNotVisibleOnFling;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleImeAnimationController getImeAnimController() {
|
||||||
|
return imeAnimController;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onStartNestedScroll(@NonNull final View child,
|
||||||
|
@NonNull final View target,
|
||||||
|
final int axes,
|
||||||
|
final int type) {
|
||||||
|
return (axes & SCROLL_AXIS_VERTICAL) != 0 && type == TYPE_TOUCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScrollAccepted(@NonNull final View child,
|
||||||
|
@NonNull final View target,
|
||||||
|
final int axes,
|
||||||
|
final int type) {
|
||||||
|
nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
|
||||||
|
currentNestedScrollingChild = child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedPreScroll(@NonNull final View target,
|
||||||
|
final int dx,
|
||||||
|
final int dy,
|
||||||
|
@NonNull final int[] consumed,
|
||||||
|
final int type) {
|
||||||
|
if (imeAnimController.isInsetAnimationRequestPending()) {
|
||||||
|
consumed[0] = dx;
|
||||||
|
consumed[1] = dy;
|
||||||
|
} else {
|
||||||
|
int deltaY = dy;
|
||||||
|
if (dropNextY != 0) {
|
||||||
|
consumed[1] = dropNextY;
|
||||||
|
deltaY = dy - dropNextY;
|
||||||
|
dropNextY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deltaY < 0) {
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress()) {
|
||||||
|
consumed[1] -= imeAnimController.insetBy(-deltaY);
|
||||||
|
} else if (scrollImeOffScreenWhenVisible && !imeAnimController.isInsetAnimationRequestPending()) {
|
||||||
|
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
|
||||||
|
if (rootWindowInsets != null) {
|
||||||
|
if (rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
|
||||||
|
startControlRequest();
|
||||||
|
consumed[1] = deltaY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScroll(@NonNull final View target,
|
||||||
|
final int dxConsumed,
|
||||||
|
final int dyConsumed,
|
||||||
|
final int dxUnconsumed,
|
||||||
|
final int dyUnconsumed,
|
||||||
|
final int type,
|
||||||
|
@NonNull final int[] consumed) {
|
||||||
|
if (dyUnconsumed > 0) {
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress()) {
|
||||||
|
consumed[1] = -imeAnimController.insetBy(-dyUnconsumed);
|
||||||
|
} else if (scrollImeOnScreenWhenNotVisible && !imeAnimController.isInsetAnimationRequestPending()) {
|
||||||
|
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
|
||||||
|
if (rootWindowInsets != null) {
|
||||||
|
if (!rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
|
||||||
|
startControlRequest();
|
||||||
|
consumed[1] = dyUnconsumed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onNestedFling(@NonNull final View target,
|
||||||
|
final float velocityX,
|
||||||
|
final float velocityY,
|
||||||
|
final boolean consumed) {
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress()) {
|
||||||
|
imeAnimController.animateToFinish(velocityY);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
boolean imeVisible = false;
|
||||||
|
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
|
||||||
|
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
|
||||||
|
imeVisible = true;
|
||||||
|
}
|
||||||
|
if (velocityY > 0 && scrollImeOnScreenWhenNotVisibleOnFling && !imeVisible) {
|
||||||
|
imeAnimController.startAndFling(this, velocityY);
|
||||||
|
return true;
|
||||||
|
} else if (velocityY < 0 && scrollImeOffScreenWhenVisibleOnFling && imeVisible) {
|
||||||
|
imeAnimController.startAndFling(this, velocityY);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopNestedScroll(@NonNull final View target, final int type) {
|
||||||
|
nestedScrollingParentHelper.onStopNestedScroll(target, type);
|
||||||
|
if (imeAnimController.isInsetAnimationInProgress() && !imeAnimController.isInsetAnimationFinishing()) {
|
||||||
|
imeAnimController.animateToFinish(null);
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispatchWindowInsetsAnimationPrepare(@NonNull final WindowInsetsAnimation animation) {
|
||||||
|
super.dispatchWindowInsetsAnimationPrepare(animation);
|
||||||
|
ViewUtils.suppressLayoutCompat(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startControlRequest() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ViewUtils.suppressLayoutCompat(this, true);
|
||||||
|
if (currentNestedScrollingChild != null) {
|
||||||
|
currentNestedScrollingChild.getLocationInWindow(startViewLocation);
|
||||||
|
}
|
||||||
|
imeAnimController.startControlRequest(this, windowInsetsAnimationControllerCompat -> onControllerReady());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onControllerReady() {
|
||||||
|
if (currentNestedScrollingChild != null) {
|
||||||
|
imeAnimController.insetBy(0);
|
||||||
|
int[] location = tempIntArray2;
|
||||||
|
currentNestedScrollingChild.getLocationInWindow(location);
|
||||||
|
dropNextY = location[1] - startViewLocation[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reset() {
|
||||||
|
dropNextY = 0;
|
||||||
|
Arrays.fill(startViewLocation, 0);
|
||||||
|
ViewUtils.suppressLayoutCompat(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScrollAccepted(@NonNull final View child,
|
||||||
|
@NonNull final View target,
|
||||||
|
final int axes) {
|
||||||
|
onNestedScrollAccepted(child, target, axes, TYPE_TOUCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNestedScroll(@NonNull final View target,
|
||||||
|
final int dxConsumed,
|
||||||
|
final int dyConsumed,
|
||||||
|
final int dxUnconsumed,
|
||||||
|
final int dyUnconsumed,
|
||||||
|
final int type) {
|
||||||
|
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, tempIntArray2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopNestedScroll(@NonNull final View target) {
|
||||||
|
onStopNestedScroll(target, TYPE_TOUCH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
||||||
|
|
||||||
|
public class InsetsNotifyingCoordinatorLayout extends CoordinatorLayout {
|
||||||
|
|
||||||
|
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||||
|
int childCount = getChildCount();
|
||||||
|
for (int index = 0; index < childCount; index++) {
|
||||||
|
getChildAt(index).dispatchApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.WindowInsets;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
public class InsetsNotifyingLinearLayout extends LinearLayout {
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public InsetsNotifyingLinearLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
|
||||||
|
int childCount = getChildCount();
|
||||||
|
for (int index = 0; index < childCount; index++) {
|
||||||
|
getChildAt(index).dispatchApplyWindowInsets(insets);
|
||||||
|
}
|
||||||
|
return insets;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import androidx.annotation.NavigationRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.navigation.NavController;
|
||||||
|
import androidx.navigation.Navigator;
|
||||||
|
import androidx.navigation.fragment.FragmentNavigator;
|
||||||
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
|
public class NavHostFragmentWithDefaultAnimations extends NavHostFragment {
|
||||||
|
private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId";
|
||||||
|
private static final String KEY_START_DESTINATION_ARGS =
|
||||||
|
"android-support-nav:fragment:startDestinationArgs";
|
||||||
|
private static final String KEY_NAV_CONTROLLER_STATE =
|
||||||
|
"android-support-nav:fragment:navControllerState";
|
||||||
|
private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost";
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static NavHostFragment create(@NavigationRes int graphResId) {
|
||||||
|
return create(graphResId, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static NavHostFragment create(@NavigationRes int graphResId,
|
||||||
|
@Nullable Bundle startDestinationArgs) {
|
||||||
|
Bundle b = null;
|
||||||
|
if (graphResId != 0) {
|
||||||
|
b = new Bundle();
|
||||||
|
b.putInt(KEY_GRAPH_ID, graphResId);
|
||||||
|
}
|
||||||
|
if (startDestinationArgs != null) {
|
||||||
|
if (b == null) {
|
||||||
|
b = new Bundle();
|
||||||
|
}
|
||||||
|
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
final NavHostFragmentWithDefaultAnimations result = new NavHostFragmentWithDefaultAnimations();
|
||||||
|
if (b != null) {
|
||||||
|
result.setArguments(b);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
|
||||||
|
return new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreateNavController(@NonNull final NavController navController) {
|
||||||
|
super.onCreateNavController(navController);
|
||||||
|
navController.getNavigatorProvider()
|
||||||
|
.addNavigator(new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId()));
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,8 @@ package awais.instagrabber.customviews;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -25,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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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));
|
||||||
}
|
}
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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: {
|
||||||
|
@ -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())
|
||||||
|
@ -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() {
|
||||||
|
@ -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
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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() {
|
||||||
|
@ -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;
|
||||||
|
@ -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("@", "");
|
||||||
|
@ -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() {
|
||||||
|
@ -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);
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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());
|
||||||
|
@ -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) {
|
||||||
|
91
app/src/main/java/awais/instagrabber/utils/NullSafePair.java
Normal file
91
app/src/main/java/awais/instagrabber/utils/NullSafePair.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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})
|
||||||
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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"),
|
||||||
|
@ -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"),
|
||||||
|
@ -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)) {
|
||||||
|
11
app/src/main/res/anim/slide_in_right.xml
Normal file
11
app/src/main/res/anim/slide_in_right.xml
Normal 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>
|
@ -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>
|
|
11
app/src/main/res/anim/slide_out_left.xml
Normal file
11
app/src/main/res/anim/slide_out_left.xml
Normal 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>
|
@ -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>
|
|
@ -1,9 +1,9 @@
|
|||||||
<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" />
|
||||||
|
10
app/src/main/res/drawable/ic_round_bookmark_border_24.xml
Normal file
10
app/src/main/res/drawable/ic_round_bookmark_border_24.xml
Normal 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>
|
10
app/src/main/res/drawable/ic_round_edit_24.xml
Normal file
10
app/src/main/res/drawable/ic_round_edit_24.xml
Normal 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>
|
@ -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>
|
@ -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>
|
|
@ -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
|
|
||||||
android:id="@+id/caption_parent"
|
|
||||||
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
@ -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>
|
||||||
|
@ -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"
|
||||||
@ -310,3 +302,14 @@
|
|||||||
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>
|
@ -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
Loading…
Reference in New Issue
Block a user