mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-12-23 13:26:59 +00:00
Merge branch 'master' into split-abi-apks
This commit is contained in:
commit
b56f3e69e2
@ -32,6 +32,24 @@
|
|||||||
"question"
|
"question"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"login": "zerrium",
|
||||||
|
"name": "Zerrium",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/58355441?v=4",
|
||||||
|
"profile": "https://github.com/zerrium",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "junhuicoding",
|
||||||
|
"name": "Chua Jun Hui",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/54289027?v=4",
|
||||||
|
"profile": "https://github.com/junhuicoding",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"login": "andersonvom",
|
"login": "andersonvom",
|
||||||
"name": "Anderson Mesquita",
|
"name": "Anderson Mesquita",
|
||||||
@ -42,6 +60,16 @@
|
|||||||
"bug"
|
"bug"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"login": "vojta-horanek",
|
||||||
|
"name": "Vojtěch Hořánek",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/12630566?v=4",
|
||||||
|
"profile": "https://vojtechh.eu/",
|
||||||
|
"contributions": [
|
||||||
|
"code",
|
||||||
|
"translation"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"login": "MeLlamoPablo",
|
"login": "MeLlamoPablo",
|
||||||
"name": "Pablo Rodríguez",
|
"name": "Pablo Rodríguez",
|
||||||
|
5
.codebeatsettings
Normal file
5
.codebeatsettings
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"JAVA": {
|
||||||
|
"TOO_MANY_IVARS": [8, 10, 20, 30]
|
||||||
|
}
|
||||||
|
}
|
@ -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" />
|
||||||
|
4
.project
4
.project
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<projectDescription>
|
<projectDescription>
|
||||||
<name>InstaGrabber</name>
|
<name>Barinsta</name>
|
||||||
<comment>Project instagrabber created by Buildship.</comment>
|
<comment>Project instagrabber created by Buildship.</comment>
|
||||||
<projects>
|
<projects>
|
||||||
</projects>
|
</projects>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
</natures>
|
</natures>
|
||||||
<filteredResources>
|
<filteredResources>
|
||||||
<filter>
|
<filter>
|
||||||
<id>1600117114918</id>
|
<id>0</id>
|
||||||
<name></name>
|
<name></name>
|
||||||
<type>30</type>
|
<type>30</type>
|
||||||
<matcher>
|
<matcher>
|
||||||
|
29
README.md
29
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-39-orange.svg)](#contributors)
|
[![All Contributors](https://img.shields.io/badge/all_contributors-42-orange.svg)](#contributors)
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
Instagram client; previously known as InstaGrabber.
|
Instagram client; previously known as InstaGrabber.
|
||||||
@ -56,52 +56,55 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
|
|||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://austinhuang.me"><img src="https://avatars1.githubusercontent.com/u/16656689?s=100" width="100px;" alt=""/><br /><sub><b>Austin Huang</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Documentation">📖</a> <a href="#question-austinhuang0131" title="Answering Questions">💬</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a> <a href="#ideas-austinhuang0131" title="Ideas, Planning, & Feedback">🤔</a></td>
|
<td align="center"><a href="https://austinhuang.me"><img src="https://avatars1.githubusercontent.com/u/16656689?s=100" width="100px;" alt=""/><br /><sub><b>Austin Huang</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/commits?author=austinhuang0131" title="Documentation">📖</a> <a href="#question-austinhuang0131" title="Answering Questions">💬</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a> <a href="#ideas-austinhuang0131" title="Ideas, Planning, & Feedback">🤔</a></td>
|
||||||
<td align="center"><a href="https://github.com/ammargitham"><img src="https://avatars0.githubusercontent.com/u/8017365?s=100" width="100px;" alt=""/><br /><sub><b>Ammar Githam</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=ammargitham" title="Code">💻</a> <a href="#design-ammargitham" title="Design">🎨</a> <a href="#ideas-ammargitham" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-ammargitham" title="Maintenance">🚧</a> <a href="#question-ammargitham" title="Answering Questions">💬</a></td>
|
<td align="center"><a href="https://github.com/ammargitham"><img src="https://avatars0.githubusercontent.com/u/8017365?s=100" width="100px;" alt=""/><br /><sub><b>Ammar Githam</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=ammargitham" title="Code">💻</a> <a href="#design-ammargitham" title="Design">🎨</a> <a href="#ideas-ammargitham" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-ammargitham" title="Maintenance">🚧</a> <a href="#question-ammargitham" title="Answering Questions">💬</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/zerrium"><img src="https://avatars.githubusercontent.com/u/58355441?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Zerrium</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=zerrium" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/junhuicoding"><img src="https://avatars.githubusercontent.com/u/54289027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chua Jun Hui</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=junhuicoding" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://github.com/andersonvom"><img src="https://avatars3.githubusercontent.com/u/69922?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anderson Mesquita</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=andersonvom" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aandersonvom" title="Bug reports">🐛</a></td>
|
<td align="center"><a href="https://github.com/andersonvom"><img src="https://avatars3.githubusercontent.com/u/69922?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anderson Mesquita</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=andersonvom" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aandersonvom" title="Bug reports">🐛</a></td>
|
||||||
|
<td align="center"><a href="https://vojtechh.eu/"><img src="https://avatars.githubusercontent.com/u/12630566?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Vojtěch Hořánek</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=vojta-horanek" title="Code">💻</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
</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/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>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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="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/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center"><a href="https://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>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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/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/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/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>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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://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://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center"><a href="https://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>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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/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/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/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>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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://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://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<td align="center"><a href="https://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>
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<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>
|
||||||
<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>
|
||||||
|
@ -20,8 +20,8 @@ android {
|
|||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 29
|
targetSdkVersion 29
|
||||||
|
|
||||||
versionCode 60
|
versionCode 62
|
||||||
versionName '19.1.0'
|
versionName '19.2.1'
|
||||||
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
@ -33,6 +33,12 @@ android {
|
|||||||
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@ -133,6 +139,13 @@ android {
|
|||||||
outputFileName = abi == null ? "barinsta_${suffix}.apk" : "barinsta_${suffix}_${abi}.apk"
|
outputFileName = abi == null ? "barinsta_${suffix}.apk" : "barinsta_${suffix}_${abi}.apk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
// Exclude file to avoid
|
||||||
|
// Error: Duplicate files during packaging of APK
|
||||||
|
exclude 'META-INF/LICENSE.md'
|
||||||
|
exclude 'META-INF/LICENSE-notice.md'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
@ -143,8 +156,8 @@ 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.2'
|
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-alpha02'
|
||||||
|
|
||||||
@ -154,7 +167,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||||
implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
|
implementation "androidx.recyclerview:recyclerview:1.2.0"
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.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"
|
||||||
@ -174,18 +187,16 @@ dependencies {
|
|||||||
annotationProcessor "androidx.room:room-compiler:$room_version"
|
annotationProcessor "androidx.room:room-compiler:$room_version"
|
||||||
|
|
||||||
// CameraX
|
// CameraX
|
||||||
def camerax_version = "1.1.0-alpha03"
|
def camerax_version = "1.1.0-alpha04"
|
||||||
implementation "androidx.camera:camera-camera2:$camerax_version"
|
implementation "androidx.camera:camera-camera2:$camerax_version"
|
||||||
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
implementation "androidx.camera:camera-lifecycle:$camerax_version"
|
||||||
implementation "androidx.camera:camera-view:1.0.0-alpha22"
|
implementation "androidx.camera:camera-view:1.0.0-alpha24"
|
||||||
|
|
||||||
// EmojiCompat
|
// EmojiCompat
|
||||||
def emoji_compat_version = "1.1.0"
|
def emoji_compat_version = "1.1.0"
|
||||||
implementation "androidx.emoji:emoji:$emoji_compat_version"
|
implementation "androidx.emoji:emoji:$emoji_compat_version"
|
||||||
implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
|
implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
|
||||||
|
|
||||||
implementation 'me.austinhuang:AutoLinkTextViewV2:-SNAPSHOT'
|
|
||||||
|
|
||||||
implementation 'com.facebook.fresco:fresco:2.3.0'
|
implementation 'com.facebook.fresco:fresco:2.3.0'
|
||||||
implementation 'com.facebook.fresco:animated-webp:2.3.0'
|
implementation 'com.facebook.fresco:animated-webp:2.3.0'
|
||||||
implementation 'com.facebook.fresco:webpsupport:2.3.0'
|
implementation 'com.facebook.fresco:webpsupport:2.3.0'
|
||||||
@ -195,6 +206,10 @@ dependencies {
|
|||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
|
|
||||||
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
|
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
|
||||||
|
|
||||||
|
implementation 'com.github.skydoves:balloon:1.3.4'
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
@ -203,4 +218,11 @@ dependencies {
|
|||||||
githubImplementation 'io.sentry:sentry-android:4.3.0'
|
githubImplementation 'io.sentry:sentry-android:4.3.0'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
|
||||||
|
|
||||||
|
androidTestImplementation 'org.junit.jupiter:junit-jupiter:5.7.1'
|
||||||
|
androidTestImplementation 'androidx.test:core:1.3.0'
|
||||||
|
androidTestImplementation 'com.android.support:support-annotations:28.0.0'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation "androidx.room:room-testing:2.2.6"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
227
app/schemas/awais.instagrabber.db.AppDatabase/6.json
Normal file
227
app/schemas/awais.instagrabber.db.AppDatabase/6.json
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "232e618b3bfcb4661336b359d036c455",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "accounts",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uid` TEXT, `username` TEXT, `cookie` TEXT, `full_name` TEXT, `profile_pic` TEXT)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "cookie",
|
||||||
|
"columnName": "cookie",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "fullName",
|
||||||
|
"columnName": "full_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "profilePic",
|
||||||
|
"columnName": "profile_pic",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "favorites",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query_text` TEXT, `type` TEXT, `display_name` TEXT, `pic_url` TEXT, `date_added` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "query",
|
||||||
|
"columnName": "query_text",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "displayName",
|
||||||
|
"columnName": "display_name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "picUrl",
|
||||||
|
"columnName": "pic_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "dateAdded",
|
||||||
|
"columnName": "date_added",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "dm_last_notified",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `thread_id` TEXT, `last_notified_msg_ts` INTEGER, `last_notified_at` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "threadId",
|
||||||
|
"columnName": "thread_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastNotifiedMsgTs",
|
||||||
|
"columnName": "last_notified_msg_ts",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastNotifiedAt",
|
||||||
|
"columnName": "last_notified_at",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_dm_last_notified_thread_id",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"thread_id"
|
||||||
|
],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_dm_last_notified_thread_id` ON `${TABLE_NAME}` (`thread_id`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "recent_searches",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ig_id` TEXT NOT NULL, `name` TEXT NOT NULL, `username` TEXT, `pic_url` TEXT, `type` TEXT NOT NULL, `last_searched_on` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "igId",
|
||||||
|
"columnName": "ig_id",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "username",
|
||||||
|
"columnName": "username",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "picUrl",
|
||||||
|
"columnName": "pic_url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "lastSearchedOn",
|
||||||
|
"columnName": "last_searched_on",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_recent_searches_ig_id_type",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"ig_id",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `${TABLE_NAME}` (`ig_id`, `type`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '232e618b3bfcb4661336b359d036c455')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package awais.instagrabber.db;
|
||||||
|
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.room.migration.Migration;
|
||||||
|
import androidx.room.testing.MigrationTestHelper;
|
||||||
|
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||||
|
import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static awais.instagrabber.db.AppDatabase.MIGRATION_4_5;
|
||||||
|
import static awais.instagrabber.db.AppDatabase.MIGRATION_5_6;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class MigrationTest {
|
||||||
|
private static final String TEST_DB = "migration-test";
|
||||||
|
private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_4_5, MIGRATION_5_6};
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public MigrationTestHelper helper;
|
||||||
|
|
||||||
|
public MigrationTest() {
|
||||||
|
final String canonicalName = AppDatabase.class.getCanonicalName();
|
||||||
|
assert canonicalName != null;
|
||||||
|
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
|
||||||
|
canonicalName,
|
||||||
|
new FrameworkSQLiteOpenHelperFactory());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void migrateAll() throws IOException {
|
||||||
|
// Create earliest version of the database. Have to start with 4 since that is the version we migrated to Room.
|
||||||
|
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 4);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
// Open latest version of the database. Room will validate the schema
|
||||||
|
// once all migrations execute.
|
||||||
|
AppDatabase appDb = Room.databaseBuilder(InstrumentationRegistry.getInstrumentation().getTargetContext(),
|
||||||
|
AppDatabase.class,
|
||||||
|
TEST_DB)
|
||||||
|
.addMigrations(ALL_MIGRATIONS).build();
|
||||||
|
appDb.getOpenHelper().getWritableDatabase();
|
||||||
|
appDb.close();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package awais.instagrabber.db.dao;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.Room;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.db.AppDatabase;
|
||||||
|
import awais.instagrabber.db.entities.RecentSearch;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class RecentSearchDaoTest {
|
||||||
|
private static final String TAG = RecentSearchDaoTest.class.getSimpleName();
|
||||||
|
|
||||||
|
private RecentSearchDao dao;
|
||||||
|
private AppDatabase db;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createDb() {
|
||||||
|
final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
|
||||||
|
dao = db.recentSearchDao();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void closeDb() {
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeQueryDelete() {
|
||||||
|
final RecentSearch recentSearch = insertRecentSearch("1", "test1", FavoriteType.HASHTAG);
|
||||||
|
final RecentSearch byIgIdAndType = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG);
|
||||||
|
Assertions.assertEquals(recentSearch, byIgIdAndType);
|
||||||
|
dao.deleteRecentSearch(byIgIdAndType);
|
||||||
|
final RecentSearch deleted = dao.getRecentSearchByIgIdAndType("1", FavoriteType.HASHTAG);
|
||||||
|
Assertions.assertNull(deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void queryAllOrdered() {
|
||||||
|
final List<RecentSearch> insertListReversed = ImmutableList
|
||||||
|
.<RecentSearch>builder()
|
||||||
|
.add(insertRecentSearch("1", "test1", FavoriteType.HASHTAG))
|
||||||
|
.add(insertRecentSearch("2", "test2", FavoriteType.LOCATION))
|
||||||
|
.add(insertRecentSearch("3", "test3", FavoriteType.USER))
|
||||||
|
.add(insertRecentSearch("4", "test4", FavoriteType.USER))
|
||||||
|
.add(insertRecentSearch("5", "test5", FavoriteType.USER))
|
||||||
|
.build()
|
||||||
|
.reverse(); // important
|
||||||
|
final List<RecentSearch> fromDb = dao.getAllRecentSearches();
|
||||||
|
Assertions.assertIterableEquals(insertListReversed, fromDb);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private RecentSearch insertRecentSearch(final String igId, final String name, final FavoriteType type) {
|
||||||
|
final RecentSearch recentSearch = new RecentSearch(
|
||||||
|
igId,
|
||||||
|
name,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
type,
|
||||||
|
LocalDateTime.now()
|
||||||
|
);
|
||||||
|
dao.insertRecentSearch(recentSearch);
|
||||||
|
return recentSearch;
|
||||||
|
}
|
||||||
|
}
|
6
app/src/github/res/values-ca/strings.xml
Normal file
6
app/src/github/res/values-ca/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Habilita el Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry és un oient/intèrpret d\'error que envia asíncronament l\'error/esdeveniment a Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry s\'iniciarà al pròxim llançament</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-cs/strings.xml
Normal file
6
app/src/github/res/values-cs/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Povolit Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry je listener/handler, který zaznamenává chyby a asynchronně je posílá na Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry se spustí při příštím spuštění</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-de/strings.xml
Normal file
6
app/src/github/res/values-de/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-el/strings.xml
Normal file
6
app/src/github/res/values-el/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Ενεργοποίηση Sentry</string>
|
||||||
|
<string name="sentry_summary">Το Sentry είναι διαχειριστής σφαλμάτων ασύγχρονης αποστολής του σφάλματος/συμβάντος στο Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Το Sentry θα ξεκινήσει στην επόμενη εκκίνηση</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-es/strings.xml
Normal file
6
app/src/github/res/values-es/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Activar Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry es un oyente/manejador de errores que asincrónicamente envía el error/evento a Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry comenzará en el próximo inicio</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-eu/strings.xml
Normal file
6
app/src/github/res/values-eu/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-fa/strings.xml
Normal file
6
app/src/github/res/values-fa/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-fr/strings.xml
Normal file
6
app/src/github/res/values-fr/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Activer Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry est un écouteur/gestionnaire d\'erreurs qui envoie de manière asynchrone l\'erreur/l\'événement à Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry commencera au prochain lancement</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-hi/strings.xml
Normal file
6
app/src/github/res/values-hi/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-in/strings.xml
Normal file
6
app/src/github/res/values-in/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-it/strings.xml
Normal file
6
app/src/github/res/values-it/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Abilita Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry è un ascoltatore/gestore di errori che invia asincronicamente l\'errore/evento a Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry comincerà al prossimo lancio</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-ja/strings.xml
Normal file
6
app/src/github/res/values-ja/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-mk/strings.xml
Normal file
6
app/src/github/res/values-mk/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-nl/strings.xml
Normal file
6
app/src/github/res/values-nl/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-or/strings.xml
Normal file
6
app/src/github/res/values-or/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-pl/strings.xml
Normal file
6
app/src/github/res/values-pl/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Włącz Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry jest słuchaczem/obsługą błędów, które asynchronicznie wysyłają błąd/zdarzenie do Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry rozpocznie się przy następnym uruchomieniu</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-pt/strings.xml
Normal file
6
app/src/github/res/values-pt/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Ativar Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry é um ouvinte/gestor de erros que assincronicamente envia o erro/evento para Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry começará no próximo início</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-ru/strings.xml
Normal file
6
app/src/github/res/values-ru/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Включить режим \"часового\"</string>
|
||||||
|
<string name="sentry_summary">\"Часовой\" - это слушатель/обработчик ошибок, который асинхронно отправляет ошибку/событие на Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">\"Часовой\" будет запущен при следующем запуске</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-sk/strings.xml
Normal file
6
app/src/github/res/values-sk/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-sv/strings.xml
Normal file
6
app/src/github/res/values-sv/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-tr/strings.xml
Normal file
6
app/src/github/res/values-tr/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-vi/strings.xml
Normal file
6
app/src/github/res/values-vi/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">Enable Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry is a listener/handler for errors that asynchronously sends out the error/event to Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">Sentry will start on next launch</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-zh-rCN/strings.xml
Normal file
6
app/src/github/res/values-zh-rCN/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">启用 Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry 会将错误报告发送至 Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">启用 Sentry 将在下次启动应用时生效</string>
|
||||||
|
</resources>
|
6
app/src/github/res/values-zh-rTW/strings.xml
Normal file
6
app/src/github/res/values-zh-rTW/strings.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="enable_sentry">啟用 Sentry</string>
|
||||||
|
<string name="sentry_summary">Sentry 將會錯誤報告發送至 Sentry.io</string>
|
||||||
|
<string name="sentry_start_next_launch">下次啟用應用程式時將會開啟 Sentry</string>
|
||||||
|
</resources>
|
@ -54,6 +54,8 @@
|
|||||||
<data android:scheme="https" />
|
<data android:scheme="https" />
|
||||||
<data android:host="ig.me" />
|
<data android:host="ig.me" />
|
||||||
<data android:host="www.ig.me" />
|
<data android:host="www.ig.me" />
|
||||||
|
<data android:host="instagr.am" />
|
||||||
|
<data android:host="www.instagr.am" />
|
||||||
<data android:host="instagram.com" />
|
<data android:host="instagram.com" />
|
||||||
<data android:host="www.instagram.com" />
|
<data android:host="www.instagram.com" />
|
||||||
<data android:pathPrefix="/" />
|
<data android:pathPrefix="/" />
|
||||||
@ -130,6 +132,9 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".activities.MainActivity" />
|
android:value=".activities.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".utils.ProcessPhoenix"
|
||||||
|
android:theme="@style/Theme.AppCompat.Translucent" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
|
@ -26,6 +26,9 @@ import static awais.instagrabber.utils.Utils.clipboardManager;
|
|||||||
import static awais.instagrabber.utils.Utils.datetimeParser;
|
import static awais.instagrabber.utils.Utils.datetimeParser;
|
||||||
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 final class InstaGrabberApplication extends Application {
|
public final class InstaGrabberApplication extends Application {
|
||||||
private static final String TAG = "InstaGrabberApplication";
|
private static final String TAG = "InstaGrabberApplication";
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import awais.instagrabber.utils.Constants;
|
|||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
|
|
||||||
public final class Login extends BaseLanguageActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
|
public final class Login extends BaseLanguageActivity implements View.OnClickListener {
|
||||||
private final WebViewClient webViewClient = new WebViewClient() {
|
private final WebViewClient webViewClient = new WebViewClient() {
|
||||||
@Override
|
@Override
|
||||||
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) {
|
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) {
|
||||||
@ -53,7 +53,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final WebChromeClient webChromeClient = new WebChromeClient();
|
private final WebChromeClient webChromeClient = new WebChromeClient();
|
||||||
private String webViewUrl, defaultUserAgent;
|
private String webViewUrl;
|
||||||
private boolean ready = false;
|
private boolean ready = false;
|
||||||
private ActivityLoginBinding loginBinding;
|
private ActivityLoginBinding loginBinding;
|
||||||
|
|
||||||
@ -65,7 +65,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
|
|||||||
|
|
||||||
initWebView();
|
initWebView();
|
||||||
|
|
||||||
loginBinding.desktopMode.setOnCheckedChangeListener(this);
|
|
||||||
loginBinding.cookies.setOnClickListener(this);
|
loginBinding.cookies.setOnClickListener(this);
|
||||||
loginBinding.refresh.setOnClickListener(this);
|
loginBinding.refresh.setOnClickListener(this);
|
||||||
}
|
}
|
||||||
@ -86,23 +85,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
|
||||||
final WebSettings webSettings = loginBinding.webView.getSettings();
|
|
||||||
|
|
||||||
final String newUserAgent = isChecked
|
|
||||||
? "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
|
|
||||||
: defaultUserAgent;
|
|
||||||
|
|
||||||
webSettings.setUserAgentString(newUserAgent);
|
|
||||||
webSettings.setUseWideViewPort(isChecked);
|
|
||||||
webSettings.setLoadWithOverviewMode(isChecked);
|
|
||||||
webSettings.setSupportZoom(isChecked);
|
|
||||||
webSettings.setBuiltInZoomControls(isChecked);
|
|
||||||
|
|
||||||
loginBinding.webView.loadUrl("https://instagram.com/");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
private void initWebView() {
|
private void initWebView() {
|
||||||
if (loginBinding != null) {
|
if (loginBinding != null) {
|
||||||
@ -110,7 +92,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
|
|||||||
loginBinding.webView.setWebViewClient(webViewClient);
|
loginBinding.webView.setWebViewClient(webViewClient);
|
||||||
final WebSettings webSettings = loginBinding.webView.getSettings();
|
final WebSettings webSettings = loginBinding.webView.getSettings();
|
||||||
if (webSettings != null) {
|
if (webSettings != null) {
|
||||||
if (defaultUserAgent == null) defaultUserAgent = webSettings.getUserAgentString();
|
webSettings.setUserAgentString("Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36");
|
||||||
webSettings.setJavaScriptEnabled(true);
|
webSettings.setJavaScriptEnabled(true);
|
||||||
webSettings.setDomStorageEnabled(true);
|
webSettings.setDomStorageEnabled(true);
|
||||||
webSettings.setSupportZoom(true);
|
webSettings.setSupportZoom(true);
|
||||||
|
@ -8,19 +8,18 @@ import android.content.ComponentName;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.database.MatrixCursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.provider.BaseColumns;
|
import android.text.Editable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
@ -28,14 +27,12 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
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.widget.SearchView;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
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.emoji.text.EmojiCompat;
|
import androidx.emoji.text.EmojiCompat;
|
||||||
import androidx.emoji.text.FontRequestEmojiCompatConfig;
|
import androidx.emoji.text.FontRequestEmojiCompatConfig;
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
@ -50,30 +47,27 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
|
|||||||
import com.google.android.material.badge.BadgeDrawable;
|
import com.google.android.material.badge.BadgeDrawable;
|
||||||
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
|
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.Iterators;
|
import com.google.common.collect.Iterators;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Deque;
|
import java.util.Deque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import awais.instagrabber.BuildConfig;
|
import awais.instagrabber.BuildConfig;
|
||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.SuggestionsAdapter;
|
|
||||||
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.TextWatcherAdapter;
|
||||||
import awais.instagrabber.databinding.ActivityMainBinding;
|
import awais.instagrabber.databinding.ActivityMainBinding;
|
||||||
import awais.instagrabber.fragments.PostViewV2Fragment;
|
import awais.instagrabber.fragments.PostViewV2Fragment;
|
||||||
import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
|
import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections;
|
||||||
import awais.instagrabber.fragments.main.FeedFragment;
|
|
||||||
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
import awais.instagrabber.fragments.settings.PreferenceKeys;
|
||||||
import awais.instagrabber.models.IntentModel;
|
import awais.instagrabber.models.IntentModel;
|
||||||
import awais.instagrabber.models.Tab;
|
import awais.instagrabber.models.Tab;
|
||||||
import awais.instagrabber.models.enums.SuggestionType;
|
|
||||||
import awais.instagrabber.repositories.responses.search.SearchItem;
|
|
||||||
import awais.instagrabber.repositories.responses.search.SearchResponse;
|
|
||||||
import awais.instagrabber.services.ActivityCheckerService;
|
import awais.instagrabber.services.ActivityCheckerService;
|
||||||
import awais.instagrabber.services.DMSyncAlarmReceiver;
|
import awais.instagrabber.services.DMSyncAlarmReceiver;
|
||||||
import awais.instagrabber.utils.AppExecutors;
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
@ -86,10 +80,7 @@ import awais.instagrabber.utils.Utils;
|
|||||||
import awais.instagrabber.utils.emoji.EmojiParser;
|
import awais.instagrabber.utils.emoji.EmojiParser;
|
||||||
import awais.instagrabber.viewmodels.AppStateViewModel;
|
import awais.instagrabber.viewmodels.AppStateViewModel;
|
||||||
import awais.instagrabber.viewmodels.DirectInboxViewModel;
|
import awais.instagrabber.viewmodels.DirectInboxViewModel;
|
||||||
import awais.instagrabber.webservices.SearchService;
|
import awais.instagrabber.webservices.RetrofitFactory;
|
||||||
import retrofit2.Call;
|
|
||||||
import retrofit2.Callback;
|
|
||||||
import retrofit2.Response;
|
|
||||||
|
|
||||||
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
|
import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController;
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
@ -98,16 +89,21 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
private static final String TAG = "MainActivity";
|
private static final String TAG = "MainActivity";
|
||||||
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
|
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
|
||||||
private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId";
|
private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId";
|
||||||
|
private static final List<Integer> SEARCH_VISIBLE_DESTINATIONS = ImmutableList.of(
|
||||||
|
R.id.feedFragment,
|
||||||
|
R.id.profileFragment,
|
||||||
|
R.id.directMessagesInboxFragment,
|
||||||
|
R.id.discoverFragment,
|
||||||
|
R.id.favoritesFragment,
|
||||||
|
R.id.hashTagFragment,
|
||||||
|
R.id.locationFragment
|
||||||
|
);
|
||||||
|
|
||||||
|
private static MainActivity instance;
|
||||||
|
|
||||||
private ActivityMainBinding binding;
|
private ActivityMainBinding binding;
|
||||||
private LiveData<NavController> currentNavControllerLiveData;
|
private LiveData<NavController> currentNavControllerLiveData;
|
||||||
private MenuItem searchMenuItem;
|
private MenuItem searchMenuItem;
|
||||||
private SuggestionsAdapter suggestionAdapter;
|
|
||||||
private AutoCompleteTextView searchAutoComplete;
|
|
||||||
private SearchView searchView;
|
|
||||||
private SearchService searchService;
|
|
||||||
private boolean showSearch = true;
|
|
||||||
private Handler suggestionsFetchHandler;
|
|
||||||
private int firstFragmentGraphIndex;
|
private int firstFragmentGraphIndex;
|
||||||
private int lastSelectedNavMenuId;
|
private int lastSelectedNavMenuId;
|
||||||
private boolean isActivityCheckerServiceBound = false;
|
private boolean isActivityCheckerServiceBound = false;
|
||||||
@ -131,13 +127,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static MainActivity getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
protected void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
instance = this;
|
||||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
setupCookie();
|
||||||
CookieUtils.setupCookies(cookie);
|
|
||||||
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != 0;
|
|
||||||
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());
|
||||||
@ -154,7 +153,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
setupBottomNavigationBar(true);
|
setupBottomNavigationBar(true);
|
||||||
}
|
}
|
||||||
setupSuggestions();
|
|
||||||
if (!BuildConfig.isPre) {
|
if (!BuildConfig.isPre) {
|
||||||
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
|
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
|
||||||
if (checkUpdates) FlavorTown.updateCheck(this);
|
if (checkUpdates) FlavorTown.updateCheck(this);
|
||||||
@ -163,7 +161,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
|
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
|
if (isLoggedIn && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) {
|
||||||
bindActivityCheckerService();
|
bindActivityCheckerService();
|
||||||
}
|
}
|
||||||
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
getSupportFragmentManager().addOnBackStackChangedListener(this);
|
||||||
@ -173,9 +171,29 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
EmojiVariantManager.getInstance();
|
EmojiVariantManager.getInstance();
|
||||||
});
|
});
|
||||||
initEmojiCompat();
|
initEmojiCompat();
|
||||||
searchService = SearchService.getInstance();
|
|
||||||
// initDmService();
|
// initDmService();
|
||||||
initDmUnreadCount();
|
initDmUnreadCount();
|
||||||
|
initSearchInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCookie() {
|
||||||
|
final String cookie = settingsHelper.getString(Constants.COOKIE);
|
||||||
|
long userId = 0;
|
||||||
|
String csrfToken = null;
|
||||||
|
if (!TextUtils.isEmpty(cookie)) {
|
||||||
|
userId = CookieUtils.getUserIdFromCookie(cookie);
|
||||||
|
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
||||||
|
}
|
||||||
|
if (TextUtils.isEmpty(cookie) || userId == 0 || TextUtils.isEmpty(csrfToken)) {
|
||||||
|
isLoggedIn = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String deviceUuid = settingsHelper.getString(Constants.DEVICE_UUID);
|
||||||
|
if (TextUtils.isEmpty(deviceUuid)) {
|
||||||
|
settingsHelper.putString(Constants.DEVICE_UUID, UUID.randomUUID().toString());
|
||||||
|
}
|
||||||
|
CookieUtils.setupCookies(cookie);
|
||||||
|
isLoggedIn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initDmService() {
|
private void initDmService() {
|
||||||
@ -195,25 +213,74 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initSearchInput() {
|
||||||
|
binding.searchInputLayout.setEndIconOnClickListener(v -> {
|
||||||
|
final EditText editText = binding.searchInputLayout.getEditText();
|
||||||
|
if (editText == null) return;
|
||||||
|
editText.setText("");
|
||||||
|
});
|
||||||
|
binding.searchInputLayout.addOnEditTextAttachedListener(textInputLayout -> {
|
||||||
|
textInputLayout.setEndIconVisible(false);
|
||||||
|
final EditText editText = textInputLayout.getEditText();
|
||||||
|
if (editText == null) return;
|
||||||
|
editText.addTextChangedListener(new TextWatcherAdapter() {
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
binding.searchInputLayout.setEndIconVisible(!TextUtils.isEmpty(s));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.main_menu, menu);
|
getMenuInflater().inflate(R.menu.main_menu, menu);
|
||||||
searchMenuItem = menu.findItem(R.id.search);
|
searchMenuItem = menu.findItem(R.id.search);
|
||||||
if (showSearch && currentNavControllerLiveData != null) {
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
final NavController navController = currentNavControllerLiveData.getValue();
|
||||||
if (navController != null) {
|
if (navController != null) {
|
||||||
final NavDestination currentDestination = navController.getCurrentDestination();
|
final NavDestination currentDestination = navController.getCurrentDestination();
|
||||||
if (currentDestination != null) {
|
if (currentDestination != null) {
|
||||||
final int destinationId = currentDestination.getId();
|
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
|
||||||
showSearch = destinationId == R.id.profileFragment;
|
setupMenu(backStack.size(), currentDestination.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// if (binding.searchInputLayout.getVisibility() == View.VISIBLE) {
|
||||||
if (!showSearch) {
|
// searchMenuItem.setVisible(false).setEnabled(false);
|
||||||
searchMenuItem.setVisible(false);
|
// return true;
|
||||||
|
// }
|
||||||
|
// searchMenuItem.setVisible(true).setEnabled(true);
|
||||||
|
// if (showSearch && currentNavControllerLiveData != null) {
|
||||||
|
// final NavController navController = currentNavControllerLiveData.getValue();
|
||||||
|
// if (navController != null) {
|
||||||
|
// final NavDestination currentDestination = navController.getCurrentDestination();
|
||||||
|
// if (currentDestination != null) {
|
||||||
|
// final int destinationId = currentDestination.getId();
|
||||||
|
// showSearch = destinationId == R.id.profileFragment;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (!showSearch) {
|
||||||
|
// searchMenuItem.setVisible(false);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return setupSearchView();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return setupSearchView();
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
|
||||||
|
if (item.getItemId() == R.id.search) {
|
||||||
|
final NavController navController = currentNavControllerLiveData.getValue();
|
||||||
|
if (navController == null) return false;
|
||||||
|
try {
|
||||||
|
navController.navigate(R.id.action_global_search);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onOptionsItemSelected: ", e);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -263,6 +330,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
Log.e(TAG, "onDestroy: ", e);
|
Log.e(TAG, "onDestroy: ", e);
|
||||||
}
|
}
|
||||||
unbindActivityCheckerService();
|
unbindActivityCheckerService();
|
||||||
|
try {
|
||||||
|
RetrofitFactory.getInstance().destroy();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "onDestroy: ", e);
|
||||||
|
}
|
||||||
|
instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -314,176 +387,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
notificationManager.createNotificationChannel(silentNotificationChannel);
|
notificationManager.createNotificationChannel(silentNotificationChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSuggestions() {
|
|
||||||
suggestionsFetchHandler = new Handler();
|
|
||||||
suggestionAdapter = new SuggestionsAdapter(this, (type, query) -> {
|
|
||||||
if (searchMenuItem != null) searchMenuItem.collapseActionView();
|
|
||||||
if (searchView != null && !searchView.isIconified()) searchView.setIconified(true);
|
|
||||||
if (currentNavControllerLiveData == null) return;
|
|
||||||
final NavController navController = currentNavControllerLiveData.getValue();
|
|
||||||
if (navController == null) return;
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
switch (type) {
|
|
||||||
case TYPE_LOCATION:
|
|
||||||
bundle.putLong("locationId", Long.parseLong(query));
|
|
||||||
navController.navigate(R.id.action_global_locationFragment, bundle);
|
|
||||||
break;
|
|
||||||
case TYPE_HASHTAG:
|
|
||||||
bundle.putString("hashtag", query);
|
|
||||||
navController.navigate(R.id.action_global_hashTagFragment, bundle);
|
|
||||||
break;
|
|
||||||
case TYPE_USER:
|
|
||||||
bundle.putString("username", query);
|
|
||||||
navController.navigate(R.id.action_global_profileFragment, bundle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean setupSearchView() {
|
|
||||||
final View actionView = searchMenuItem.getActionView();
|
|
||||||
if (!(actionView instanceof SearchView)) return false;
|
|
||||||
searchView = (SearchView) actionView;
|
|
||||||
searchView.setSuggestionsAdapter(suggestionAdapter);
|
|
||||||
searchView.setMaxWidth(Integer.MAX_VALUE);
|
|
||||||
final View searchText = searchView.findViewById(R.id.search_src_text);
|
|
||||||
if (searchText instanceof AutoCompleteTextView) {
|
|
||||||
searchAutoComplete = (AutoCompleteTextView) searchText;
|
|
||||||
}
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
||||||
private boolean searchUser;
|
|
||||||
private boolean searchHash;
|
|
||||||
private Call<SearchResponse> prevSuggestionAsync;
|
|
||||||
private final String[] COLUMNS = {
|
|
||||||
BaseColumns._ID,
|
|
||||||
Constants.EXTRAS_USERNAME,
|
|
||||||
Constants.EXTRAS_NAME,
|
|
||||||
Constants.EXTRAS_TYPE,
|
|
||||||
"query",
|
|
||||||
"pfp",
|
|
||||||
"verified"
|
|
||||||
};
|
|
||||||
private String currentSearchQuery;
|
|
||||||
|
|
||||||
private final Callback<SearchResponse> cb = new Callback<SearchResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(@NonNull final Call<SearchResponse> call,
|
|
||||||
@NonNull final Response<SearchResponse> response) {
|
|
||||||
final MatrixCursor cursor;
|
|
||||||
final SearchResponse body = response.body();
|
|
||||||
if (body == null) {
|
|
||||||
cursor = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final List<SearchItem> result = new ArrayList<>();
|
|
||||||
if (isLoggedIn) {
|
|
||||||
if (body.getList() != null) {
|
|
||||||
result.addAll(searchHash ? body.getList()
|
|
||||||
.stream()
|
|
||||||
.filter(i -> i.getUser() == null)
|
|
||||||
.collect(Collectors.toList())
|
|
||||||
: body.getList());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers());
|
|
||||||
if (body.getHashtags() != null) result.addAll(body.getHashtags());
|
|
||||||
if (body.getPlaces() != null) result.addAll(body.getPlaces());
|
|
||||||
}
|
|
||||||
cursor = new MatrixCursor(COLUMNS, 0);
|
|
||||||
for (int i = 0; i < result.size(); i++) {
|
|
||||||
final SearchItem suggestionModel = result.get(i);
|
|
||||||
if (suggestionModel != null) {
|
|
||||||
Object[] objects = null;
|
|
||||||
if (suggestionModel.getUser() != null)
|
|
||||||
objects = new Object[]{
|
|
||||||
suggestionModel.getPosition(),
|
|
||||||
suggestionModel.getUser().getUsername(),
|
|
||||||
suggestionModel.getUser().getFullName(),
|
|
||||||
SuggestionType.TYPE_USER,
|
|
||||||
suggestionModel.getUser().getUsername(),
|
|
||||||
suggestionModel.getUser().getProfilePicUrl(),
|
|
||||||
suggestionModel.getUser().isVerified()};
|
|
||||||
else if (suggestionModel.getHashtag() != null)
|
|
||||||
objects = new Object[]{
|
|
||||||
suggestionModel.getPosition(),
|
|
||||||
suggestionModel.getHashtag().getName(),
|
|
||||||
suggestionModel.getHashtag().getSubtitle(),
|
|
||||||
SuggestionType.TYPE_HASHTAG,
|
|
||||||
suggestionModel.getHashtag().getName(),
|
|
||||||
"res:/" + R.drawable.ic_hashtag,
|
|
||||||
false};
|
|
||||||
else if (suggestionModel.getPlace() != null)
|
|
||||||
objects = new Object[]{
|
|
||||||
suggestionModel.getPosition(),
|
|
||||||
suggestionModel.getPlace().getTitle(),
|
|
||||||
suggestionModel.getPlace().getSubtitle(),
|
|
||||||
SuggestionType.TYPE_LOCATION,
|
|
||||||
suggestionModel.getPlace().getLocation().getPk(),
|
|
||||||
"res:/" + R.drawable.ic_location,
|
|
||||||
false};
|
|
||||||
cursor.addRow(objects);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
suggestionAdapter.changeCursor(cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@NonNull final Call<SearchResponse> call,
|
|
||||||
@NonNull Throwable t) {
|
|
||||||
if (!call.isCanceled()) {
|
|
||||||
Log.e(TAG, "Exception on search:", t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Runnable runnable = () -> {
|
|
||||||
cancelSuggestionsAsync();
|
|
||||||
if (TextUtils.isEmpty(currentSearchQuery)) {
|
|
||||||
suggestionAdapter.changeCursor(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
searchUser = currentSearchQuery.charAt(0) == '@';
|
|
||||||
searchHash = currentSearchQuery.charAt(0) == '#';
|
|
||||||
if (currentSearchQuery.length() == 1 && (searchHash || searchUser)) {
|
|
||||||
if (searchAutoComplete != null) {
|
|
||||||
searchAutoComplete.setThreshold(2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (searchAutoComplete != null) {
|
|
||||||
searchAutoComplete.setThreshold(1);
|
|
||||||
}
|
|
||||||
prevSuggestionAsync = searchService.search(isLoggedIn,
|
|
||||||
searchUser || searchHash ? currentSearchQuery.substring(1)
|
|
||||||
: currentSearchQuery,
|
|
||||||
searchUser ? "user" : (searchHash ? "hashtag" : "blended"));
|
|
||||||
suggestionAdapter.changeCursor(null);
|
|
||||||
prevSuggestionAsync.enqueue(cb);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void cancelSuggestionsAsync() {
|
|
||||||
if (prevSuggestionAsync != null)
|
|
||||||
try {
|
|
||||||
prevSuggestionAsync.cancel();
|
|
||||||
} catch (final Exception ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(final String query) {
|
|
||||||
return onQueryTextChange(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(final String query) {
|
|
||||||
suggestionsFetchHandler.removeCallbacks(runnable);
|
|
||||||
currentSearchQuery = query;
|
|
||||||
suggestionsFetchHandler.postDelayed(runnable, 800);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) {
|
private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) {
|
||||||
currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav();
|
currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav();
|
||||||
final List<Integer> mainNavList = currentTabs.stream()
|
final List<Integer> mainNavList = currentTabs.stream()
|
||||||
@ -506,16 +409,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
firstFragmentGraphIndex);
|
firstFragmentGraphIndex);
|
||||||
navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
|
navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
|
||||||
currentNavControllerLiveData = navControllerLiveData;
|
currentNavControllerLiveData = navControllerLiveData;
|
||||||
binding.bottomNavView.setOnNavigationItemReselectedListener(item -> {
|
|
||||||
// Log.d(TAG, "setupBottomNavigationBar: item: " + item);
|
|
||||||
final Fragment navHostFragment = getSupportFragmentManager().findFragmentById(R.id.main_nav_host);
|
|
||||||
if (navHostFragment != null) {
|
|
||||||
final Fragment fragment = navHostFragment.getChildFragmentManager().getPrimaryNavigationFragment();
|
|
||||||
if (fragment instanceof FeedFragment) {
|
|
||||||
((FeedFragment) fragment).scrollToTop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSelectedTab(final List<Tab> tabs) {
|
private void setSelectedTab(final List<Tab> tabs) {
|
||||||
@ -542,6 +435,13 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
|
|
||||||
private List<Tab> setupAnonBottomNav() {
|
private List<Tab> setupAnonBottomNav() {
|
||||||
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
|
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
|
||||||
|
final Tab favoriteTab = new Tab(R.drawable.ic_star_24,
|
||||||
|
getString(R.string.title_favorites),
|
||||||
|
false,
|
||||||
|
"favorites_nav_graph",
|
||||||
|
R.navigation.favorites_nav_graph,
|
||||||
|
R.id.favorites_nav_graph,
|
||||||
|
R.id.favoritesFragment);
|
||||||
final Tab profileTab = new Tab(R.drawable.ic_person_24,
|
final Tab profileTab = new Tab(R.drawable.ic_person_24,
|
||||||
getString(R.string.profile),
|
getString(R.string.profile),
|
||||||
false,
|
false,
|
||||||
@ -558,12 +458,15 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
R.id.morePreferencesFragment);
|
R.id.morePreferencesFragment);
|
||||||
final Menu menu = binding.bottomNavView.getMenu();
|
final Menu menu = binding.bottomNavView.getMenu();
|
||||||
menu.clear();
|
menu.clear();
|
||||||
|
menu.add(0, favoriteTab.getNavigationRootId(), 0, favoriteTab.getTitle()).setIcon(favoriteTab.getIconResId());
|
||||||
menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId());
|
menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId());
|
||||||
menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId());
|
menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId());
|
||||||
if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) {
|
if (selectedItemId != R.id.profile_nav_graph
|
||||||
|
&& selectedItemId != R.id.more_nav_graph
|
||||||
|
&& selectedItemId != R.id.favorites_nav_graph) {
|
||||||
setBottomNavSelectedTab(profileTab);
|
setBottomNavSelectedTab(profileTab);
|
||||||
}
|
}
|
||||||
return ImmutableList.of(profileTab, moreTab);
|
return ImmutableList.of(favoriteTab, profileTab, moreTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Tab> setupMainBottomNav() {
|
private List<Tab> setupMainBottomNav() {
|
||||||
@ -584,20 +487,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
binding.bottomNavView.setSelectedItemId(navGraphRootId);
|
binding.bottomNavView.setSelectedItemId(navGraphRootId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @NonNull
|
|
||||||
// private List<Integer> getMainNavList(final int main_nav_ids) {
|
|
||||||
// final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
|
|
||||||
// final List<Integer> mainNavList = new ArrayList<>(navIds.length());
|
|
||||||
// final int length = navIds.length();
|
|
||||||
// for (int i = 0; i < length; i++) {
|
|
||||||
// final int resourceId = navIds.getResourceId(i, -1);
|
|
||||||
// if (resourceId < 0) continue;
|
|
||||||
// mainNavList.add(resourceId);
|
|
||||||
// }
|
|
||||||
// navIds.recycle();
|
|
||||||
// return mainNavList;
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
|
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
|
||||||
if (navController == null) return;
|
if (navController == null) return;
|
||||||
NavigationUI.setupWithNavController(toolbar, navController);
|
NavigationUI.setupWithNavController(toolbar, navController);
|
||||||
@ -616,11 +505,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);
|
||||||
@ -629,12 +519,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
|
|
||||||
private void setupMenu(final int backStackSize, final int destinationId) {
|
private void setupMenu(final int backStackSize, final int destinationId) {
|
||||||
if (searchMenuItem == null) return;
|
if (searchMenuItem == null) return;
|
||||||
if (backStackSize >= 2 && destinationId == R.id.profileFragment) {
|
if (backStackSize >= 2 && SEARCH_VISIBLE_DESTINATIONS.contains(destinationId)) {
|
||||||
showSearch = true;
|
|
||||||
searchMenuItem.setVisible(true);
|
searchMenuItem.setVisible(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showSearch = false;
|
|
||||||
searchMenuItem.setVisible(false);
|
searchMenuItem.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -765,7 +653,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) {
|
||||||
@ -778,11 +670,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();
|
||||||
@ -838,11 +735,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) {
|
||||||
@ -905,14 +810,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
return binding.toolbar;
|
return binding.toolbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public View getRootView() {
|
||||||
|
return binding.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
public List<Tab> getCurrentTabs() {
|
public List<Tab> getCurrentTabs() {
|
||||||
return currentTabs;
|
return currentTabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) {
|
|
||||||
// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId);
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void setNavBarDMUnreadCountBadge(final int unseenCount) {
|
private void setNavBarDMUnreadCountBadge(final int unseenCount) {
|
||||||
final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
|
final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
|
||||||
if (badge == null) return;
|
if (badge == null) return;
|
||||||
@ -927,4 +832,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
|
|||||||
badge.setNumber(unseenCount);
|
badge.setNumber(unseenCount);
|
||||||
badge.setVisible(true);
|
badge.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public TextInputLayout showSearchView() {
|
||||||
|
binding.searchInputLayout.setVisibility(View.VISIBLE);
|
||||||
|
return binding.searchInputLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void hideSearchView() {
|
||||||
|
binding.searchInputLayout.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,195 +1,60 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Objects;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.comments.ChildCommentViewHolder;
|
import awais.instagrabber.adapters.viewholder.CommentViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.comments.ParentCommentViewHolder;
|
|
||||||
import awais.instagrabber.databinding.ItemCommentBinding;
|
import awais.instagrabber.databinding.ItemCommentBinding;
|
||||||
import awais.instagrabber.databinding.ItemCommentSmallBinding;
|
import awais.instagrabber.models.Comment;
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
|
|
||||||
public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerView.ViewHolder> {
|
public final class CommentsAdapter extends ListAdapter<Comment, CommentViewHolder> {
|
||||||
private static final int TYPE_PARENT = 1;
|
private static final DiffUtil.ItemCallback<Comment> DIFF_CALLBACK = new DiffUtil.ItemCallback<Comment>() {
|
||||||
private static final int TYPE_CHILD = 2;
|
|
||||||
|
|
||||||
private final Map<Integer, Integer> positionTypeMap = new HashMap<>();
|
|
||||||
|
|
||||||
// private final Filter filter = new Filter() {
|
|
||||||
// @NonNull
|
|
||||||
// @Override
|
|
||||||
// protected FilterResults performFiltering(final CharSequence filter) {
|
|
||||||
// final FilterResults results = new FilterResults();
|
|
||||||
// results.values = commentModels;
|
|
||||||
//
|
|
||||||
// final int commentsLen = commentModels == null ? 0 : commentModels.size();
|
|
||||||
// if (commentModels != null && commentsLen > 0 && !TextUtils.isEmpty(filter)) {
|
|
||||||
// final String query = filter.toString().toLowerCase();
|
|
||||||
// final ArrayList<CommentModel> filterList = new ArrayList<>(commentsLen);
|
|
||||||
//
|
|
||||||
// for (final CommentModel commentModel : commentModels) {
|
|
||||||
// final String commentText = commentModel.getText().toString().toLowerCase();
|
|
||||||
//
|
|
||||||
// if (commentText.contains(query)) filterList.add(commentModel);
|
|
||||||
// else {
|
|
||||||
// final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
|
|
||||||
// if (childCommentModels != null) {
|
|
||||||
// for (final CommentModel childCommentModel : childCommentModels) {
|
|
||||||
// final String childCommentText = childCommentModel.getText().toString().toLowerCase();
|
|
||||||
// if (childCommentText.contains(query)) filterList.add(commentModel);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// filterList.trimToSize();
|
|
||||||
// results.values = filterList.toArray(new CommentModel[0]);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return results;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) {
|
|
||||||
// if (results.values instanceof List) {
|
|
||||||
// //noinspection unchecked
|
|
||||||
// filteredCommentModels = (List<CommentModel>) results.values;
|
|
||||||
// notifyDataSetChanged();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
private static final DiffUtil.ItemCallback<CommentModel> DIFF_CALLBACK = new DiffUtil.ItemCallback<CommentModel>() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
|
public boolean areItemsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
|
||||||
return oldItem.getId().equals(newItem.getId());
|
return Objects.equals(oldItem.getId(), newItem.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
|
public boolean areContentsTheSame(@NonNull final Comment oldItem, @NonNull final Comment newItem) {
|
||||||
return oldItem.getId().equals(newItem.getId());
|
return Objects.equals(oldItem, newItem);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private final CommentCallback commentCallback;
|
|
||||||
private CommentModel selected, toChangeLike;
|
|
||||||
private int selectedIndex, likedIndex;
|
|
||||||
|
|
||||||
public CommentsAdapter(final CommentCallback commentCallback) {
|
private final boolean showingReplies;
|
||||||
|
private final CommentCallback commentCallback;
|
||||||
|
private final long currentUserId;
|
||||||
|
|
||||||
|
public CommentsAdapter(final long currentUserId,
|
||||||
|
final boolean showingReplies,
|
||||||
|
final CommentCallback commentCallback) {
|
||||||
super(DIFF_CALLBACK);
|
super(DIFF_CALLBACK);
|
||||||
|
this.showingReplies = showingReplies;
|
||||||
|
this.currentUserId = currentUserId;
|
||||||
this.commentCallback = commentCallback;
|
this.commentCallback = commentCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
public CommentViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
|
||||||
final Context context = parent.getContext();
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(context);
|
|
||||||
if (type == TYPE_PARENT) {
|
|
||||||
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
|
final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
|
||||||
return new ParentCommentViewHolder(binding);
|
return new CommentViewHolder(binding, currentUserId, commentCallback);
|
||||||
}
|
|
||||||
final ItemCommentSmallBinding binding = ItemCommentSmallBinding.inflate(layoutInflater, parent, false);
|
|
||||||
return new ChildCommentViewHolder(binding);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) {
|
||||||
CommentModel commentModel = getItem(position);
|
final Comment comment = getItem(position);
|
||||||
if (commentModel == null) return;
|
holder.bind(comment, showingReplies && position == 0, showingReplies && position != 0);
|
||||||
final int type = getItemViewType(position);
|
|
||||||
final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId());
|
|
||||||
final boolean toLike = this.toChangeLike != null && this.toChangeLike.getId().equals(commentModel.getId());
|
|
||||||
if (toLike) commentModel = this.toChangeLike;
|
|
||||||
if (type == TYPE_PARENT) {
|
|
||||||
final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder;
|
|
||||||
viewHolder.bind(commentModel, selected, commentCallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final ChildCommentViewHolder viewHolder = (ChildCommentViewHolder) holder;
|
|
||||||
viewHolder.bind(commentModel, selected, commentCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void submitList(@Nullable final List<CommentModel> list) {
|
|
||||||
final List<CommentModel> flatList = flattenList(list);
|
|
||||||
super.submitList(flatList);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void submitList(@Nullable final List<CommentModel> list, @Nullable final Runnable commitCallback) {
|
|
||||||
final List<CommentModel> flatList = flattenList(list);
|
|
||||||
super.submitList(flatList, commitCallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<CommentModel> flattenList(final List<CommentModel> list) {
|
|
||||||
if (list == null) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
final List<CommentModel> flatList = new ArrayList<>();
|
|
||||||
int lastCommentIndex = -1;
|
|
||||||
for (final CommentModel parent : list) {
|
|
||||||
lastCommentIndex++;
|
|
||||||
flatList.add(parent);
|
|
||||||
positionTypeMap.put(lastCommentIndex, TYPE_PARENT);
|
|
||||||
final List<CommentModel> children = parent.getChildCommentModels();
|
|
||||||
if (children != null) {
|
|
||||||
for (final CommentModel child : children) {
|
|
||||||
lastCommentIndex++;
|
|
||||||
flatList.add(child);
|
|
||||||
positionTypeMap.put(lastCommentIndex, TYPE_CHILD);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return flatList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemViewType(final int position) {
|
|
||||||
final Integer type = positionTypeMap.get(position);
|
|
||||||
if (type == null) {
|
|
||||||
return TYPE_PARENT;
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelected(final CommentModel commentModel) {
|
|
||||||
this.selected = commentModel;
|
|
||||||
selectedIndex = getCurrentList().indexOf(commentModel);
|
|
||||||
notifyItemChanged(selectedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clearSelection() {
|
|
||||||
this.selected = null;
|
|
||||||
notifyItemChanged(selectedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLiked(final CommentModel commentModel, final boolean liked) {
|
|
||||||
likedIndex = getCurrentList().indexOf(commentModel);
|
|
||||||
CommentModel newCommentModel = commentModel;
|
|
||||||
newCommentModel.setLiked(liked);
|
|
||||||
this.toChangeLike = newCommentModel;
|
|
||||||
notifyItemChanged(likedIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommentModel getSelected() {
|
|
||||||
return selected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface CommentCallback {
|
public interface CommentCallback {
|
||||||
void onClick(final CommentModel comment);
|
void onClick(final Comment comment);
|
||||||
|
|
||||||
void onHashtagClick(final String hashtag);
|
void onHashtagClick(final String hashtag);
|
||||||
|
|
||||||
@ -198,5 +63,15 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
|
|||||||
void onURLClick(final String url);
|
void onURLClick(final String url);
|
||||||
|
|
||||||
void onEmailClick(final String emailAddress);
|
void onEmailClick(final String emailAddress);
|
||||||
|
|
||||||
|
void onLikeClick(final Comment comment, boolean liked, final boolean isReply);
|
||||||
|
|
||||||
|
void onRepliesClick(final Comment comment);
|
||||||
|
|
||||||
|
void onViewLikes(Comment comment);
|
||||||
|
|
||||||
|
void onTranslate(Comment comment);
|
||||||
|
|
||||||
|
void onDelete(Comment comment, boolean isReply);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
@ -19,7 +19,7 @@ import java.util.List;
|
|||||||
import awais.instagrabber.R;
|
import awais.instagrabber.R;
|
||||||
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
|
import awais.instagrabber.adapters.viewholder.FavoriteViewHolder;
|
||||||
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
||||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
import awais.instagrabber.db.entities.Favorite;
|
import awais.instagrabber.db.entities.Favorite;
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ public class FavoritesAdapter extends RecyclerView.Adapter<RecyclerView.ViewHold
|
|||||||
// header
|
// header
|
||||||
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false));
|
return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false));
|
||||||
}
|
}
|
||||||
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false);
|
final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(inflater, parent, false);
|
||||||
return new FavoriteViewHolder(binding);
|
return new FavoriteViewHolder(binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ public final class LikesAdapter extends RecyclerView.Adapter<FollowsViewHolder>
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
|
public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) {
|
||||||
final User model = profileModels.get(position);
|
final User model = profileModels.get(position);
|
||||||
holder.bind(model, null, onClickListener);
|
holder.bind(model, onClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.fragments.search.SearchCategoryFragment;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
public class SearchCategoryAdapter extends FragmentStateAdapter {
|
||||||
|
|
||||||
|
private final List<FavoriteType> categories;
|
||||||
|
|
||||||
|
public SearchCategoryAdapter(@NonNull final Fragment fragment,
|
||||||
|
@NonNull final List<FavoriteType> categories) {
|
||||||
|
super(fragment);
|
||||||
|
this.categories = categories;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Fragment createFragment(final int position) {
|
||||||
|
return SearchCategoryFragment.newInstance(categories.get(position));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return categories.size();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,215 @@
|
|||||||
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.AdapterListUpdateCallback;
|
||||||
|
import androidx.recyclerview.widget.AsyncDifferConfig;
|
||||||
|
import androidx.recyclerview.widget.AsyncListDiffer;
|
||||||
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.viewholder.SearchItemViewHolder;
|
||||||
|
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
|
||||||
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
|
import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
import awais.instagrabber.repositories.responses.search.SearchItem;
|
||||||
|
|
||||||
|
public final class SearchItemsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||||
|
private static final String TAG = SearchItemsAdapter.class.getSimpleName();
|
||||||
|
private static final DiffUtil.ItemCallback<SearchItemOrHeader> DIFF_CALLBACK = new DiffUtil.ItemCallback<SearchItemOrHeader>() {
|
||||||
|
@Override
|
||||||
|
public boolean areItemsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) {
|
||||||
|
return Objects.equals(oldItem, newItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean areContentsTheSame(@NonNull final SearchItemOrHeader oldItem, @NonNull final SearchItemOrHeader newItem) {
|
||||||
|
return Objects.equals(oldItem, newItem);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private static final String RECENT = "recent";
|
||||||
|
private static final String FAVORITE = "favorite";
|
||||||
|
private static final int VIEW_TYPE_HEADER = 0;
|
||||||
|
private static final int VIEW_TYPE_ITEM = 1;
|
||||||
|
|
||||||
|
private final OnSearchItemClickListener onSearchItemClickListener;
|
||||||
|
private final AsyncListDiffer<SearchItemOrHeader> differ;
|
||||||
|
|
||||||
|
public SearchItemsAdapter(final OnSearchItemClickListener onSearchItemClickListener) {
|
||||||
|
differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
|
||||||
|
new AsyncDifferConfig.Builder<>(DIFF_CALLBACK).build());
|
||||||
|
this.onSearchItemClickListener = onSearchItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||||
|
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
|
||||||
|
if (viewType == VIEW_TYPE_HEADER) {
|
||||||
|
return new HeaderViewHolder(ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false));
|
||||||
|
}
|
||||||
|
final ItemSearchResultBinding binding = ItemSearchResultBinding.inflate(layoutInflater, parent, false);
|
||||||
|
return new SearchItemViewHolder(binding, onSearchItemClickListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
|
||||||
|
if (getItemViewType(position) == VIEW_TYPE_HEADER) {
|
||||||
|
final SearchItemOrHeader searchItemOrHeader = getItem(position);
|
||||||
|
if (!searchItemOrHeader.isHeader()) return;
|
||||||
|
((HeaderViewHolder) holder).bind(searchItemOrHeader.header);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
((SearchItemViewHolder) holder).bind(getItem(position).searchItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SearchItemOrHeader getItem(int position) {
|
||||||
|
return differ.getCurrentList().get(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return differ.getCurrentList().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(final int position) {
|
||||||
|
return getItem(position).isHeader() ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitList(@Nullable final List<SearchItem> list) {
|
||||||
|
if (list == null) {
|
||||||
|
differ.submitList(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
differ.submitList(sectionAndSort(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void submitList(@Nullable final List<SearchItem> list, @Nullable final Runnable commitCallback) {
|
||||||
|
if (list == null) {
|
||||||
|
differ.submitList(null, commitCallback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
differ.submitList(sectionAndSort(list), commitCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private List<SearchItemOrHeader> sectionAndSort(@NonNull final List<SearchItem> list) {
|
||||||
|
final boolean containsRecentOrFavorite = list.stream().anyMatch(searchItem -> searchItem.isRecent() || searchItem.isFavorite());
|
||||||
|
// Don't do anything if not showing recent results
|
||||||
|
if (!containsRecentOrFavorite) {
|
||||||
|
return list.stream()
|
||||||
|
.map(SearchItemOrHeader::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
final List<SearchItem> listCopy = new ArrayList<>(list);
|
||||||
|
Collections.sort(listCopy, (o1, o2) -> {
|
||||||
|
final boolean bothRecent = o1.isRecent() && o2.isRecent();
|
||||||
|
if (bothRecent) {
|
||||||
|
// Don't sort
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
final boolean bothFavorite = o1.isFavorite() && o2.isFavorite();
|
||||||
|
if (bothFavorite) {
|
||||||
|
if (o1.getType() == o2.getType()) return 0;
|
||||||
|
// keep users at top
|
||||||
|
if (o1.getType() == FavoriteType.USER) return -1;
|
||||||
|
if (o2.getType() == FavoriteType.USER) return 1;
|
||||||
|
// keep locations at bottom
|
||||||
|
if (o1.getType() == FavoriteType.LOCATION) return 1;
|
||||||
|
if (o2.getType() == FavoriteType.LOCATION) return -1;
|
||||||
|
}
|
||||||
|
// keep recents at top
|
||||||
|
if (o1.isRecent()) return -1;
|
||||||
|
if (o2.isRecent()) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
final List<SearchItemOrHeader> itemOrHeaders = new ArrayList<>();
|
||||||
|
for (int i = 0; i < listCopy.size(); i++) {
|
||||||
|
final SearchItem searchItem = listCopy.get(i);
|
||||||
|
final SearchItemOrHeader prev = itemOrHeaders.isEmpty() ? null : itemOrHeaders.get(itemOrHeaders.size() - 1);
|
||||||
|
boolean prevWasSameType = prev != null && ((prev.searchItem.isRecent() && searchItem.isRecent())
|
||||||
|
|| (prev.searchItem.isFavorite() && searchItem.isFavorite()));
|
||||||
|
if (prevWasSameType) {
|
||||||
|
// just add the item
|
||||||
|
itemOrHeaders.add(new SearchItemOrHeader(searchItem));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// add header and item
|
||||||
|
// add header only if search item is recent or favorite
|
||||||
|
if (searchItem.isRecent() || searchItem.isFavorite()) {
|
||||||
|
itemOrHeaders.add(new SearchItemOrHeader(searchItem.isRecent() ? RECENT : FAVORITE));
|
||||||
|
}
|
||||||
|
itemOrHeaders.add(new SearchItemOrHeader(searchItem));
|
||||||
|
}
|
||||||
|
return itemOrHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SearchItemOrHeader {
|
||||||
|
String header;
|
||||||
|
SearchItem searchItem;
|
||||||
|
|
||||||
|
public SearchItemOrHeader(final SearchItem searchItem) {
|
||||||
|
this.searchItem = searchItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SearchItemOrHeader(final String header) {
|
||||||
|
this.header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isHeader() {
|
||||||
|
return header != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final SearchItemOrHeader that = (SearchItemOrHeader) o;
|
||||||
|
return Objects.equals(header, that.header) &&
|
||||||
|
Objects.equals(searchItem, that.searchItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(header, searchItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
private final ItemFavSectionHeaderBinding binding;
|
||||||
|
|
||||||
|
public HeaderViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final String header) {
|
||||||
|
if (header == null) return;
|
||||||
|
final int headerText;
|
||||||
|
switch (header) {
|
||||||
|
case RECENT:
|
||||||
|
headerText = R.string.recent;
|
||||||
|
break;
|
||||||
|
case FAVORITE:
|
||||||
|
headerText = R.string.title_favorites;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
headerText = R.string.unknown;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
binding.getRoot().setText(headerText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,17 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
|
||||||
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onThumbnailLoaded(final int position) {}
|
public void onThumbnailLoaded(final int position) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onItemClicked(final int position) {}
|
public void onItemClicked(final int position, final Media media, final View view) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerPlay(final int position) {}
|
public void onPlayerPlay(final int position) {}
|
||||||
@ -15,4 +21,12 @@ public class SliderCallbackAdapter implements SliderItemsAdapter.SliderCallback
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerRelease(final int position) {}
|
public void onPlayerRelease(final int position) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInFullScreen() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,27 @@
|
|||||||
package awais.instagrabber.adapters;
|
package awais.instagrabber.adapters;
|
||||||
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.DiffUtil;
|
import androidx.recyclerview.widget.DiffUtil;
|
||||||
import androidx.recyclerview.widget.ListAdapter;
|
import androidx.recyclerview.widget.ListAdapter;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
|
import awais.instagrabber.adapters.viewholder.SliderItemViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
|
import awais.instagrabber.adapters.viewholder.SliderPhotoViewHolder;
|
||||||
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
|
import awais.instagrabber.adapters.viewholder.SliderVideoViewHolder;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
|
||||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||||
import awais.instagrabber.models.enums.MediaItemType;
|
import awais.instagrabber.models.enums.MediaItemType;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
|
||||||
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
|
public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewHolder> {
|
||||||
|
|
||||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
|
||||||
private final boolean loadVideoOnItemClick;
|
private final boolean loadVideoOnItemClick;
|
||||||
private final SliderCallback sliderCallback;
|
private final SliderCallback sliderCallback;
|
||||||
private final LayoutExoCustomControlsBinding controlsBinding;
|
|
||||||
|
|
||||||
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
|
private static final DiffUtil.ItemCallback<Media> DIFF_CALLBACK = new DiffUtil.ItemCallback<Media>() {
|
||||||
@Override
|
@Override
|
||||||
@ -36,15 +35,11 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public SliderItemsAdapter(final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
public SliderItemsAdapter(final boolean loadVideoOnItemClick,
|
||||||
final LayoutExoCustomControlsBinding controlsBinding,
|
|
||||||
final boolean loadVideoOnItemClick,
|
|
||||||
final SliderCallback sliderCallback) {
|
final SliderCallback sliderCallback) {
|
||||||
super(DIFF_CALLBACK);
|
super(DIFF_CALLBACK);
|
||||||
this.onVerticalDragListener = onVerticalDragListener;
|
|
||||||
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
||||||
this.sliderCallback = sliderCallback;
|
this.sliderCallback = sliderCallback;
|
||||||
this.controlsBinding = controlsBinding;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -55,12 +50,12 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
|||||||
switch (mediaItemType) {
|
switch (mediaItemType) {
|
||||||
case MEDIA_TYPE_VIDEO: {
|
case MEDIA_TYPE_VIDEO: {
|
||||||
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
|
final LayoutVideoPlayerWithThumbnailBinding binding = LayoutVideoPlayerWithThumbnailBinding.inflate(inflater, parent, false);
|
||||||
return new SliderVideoViewHolder(binding, onVerticalDragListener, controlsBinding, loadVideoOnItemClick);
|
return new SliderVideoViewHolder(binding, loadVideoOnItemClick);
|
||||||
}
|
}
|
||||||
case MEDIA_TYPE_IMAGE:
|
case MEDIA_TYPE_IMAGE:
|
||||||
default:
|
default:
|
||||||
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
|
final ItemSliderPhotoBinding binding = ItemSliderPhotoBinding.inflate(inflater, parent, false);
|
||||||
return new SliderPhotoViewHolder(binding, onVerticalDragListener);
|
return new SliderPhotoViewHolder(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,12 +138,16 @@ public final class SliderItemsAdapter extends ListAdapter<Media, SliderItemViewH
|
|||||||
public interface SliderCallback {
|
public interface SliderCallback {
|
||||||
void onThumbnailLoaded(int position);
|
void onThumbnailLoaded(int position);
|
||||||
|
|
||||||
void onItemClicked(int position);
|
void onItemClicked(int position, final Media media, final View view);
|
||||||
|
|
||||||
void onPlayerPlay(int position);
|
void onPlayerPlay(int position);
|
||||||
|
|
||||||
void onPlayerPause(int position);
|
void onPlayerPause(int position);
|
||||||
|
|
||||||
void onPlayerRelease(int position);
|
void onPlayerRelease(int position);
|
||||||
|
|
||||||
|
void onFullScreenModeChanged(boolean isFullScreen, final StyledPlayerView playerView);
|
||||||
|
|
||||||
|
boolean isInFullScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package awais.instagrabber.adapters;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.cursoradapter.widget.CursorAdapter;
|
|
||||||
|
|
||||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
|
||||||
import awais.instagrabber.models.enums.SuggestionType;
|
|
||||||
|
|
||||||
public final class SuggestionsAdapter extends CursorAdapter {
|
|
||||||
private static final String TAG = "SuggestionsAdapter";
|
|
||||||
|
|
||||||
private final OnSuggestionClickListener onSuggestionClickListener;
|
|
||||||
|
|
||||||
public SuggestionsAdapter(final Context context,
|
|
||||||
final OnSuggestionClickListener onSuggestionClickListener) {
|
|
||||||
super(context, null, FLAG_REGISTER_CONTENT_OBSERVER);
|
|
||||||
this.onSuggestionClickListener = onSuggestionClickListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View newView(final Context context, final Cursor cursor, final ViewGroup parent) {
|
|
||||||
final LayoutInflater layoutInflater = LayoutInflater.from(context);
|
|
||||||
final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(layoutInflater, parent, false);
|
|
||||||
return binding.getRoot();
|
|
||||||
// return layoutInflater.inflate(R.layout.item_suggestion, parent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void bindView(@NonNull final View view, final Context context, @NonNull final Cursor cursor) {
|
|
||||||
// i, username, fullname, type, query, picUrl, verified
|
|
||||||
// 0, 1 , 2 , 3 , 4 , 5 , 6
|
|
||||||
final String fullName = cursor.getString(2);
|
|
||||||
String username = cursor.getString(1);
|
|
||||||
String picUrl = cursor.getString(5);
|
|
||||||
final boolean verified = cursor.getString(6).charAt(0) == 't';
|
|
||||||
|
|
||||||
final String type = cursor.getString(3);
|
|
||||||
SuggestionType suggestionType = null;
|
|
||||||
try {
|
|
||||||
suggestionType = SuggestionType.valueOf(type);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.e(TAG, "Unknown suggestion type: " + type, e);
|
|
||||||
}
|
|
||||||
if (suggestionType == null) return;
|
|
||||||
String query = cursor.getString(4);
|
|
||||||
switch (suggestionType) {
|
|
||||||
case TYPE_USER:
|
|
||||||
username = '@' + username;
|
|
||||||
break;
|
|
||||||
case TYPE_HASHTAG:
|
|
||||||
username = '#' + username;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onSuggestionClickListener != null) {
|
|
||||||
final SuggestionType finalSuggestionType = suggestionType;
|
|
||||||
view.setOnClickListener(v -> onSuggestionClickListener.onSuggestionClick(finalSuggestionType, query));
|
|
||||||
}
|
|
||||||
final ItemSuggestionBinding binding = ItemSuggestionBinding.bind(view);
|
|
||||||
binding.isVerified.setVisibility(verified ? View.VISIBLE : View.GONE);
|
|
||||||
binding.tvUsername.setText(username);
|
|
||||||
binding.tvFullName.setVisibility(View.VISIBLE);
|
|
||||||
binding.tvFullName.setText(fullName);
|
|
||||||
binding.ivProfilePic.setImageURI(picUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnSuggestionClickListener {
|
|
||||||
void onSuggestionClick(final SuggestionType type, final String query);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,209 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.view.ContextThemeWrapper;
|
||||||
|
import androidx.appcompat.widget.PopupMenu;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
|
||||||
|
import awais.instagrabber.customviews.ProfilePicView;
|
||||||
|
import awais.instagrabber.databinding.ItemCommentBinding;
|
||||||
|
import awais.instagrabber.models.Comment;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
|
public final class CommentViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final ItemCommentBinding binding;
|
||||||
|
private final long currentUserId;
|
||||||
|
private final CommentCallback commentCallback;
|
||||||
|
@ColorInt
|
||||||
|
private int parentCommentHighlightColor;
|
||||||
|
private PopupMenu optionsPopup;
|
||||||
|
|
||||||
|
public CommentViewHolder(@NonNull final ItemCommentBinding binding,
|
||||||
|
final long currentUserId,
|
||||||
|
final CommentCallback commentCallback) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.currentUserId = currentUserId;
|
||||||
|
this.commentCallback = commentCallback;
|
||||||
|
final Context context = itemView.getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
final Resources.Theme theme = context.getTheme();
|
||||||
|
if (theme == null) return;
|
||||||
|
final TypedValue typedValue = new TypedValue();
|
||||||
|
final boolean resolved = theme.resolveAttribute(R.attr.parentCommentHighlightColor, typedValue, true);
|
||||||
|
if (resolved) {
|
||||||
|
parentCommentHighlightColor = typedValue.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final Comment comment, final boolean isReplyParent, final boolean isReply) {
|
||||||
|
if (comment == null) return;
|
||||||
|
itemView.setOnClickListener(v -> {
|
||||||
|
if (commentCallback != null) {
|
||||||
|
commentCallback.onClick(comment);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (isReplyParent && parentCommentHighlightColor != 0) {
|
||||||
|
itemView.setBackgroundColor(parentCommentHighlightColor);
|
||||||
|
} else {
|
||||||
|
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
|
||||||
|
}
|
||||||
|
setupCommentText(comment, isReply);
|
||||||
|
binding.date.setText(comment.getDateTime());
|
||||||
|
setLikes(comment, isReply);
|
||||||
|
setReplies(comment, isReply);
|
||||||
|
setUser(comment, isReply);
|
||||||
|
setupOptions(comment, isReply);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupCommentText(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
binding.comment.clearOnURLClickListeners();
|
||||||
|
binding.comment.clearOnHashtagClickListeners();
|
||||||
|
binding.comment.clearOnMentionClickListeners();
|
||||||
|
binding.comment.clearOnEmailClickListeners();
|
||||||
|
binding.comment.setText(comment.getText());
|
||||||
|
binding.comment.setTextSize(TypedValue.COMPLEX_UNIT_SP, isReply ? 12 : 14);
|
||||||
|
binding.comment.addOnHashtagListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onHashtagClick(originalText);
|
||||||
|
});
|
||||||
|
binding.comment.addOnMentionClickListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onMentionClick(originalText);
|
||||||
|
|
||||||
|
});
|
||||||
|
binding.comment.addOnEmailClickListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onEmailClick(originalText);
|
||||||
|
});
|
||||||
|
binding.comment.addOnURLClickListener(autoLinkItem -> {
|
||||||
|
final String originalText = autoLinkItem.getOriginalText();
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onURLClick(originalText);
|
||||||
|
});
|
||||||
|
binding.comment.setOnLongClickListener(v -> {
|
||||||
|
Utils.copyText(itemView.getContext(), comment.getText());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
binding.comment.setOnClickListener(v -> commentCallback.onClick(comment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setUser(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
final User user = comment.getUser();
|
||||||
|
if (user == null) return;
|
||||||
|
binding.username.setUsername(user.getUsername(), user.isVerified());
|
||||||
|
binding.username.setTextAppearance(itemView.getContext(), isReply ? R.style.TextAppearance_MaterialComponents_Subtitle2
|
||||||
|
: R.style.TextAppearance_MaterialComponents_Subtitle1);
|
||||||
|
binding.username.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onMentionClick("@" + user.getUsername());
|
||||||
|
});
|
||||||
|
binding.profilePic.setImageURI(user.getProfilePicUrl());
|
||||||
|
binding.profilePic.setSize(isReply ? ProfilePicView.Size.SMALLER : ProfilePicView.Size.SMALL);
|
||||||
|
binding.profilePic.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onMentionClick("@" + user.getUsername());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLikes(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
// final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
|
||||||
|
binding.likes.setText(String.valueOf(comment.getLikes()));
|
||||||
|
binding.likes.setOnLongClickListener(v -> {
|
||||||
|
if (commentCallback == null) return false;
|
||||||
|
commentCallback.onViewLikes(comment);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (currentUserId == 0) { // not logged in
|
||||||
|
binding.likes.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onViewLikes(comment);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final boolean liked = comment.getLiked();
|
||||||
|
final int resId = liked ? R.drawable.ic_like : R.drawable.ic_not_liked;
|
||||||
|
binding.likes.setCompoundDrawablesRelativeWithSize(ContextCompat.getDrawable(itemView.getContext(), resId), null, null, null);
|
||||||
|
binding.likes.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
// toggle like
|
||||||
|
commentCallback.onLikeClick(comment, !liked, isReply);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setReplies(@NonNull final Comment comment, final boolean isReply) {
|
||||||
|
final int replies = comment.getReplyCount();
|
||||||
|
binding.replies.setVisibility(View.VISIBLE);
|
||||||
|
final String text = isReply ? "" : String.valueOf(replies);
|
||||||
|
// final String string = itemView.getResources().getQuantityString(R.plurals.replies_count, replies, replies);
|
||||||
|
binding.replies.setText(text);
|
||||||
|
binding.replies.setOnClickListener(v -> {
|
||||||
|
if (commentCallback == null) return;
|
||||||
|
commentCallback.onRepliesClick(comment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupOptions(final Comment comment, final boolean isReply) {
|
||||||
|
binding.options.setOnClickListener(v -> {
|
||||||
|
if (optionsPopup == null) {
|
||||||
|
createOptionsPopupMenu(comment, isReply);
|
||||||
|
}
|
||||||
|
if (optionsPopup == null) return;
|
||||||
|
optionsPopup.show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createOptionsPopupMenu(final Comment comment, final boolean isReply) {
|
||||||
|
if (optionsPopup == null) {
|
||||||
|
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(itemView.getContext(), R.style.popupMenuStyle);
|
||||||
|
optionsPopup = new PopupMenu(themeWrapper, binding.options);
|
||||||
|
} else {
|
||||||
|
optionsPopup.getMenu().clear();
|
||||||
|
}
|
||||||
|
optionsPopup.getMenuInflater().inflate(R.menu.comment_options_menu, optionsPopup.getMenu());
|
||||||
|
final User user = comment.getUser();
|
||||||
|
if (currentUserId == 0 || user == null || user.getPk() != currentUserId) {
|
||||||
|
final Menu menu = optionsPopup.getMenu();
|
||||||
|
menu.removeItem(R.id.delete);
|
||||||
|
}
|
||||||
|
optionsPopup.setOnMenuItemClickListener(item -> {
|
||||||
|
if (commentCallback == null) return false;
|
||||||
|
int itemId = item.getItemId();
|
||||||
|
if (itemId == R.id.translate) {
|
||||||
|
commentCallback.onTranslate(comment);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (itemId == R.id.delete) {
|
||||||
|
commentCallback.onDelete(comment, isReply);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// private void setupReply(final Comment comment) {
|
||||||
|
// if (!isLoggedIn) {
|
||||||
|
// binding.reply.setVisibility(View.GONE);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// binding.reply.setOnClickListener(v -> {
|
||||||
|
// if (commentCallback == null) return;
|
||||||
|
// // toggle like
|
||||||
|
// commentCallback.onReplyClick(comment);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
}
|
@ -6,7 +6,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.FavoritesAdapter;
|
import awais.instagrabber.adapters.FavoritesAdapter;
|
||||||
import awais.instagrabber.databinding.ItemSuggestionBinding;
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
import awais.instagrabber.db.entities.Favorite;
|
import awais.instagrabber.db.entities.Favorite;
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
@ -14,12 +14,12 @@ import awais.instagrabber.utils.Constants;
|
|||||||
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
||||||
private static final String TAG = "FavoriteViewHolder";
|
private static final String TAG = "FavoriteViewHolder";
|
||||||
|
|
||||||
private final ItemSuggestionBinding binding;
|
private final ItemSearchResultBinding binding;
|
||||||
|
|
||||||
public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) {
|
public FavoriteViewHolder(@NonNull final ItemSearchResultBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
binding.isVerified.setVisibility(View.GONE);
|
binding.verified.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final Favorite model,
|
public void bind(final Favorite model,
|
||||||
@ -36,12 +36,12 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
|||||||
return longClickListener.onLongClick(model);
|
return longClickListener.onLongClick(model);
|
||||||
});
|
});
|
||||||
if (model.getType() == FavoriteType.HASHTAG) {
|
if (model.getType() == FavoriteType.HASHTAG) {
|
||||||
binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
|
binding.profilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC);
|
||||||
} else {
|
} else {
|
||||||
binding.ivProfilePic.setImageURI(model.getPicUrl());
|
binding.profilePic.setImageURI(model.getPicUrl());
|
||||||
}
|
}
|
||||||
binding.tvFullName.setText(model.getDisplayName());
|
binding.title.setVisibility(View.VISIBLE);
|
||||||
binding.tvUsername.setVisibility(View.VISIBLE);
|
binding.subtitle.setText(model.getDisplayName());
|
||||||
String query = model.getQuery();
|
String query = model.getQuery();
|
||||||
switch (model.getType()) {
|
switch (model.getType()) {
|
||||||
case HASHTAG:
|
case HASHTAG:
|
||||||
@ -51,11 +51,11 @@ public class FavoriteViewHolder extends RecyclerView.ViewHolder {
|
|||||||
query = "@" + query;
|
query = "@" + query;
|
||||||
break;
|
break;
|
||||||
case LOCATION:
|
case LOCATION:
|
||||||
binding.tvUsername.setVisibility(View.GONE);
|
binding.title.setVisibility(View.GONE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// do nothing
|
// do nothing
|
||||||
}
|
}
|
||||||
binding.tvUsername.setText(query);
|
binding.title.setText(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,9 @@ package awais.instagrabber.adapters.viewholder;
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.databinding.ItemFollowBinding;
|
import awais.instagrabber.databinding.ItemFollowBinding;
|
||||||
import awais.instagrabber.models.FollowModel;
|
import awais.instagrabber.models.FollowModel;
|
||||||
import awais.instagrabber.repositories.responses.User;
|
import awais.instagrabber.repositories.responses.User;
|
||||||
@ -14,23 +13,19 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
|||||||
|
|
||||||
private final ItemFollowBinding binding;
|
private final ItemFollowBinding binding;
|
||||||
|
|
||||||
public FollowsViewHolder(final ItemFollowBinding binding) {
|
public FollowsViewHolder(@NonNull final ItemFollowBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final User model,
|
public void bind(final User model,
|
||||||
final List<Long> admins,
|
|
||||||
final View.OnClickListener onClickListener) {
|
final View.OnClickListener onClickListener) {
|
||||||
if (model == null) return;
|
if (model == null) return;
|
||||||
itemView.setTag(model);
|
itemView.setTag(model);
|
||||||
itemView.setOnClickListener(onClickListener);
|
itemView.setOnClickListener(onClickListener);
|
||||||
binding.tvUsername.setText(model.getUsername());
|
binding.username.setUsername("@" + model.getUsername(), model.isVerified());
|
||||||
binding.tvFullName.setText(model.getFullName());
|
binding.fullName.setText(model.getFullName());
|
||||||
if (admins != null && admins.contains(model.getPk())) {
|
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
||||||
binding.isAdmin.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final FollowModel model,
|
public void bind(final FollowModel model,
|
||||||
@ -38,8 +33,8 @@ public final class FollowsViewHolder extends RecyclerView.ViewHolder {
|
|||||||
if (model == null) return;
|
if (model == null) return;
|
||||||
itemView.setTag(model);
|
itemView.setTag(model);
|
||||||
itemView.setOnClickListener(onClickListener);
|
itemView.setOnClickListener(onClickListener);
|
||||||
binding.tvUsername.setText(model.getUsername());
|
binding.username.setUsername("@" + model.getUsername());
|
||||||
binding.tvFullName.setText(model.getFullName());
|
binding.fullName.setText(model.getFullName());
|
||||||
binding.ivProfilePic.setImageURI(model.getProfilePicUrl());
|
binding.profilePic.setImageURI(model.getProfilePicUrl());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
package awais.instagrabber.adapters.viewholder;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.databinding.ItemSearchResultBinding;
|
||||||
|
import awais.instagrabber.fragments.search.SearchCategoryFragment.OnSearchItemClickListener;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
import awais.instagrabber.repositories.responses.Hashtag;
|
||||||
|
import awais.instagrabber.repositories.responses.Place;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
|
import awais.instagrabber.repositories.responses.search.SearchItem;
|
||||||
|
|
||||||
|
public class SearchItemViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final ItemSearchResultBinding binding;
|
||||||
|
private final OnSearchItemClickListener onSearchItemClickListener;
|
||||||
|
|
||||||
|
public SearchItemViewHolder(@NonNull final ItemSearchResultBinding binding,
|
||||||
|
final OnSearchItemClickListener onSearchItemClickListener) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.onSearchItemClickListener = onSearchItemClickListener;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void bind(final SearchItem searchItem) {
|
||||||
|
if (searchItem == null) return;
|
||||||
|
final FavoriteType type = searchItem.getType();
|
||||||
|
if (type == null) return;
|
||||||
|
String title;
|
||||||
|
String subtitle;
|
||||||
|
String picUrl;
|
||||||
|
boolean isVerified = false;
|
||||||
|
switch (type) {
|
||||||
|
case USER:
|
||||||
|
final User user = searchItem.getUser();
|
||||||
|
title = "@" + user.getUsername();
|
||||||
|
subtitle = user.getFullName();
|
||||||
|
picUrl = user.getProfilePicUrl();
|
||||||
|
isVerified = user.isVerified();
|
||||||
|
break;
|
||||||
|
case HASHTAG:
|
||||||
|
final Hashtag hashtag = searchItem.getHashtag();
|
||||||
|
title = "#" + hashtag.getName();
|
||||||
|
subtitle = hashtag.getSubtitle();
|
||||||
|
picUrl = "res:/" + R.drawable.ic_hashtag;
|
||||||
|
break;
|
||||||
|
case LOCATION:
|
||||||
|
final Place place = searchItem.getPlace();
|
||||||
|
title = place.getTitle();
|
||||||
|
subtitle = place.getSubtitle();
|
||||||
|
picUrl = "res:/" + R.drawable.ic_location;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
itemView.setOnClickListener(v -> {
|
||||||
|
if (onSearchItemClickListener != null) {
|
||||||
|
onSearchItemClickListener.onSearchItemClick(searchItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
binding.delete.setVisibility(searchItem.isRecent() ? View.VISIBLE : View.GONE);
|
||||||
|
if (searchItem.isRecent()) {
|
||||||
|
binding.delete.setEnabled(true);
|
||||||
|
binding.delete.setOnClickListener(v -> {
|
||||||
|
if (onSearchItemClickListener != null) {
|
||||||
|
binding.delete.setEnabled(false);
|
||||||
|
onSearchItemClickListener.onSearchItemDelete(searchItem);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
binding.title.setText(title);
|
||||||
|
binding.subtitle.setText(subtitle);
|
||||||
|
binding.profilePic.setImageURI(picUrl);
|
||||||
|
binding.verified.setVisibility(isVerified ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ package awais.instagrabber.adapters.viewholder;
|
|||||||
|
|
||||||
import android.graphics.drawable.Animatable;
|
import android.graphics.drawable.Animatable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -14,8 +13,8 @@ import com.facebook.imagepipeline.request.ImageRequest;
|
|||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
|
||||||
|
import awais.instagrabber.customviews.drawee.DoubleTapGestureListener;
|
||||||
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
import awais.instagrabber.databinding.ItemSliderPhotoBinding;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
@ -24,13 +23,10 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
|||||||
private static final String TAG = "FeedSliderPhotoViewHolder";
|
private static final String TAG = "FeedSliderPhotoViewHolder";
|
||||||
|
|
||||||
private final ItemSliderPhotoBinding binding;
|
private final ItemSliderPhotoBinding binding;
|
||||||
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener;
|
|
||||||
|
|
||||||
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding,
|
public SliderPhotoViewHolder(@NonNull final ItemSliderPhotoBinding binding) {
|
||||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener) {
|
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
this.onVerticalDragListener = onVerticalDragListener;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bind(@NonNull final Media model,
|
public void bind(@NonNull final Media model,
|
||||||
@ -62,74 +58,19 @@ public class SliderPhotoViewHolder extends SliderItemViewHolder {
|
|||||||
})
|
})
|
||||||
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
.setLowResImageRequest(ImageRequest.fromUri(ResponseBodyUtils.getThumbUrl(model)))
|
||||||
.build());
|
.build());
|
||||||
// binding.getRoot().setOnClickListener(v -> {
|
final DoubleTapGestureListener tapListener = new DoubleTapGestureListener(binding.getRoot()) {
|
||||||
// if (sliderCallback != null) {
|
|
||||||
// sliderCallback.onItemClicked(position);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
binding.getRoot().setTapListener(new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapUp(final MotionEvent e) {
|
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||||
if (sliderCallback != null) {
|
if (sliderCallback != null) {
|
||||||
sliderCallback.onItemClicked(position);
|
sliderCallback.onItemClicked(position, model, binding.getRoot());
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return false;
|
return super.onSingleTapConfirmed(e);
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
binding.getRoot().setTapListener(tapListener);
|
||||||
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
final AnimatedZoomableController zoomableController = AnimatedZoomableController.newInstance();
|
||||||
zoomableController.setMaxScaleFactor(3f);
|
zoomableController.setMaxScaleFactor(3f);
|
||||||
binding.getRoot().setZoomableController(zoomableController);
|
binding.getRoot().setZoomableController(zoomableController);
|
||||||
if (onVerticalDragListener != null) {
|
binding.getRoot().setZoomingEnabled(true);
|
||||||
binding.getRoot().setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
|
||||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
|
||||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
|
||||||
// final int spanWidth = deviceWidth / spanCount;
|
|
||||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
|
||||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
|
||||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
|
||||||
// if (animate) {
|
|
||||||
// Animation animation = AnimationUtils.expand(
|
|
||||||
// binding.imageViewer,
|
|
||||||
// layoutParams.width,
|
|
||||||
// layoutParams.height,
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// new Animation.AnimationListener() {
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationStart(final Animation animation) {
|
|
||||||
// showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationEnd(final Animation animation) {
|
|
||||||
// // showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationRepeat(final Animation animation) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// binding.imageViewer.startAnimation(animation);
|
|
||||||
// } else {
|
|
||||||
// layoutParams.width = width;
|
|
||||||
// layoutParams.height = height;
|
|
||||||
// binding.imageViewer.requestLayout();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void showOrHideDetails(final int spanCount) {
|
|
||||||
// if (spanCount == 1) {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// } else {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
@ -7,13 +7,13 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import awais.instagrabber.adapters.SliderItemsAdapter;
|
import awais.instagrabber.adapters.SliderItemsAdapter;
|
||||||
import awais.instagrabber.customviews.VerticalDragHelper;
|
|
||||||
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
|
||||||
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
import awais.instagrabber.customviews.VideoPlayerViewHelper;
|
||||||
import awais.instagrabber.databinding.LayoutExoCustomControlsBinding;
|
|
||||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.repositories.responses.VideoVersion;
|
import awais.instagrabber.repositories.responses.VideoVersion;
|
||||||
@ -28,40 +28,23 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
private static final String TAG = "SliderVideoViewHolder";
|
private static final String TAG = "SliderVideoViewHolder";
|
||||||
|
|
||||||
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
||||||
private final LayoutExoCustomControlsBinding controlsBinding;
|
|
||||||
private final boolean loadVideoOnItemClick;
|
private final boolean loadVideoOnItemClick;
|
||||||
private final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
|
||||||
|
private VideoPlayerViewHelper videoPlayerViewHelper;
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
|
||||||
|
final boolean loadVideoOnItemClick) {
|
||||||
|
super(binding.getRoot());
|
||||||
|
this.binding = binding;
|
||||||
|
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
||||||
|
final GestureDetector.OnGestureListener videoPlayerViewGestureListener = new GestureDetector.SimpleOnGestureListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
public boolean onSingleTapConfirmed(final MotionEvent e) {
|
||||||
binding.playerView.performClick();
|
binding.playerView.performClick();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private VideoPlayerViewHelper videoPlayerViewHelper;
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
public SliderVideoViewHolder(@NonNull final LayoutVideoPlayerWithThumbnailBinding binding,
|
|
||||||
final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener,
|
|
||||||
final LayoutExoCustomControlsBinding controlsBinding,
|
|
||||||
final boolean loadVideoOnItemClick) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
this.binding = binding;
|
|
||||||
this.controlsBinding = controlsBinding;
|
|
||||||
this.loadVideoOnItemClick = loadVideoOnItemClick;
|
|
||||||
// if (onVerticalDragListener != null) {
|
|
||||||
// final VerticalDragHelper thumbnailVerticalDragHelper = new VerticalDragHelper(binding.thumbnailParent);
|
|
||||||
// final VerticalDragHelper playerVerticalDragHelper = new VerticalDragHelper(binding.playerView);
|
|
||||||
// thumbnailVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
// playerVerticalDragHelper.setOnVerticalDragListener(onVerticalDragListener);
|
|
||||||
// binding.thumbnailParent.setOnTouchListener((v, event) -> {
|
|
||||||
// final boolean onDragTouch = thumbnailVerticalDragHelper.onDragTouch(event);
|
|
||||||
// if (onDragTouch) {
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// return thumbnailVerticalDragHelper.onGestureTouchEvent(event);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
|
final GestureDetector gestureDetector = new GestureDetector(itemView.getContext(), videoPlayerViewGestureListener);
|
||||||
binding.playerView.setOnTouchListener((v, event) -> {
|
binding.playerView.setOnTouchListener((v, event) -> {
|
||||||
gestureDetector.onTouchEvent(event);
|
gestureDetector.onTouchEvent(event);
|
||||||
@ -78,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +104,21 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
sliderCallback.onPlayerRelease(position);
|
sliderCallback.onPlayerRelease(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFullScreenModeChanged(final boolean isFullScreen, final StyledPlayerView playerView) {
|
||||||
|
if (sliderCallback != null) {
|
||||||
|
sliderCallback.onFullScreenModeChanged(isFullScreen, playerView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInFullScreen() {
|
||||||
|
if (sliderCallback != null) {
|
||||||
|
return sliderCallback.isInFullScreen();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight();
|
final float aspectRatio = (float) media.getOriginalWidth() / media.getOriginalHeight();
|
||||||
String videoUrl = null;
|
String videoUrl = null;
|
||||||
@ -139,16 +137,10 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
ResponseBodyUtils.getThumbUrl(media),
|
ResponseBodyUtils.getThumbUrl(media),
|
||||||
loadVideoOnItemClick,
|
loadVideoOnItemClick,
|
||||||
controlsBinding,
|
|
||||||
videoPlayerCallback);
|
videoPlayerCallback);
|
||||||
// binding.itemFeedBottom.btnMute.setOnClickListener(v -> {
|
|
||||||
// final float newVol = videoPlayerViewHelper.toggleMute();
|
|
||||||
// setMuteIcon(newVol);
|
|
||||||
// Utils.sessionVolumeFull = newVol == 1f;
|
|
||||||
// });
|
|
||||||
binding.playerView.setOnClickListener(v -> {
|
binding.playerView.setOnClickListener(v -> {
|
||||||
if (sliderCallback != null) {
|
if (sliderCallback != null) {
|
||||||
sliderCallback.onItemClicked(position);
|
sliderCallback.onItemClicked(position, media, binding.getRoot());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -162,62 +154,4 @@ public class SliderVideoViewHolder extends SliderItemViewHolder {
|
|||||||
if (videoPlayerViewHelper == null) return;
|
if (videoPlayerViewHelper == null) return;
|
||||||
videoPlayerViewHelper.releasePlayer();
|
videoPlayerViewHelper.releasePlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resetPlayerTimeline() {
|
|
||||||
if (videoPlayerViewHelper == null) return;
|
|
||||||
videoPlayerViewHelper.resetTimeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeCallbacks() {
|
|
||||||
if (videoPlayerViewHelper == null) return;
|
|
||||||
videoPlayerViewHelper.removeCallbacks();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private void setDimensions(final FeedModel feedModel, final int spanCount, final boolean animate) {
|
|
||||||
// final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams();
|
|
||||||
// final int deviceWidth = Utils.displayMetrics.widthPixels;
|
|
||||||
// final int spanWidth = deviceWidth / spanCount;
|
|
||||||
// final int spanHeight = NumberUtils.getResultingHeight(spanWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
|
|
||||||
// final int width = spanWidth == 0 ? deviceWidth : spanWidth;
|
|
||||||
// final int height = spanHeight == 0 ? deviceWidth + 1 : spanHeight;
|
|
||||||
// if (animate) {
|
|
||||||
// Animation animation = AnimationUtils.expand(
|
|
||||||
// binding.imageViewer,
|
|
||||||
// layoutParams.width,
|
|
||||||
// layoutParams.height,
|
|
||||||
// width,
|
|
||||||
// height,
|
|
||||||
// new Animation.AnimationListener() {
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationStart(final Animation animation) {
|
|
||||||
// showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationEnd(final Animation animation) {
|
|
||||||
// // showOrHideDetails(spanCount);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public void onAnimationRepeat(final Animation animation) {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// binding.imageViewer.startAnimation(animation);
|
|
||||||
// } else {
|
|
||||||
// layoutParams.width = width;
|
|
||||||
// layoutParams.height = height;
|
|
||||||
// binding.imageViewer.requestLayout();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private void showOrHideDetails(final int spanCount) {
|
|
||||||
// if (spanCount == 1) {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.VISIBLE);
|
|
||||||
// } else {
|
|
||||||
// binding.itemFeedTop.getRoot().setVisibility(View.GONE);
|
|
||||||
// binding.itemFeedBottom.getRoot().setVisibility(View.GONE);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
@ -1,95 +0,0 @@
|
|||||||
package awais.instagrabber.adapters.viewholder.comments;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
|
|
||||||
import awais.instagrabber.databinding.ItemCommentSmallBinding;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public final class ChildCommentViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ItemCommentSmallBinding binding;
|
|
||||||
|
|
||||||
public ChildCommentViewHolder(@NonNull final ItemCommentSmallBinding binding) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(final CommentModel comment,
|
|
||||||
final boolean selected,
|
|
||||||
final CommentCallback commentCallback) {
|
|
||||||
if (comment == null) return;
|
|
||||||
if (commentCallback != null) {
|
|
||||||
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
|
|
||||||
} else {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
|
|
||||||
}
|
|
||||||
setupCommentText(comment, commentCallback);
|
|
||||||
binding.tvDate.setText(comment.getDateTime());
|
|
||||||
setLiked(comment.getLiked());
|
|
||||||
setLikes((int) comment.getLikes());
|
|
||||||
setUser(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
|
|
||||||
binding.tvComment.clearOnURLClickListeners();
|
|
||||||
binding.tvComment.clearOnHashtagClickListeners();
|
|
||||||
binding.tvComment.clearOnMentionClickListeners();
|
|
||||||
binding.tvComment.clearOnEmailClickListeners();
|
|
||||||
binding.tvComment.setText(comment.getText());
|
|
||||||
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onHashtagClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onMentionClick(originalText);
|
|
||||||
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onEmailClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onURLClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnLongClickListener(v -> {
|
|
||||||
Utils.copyText(itemView.getContext(), comment.getText());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUser(final CommentModel comment) {
|
|
||||||
final User profileModel = comment.getProfileModel();
|
|
||||||
if (profileModel == null) return;
|
|
||||||
binding.tvUsername.setText(profileModel.getUsername());
|
|
||||||
binding.ivProfilePic.setImageURI(profileModel.getProfilePicUrl());
|
|
||||||
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLikes(final int likes) {
|
|
||||||
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
|
|
||||||
binding.tvLikes.setText(likesString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setLiked(final boolean liked) {
|
|
||||||
if (liked) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package awais.instagrabber.adapters.viewholder.comments;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.adapters.CommentsAdapter.CommentCallback;
|
|
||||||
import awais.instagrabber.databinding.ItemCommentBinding;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
public final class ParentCommentViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
private final ItemCommentBinding binding;
|
|
||||||
|
|
||||||
public ParentCommentViewHolder(@NonNull final ItemCommentBinding binding) {
|
|
||||||
super(binding.getRoot());
|
|
||||||
this.binding = binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void bind(final CommentModel comment,
|
|
||||||
final boolean selected,
|
|
||||||
final CommentCallback commentCallback) {
|
|
||||||
if (comment == null) return;
|
|
||||||
if (commentCallback != null) {
|
|
||||||
itemView.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
if (selected) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_selected));
|
|
||||||
} else {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(android.R.color.transparent));
|
|
||||||
}
|
|
||||||
setupCommentText(comment, commentCallback);
|
|
||||||
binding.tvDate.setText(comment.getDateTime());
|
|
||||||
setLiked(comment.getLiked());
|
|
||||||
setLikes((int) comment.getLikes());
|
|
||||||
setUser(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupCommentText(final CommentModel comment, final CommentCallback commentCallback) {
|
|
||||||
binding.tvComment.clearOnURLClickListeners();
|
|
||||||
binding.tvComment.clearOnHashtagClickListeners();
|
|
||||||
binding.tvComment.clearOnMentionClickListeners();
|
|
||||||
binding.tvComment.clearOnEmailClickListeners();
|
|
||||||
binding.tvComment.setText(comment.getText());
|
|
||||||
binding.tvComment.addOnHashtagListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onHashtagClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnMentionClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onMentionClick(originalText);
|
|
||||||
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnEmailClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onEmailClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.addOnURLClickListener(autoLinkItem -> {
|
|
||||||
final String originalText = autoLinkItem.getOriginalText();
|
|
||||||
if (commentCallback == null) return;
|
|
||||||
commentCallback.onURLClick(originalText);
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnLongClickListener(v -> {
|
|
||||||
Utils.copyText(itemView.getContext(), comment.getText());
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
binding.tvComment.setOnClickListener(v -> commentCallback.onClick(comment));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setUser(final CommentModel comment) {
|
|
||||||
final User profileModel = comment.getProfileModel();
|
|
||||||
if (profileModel == null) return;
|
|
||||||
binding.tvUsername.setText(profileModel.getUsername());
|
|
||||||
binding.ivProfilePic.setImageURI(profileModel.getProfilePicUrl());
|
|
||||||
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setLikes(final int likes) {
|
|
||||||
final String likesString = itemView.getResources().getQuantityString(R.plurals.likes_count, likes, likes);
|
|
||||||
binding.tvLikes.setText(likesString);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void setLiked(final boolean liked) {
|
|
||||||
if (liked) {
|
|
||||||
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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);
|
||||||
|
@ -112,6 +112,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void bind(final int position, final DirectItem item) {
|
public void bind(final int position, final DirectItem item) {
|
||||||
|
if (item == null) return;
|
||||||
this.item = item;
|
this.item = item;
|
||||||
messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
|
messageDirection = isSelf(item) ? MessageDirection.OUTGOING : MessageDirection.INCOMING;
|
||||||
// Asynchronous binding causes some weird behaviour
|
// Asynchronous binding causes some weird behaviour
|
||||||
@ -123,7 +124,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
setupLongClickListener(position, messageDirection);
|
setupLongClickListener(position, messageDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindBase(final DirectItem item, final MessageDirection messageDirection, final int position) {
|
private void bindBase(@NonNull final DirectItem item, final MessageDirection messageDirection, final int position) {
|
||||||
final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams();
|
final FrameLayout.LayoutParams containerLayoutParams = (FrameLayout.LayoutParams) binding.container.getLayoutParams();
|
||||||
final DirectItemType itemType = item.getItemType();
|
final DirectItemType itemType = item.getItemType();
|
||||||
setMessageDirectionGravity(messageDirection, containerLayoutParams);
|
setMessageDirectionGravity(messageDirection, containerLayoutParams);
|
||||||
@ -188,7 +189,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple
|
|||||||
containerLayoutParams.gravity = Gravity.CENTER;
|
containerLayoutParams.gravity = Gravity.CENTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setMessageInfo(final DirectItem item, final MessageDirection messageDirection) {
|
private void setMessageInfo(@NonNull final DirectItem item, final MessageDirection messageDirection) {
|
||||||
if (showMessageInfo()) {
|
if (showMessageInfo()) {
|
||||||
binding.messageInfo.setVisibility(View.VISIBLE);
|
binding.messageInfo.setVisibility(View.VISIBLE);
|
||||||
binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE);
|
binding.deliveryStatus.setVisibility(messageDirection == MessageDirection.OUTGOING ? View.VISIBLE : View.GONE);
|
||||||
@ -550,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();
|
||||||
|
@ -55,7 +55,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
private void setupComments(@NonNull final Media feedModel) {
|
private void setupComments(@NonNull final Media feedModel) {
|
||||||
final long commentsCount = feedModel.getCommentCount();
|
final long commentsCount = feedModel.getCommentCount();
|
||||||
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
|
bottomBinding.commentsCount.setText(String.valueOf(commentsCount));
|
||||||
bottomBinding.commentsCount.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
|
bottomBinding.btnComments.setOnClickListener(v -> feedItemCallback.onCommentsClick(feedModel));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupProfilePic(@NonNull final Media media) {
|
private void setupProfilePic(@NonNull final Media media) {
|
||||||
@ -75,6 +75,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
// final SpannableString spannableString = new SpannableString();
|
// final SpannableString spannableString = new SpannableString();
|
||||||
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
|
// spannableString.setSpan(new CommentMentionClickSpan(), 0, titleLen, 0);
|
||||||
final User user = media.getUser();
|
final User user = media.getUser();
|
||||||
|
if (user == null) return;
|
||||||
final String title = "@" + user.getUsername();
|
final String title = "@" + user.getUsername();
|
||||||
topBinding.title.setText(title);
|
topBinding.title.setText(title);
|
||||||
topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(media, topBinding.ivProfilePic));
|
topBinding.title.setOnClickListener(v -> feedItemCallback.onNameClick(media, topBinding.ivProfilePic));
|
||||||
@ -120,8 +121,7 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
|
|||||||
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
|
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
|
||||||
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
|
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT
|
||||||
));
|
));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
final String locationName = location.getName();
|
final String locationName = location.getName();
|
||||||
if (TextUtils.isEmpty(locationName)) {
|
if (TextUtils.isEmpty(locationName)) {
|
||||||
topBinding.location.setVisibility(View.GONE);
|
topBinding.location.setVisibility(View.GONE);
|
||||||
|
@ -45,9 +45,9 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
|
|||||||
final String text = "1/" + sliderItemLen;
|
final String text = "1/" + sliderItemLen;
|
||||||
binding.mediaCounter.setText(text);
|
binding.mediaCounter.setText(text);
|
||||||
binding.mediaList.setOffscreenPageLimit(1);
|
binding.mediaList.setOffscreenPageLimit(1);
|
||||||
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, null, false, new SliderCallbackAdapter() {
|
final SliderItemsAdapter adapter = new SliderItemsAdapter(false, new SliderCallbackAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClicked(final int position) {
|
public void onItemClicked(final int position, final Media media, final View view) {
|
||||||
feedItemCallback.onSliderClick(feedModel, position);
|
feedItemCallback.onSliderClick(feedModel, position);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -97,7 +97,7 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
|
|||||||
aspectRatio,
|
aspectRatio,
|
||||||
ResponseBodyUtils.getThumbUrl(media),
|
ResponseBodyUtils.getThumbUrl(media),
|
||||||
false,
|
false,
|
||||||
null,
|
// null,
|
||||||
videoPlayerCallback);
|
videoPlayerCallback);
|
||||||
binding.videoPost.thumbnail.post(() -> {
|
binding.videoPost.thumbnail.post(() -> {
|
||||||
if (media.getOriginalHeight() > 0.8 * Utils.displayMetrics.heightPixels) {
|
if (media.getOriginalHeight() > 0.8 * Utils.displayMetrics.heightPixels) {
|
||||||
|
@ -1,268 +0,0 @@
|
|||||||
package awais.instagrabber.asyncs;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.BuildConfig;
|
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.FriendshipStatus;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.NetworkUtils;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
//import awaisomereport.LogCollector;
|
|
||||||
|
|
||||||
//import static awais.instagrabber.utils.Utils.logCollector;
|
|
||||||
|
|
||||||
public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
|
|
||||||
private static final String TAG = "CommentsFetcher";
|
|
||||||
|
|
||||||
private final String shortCode, endCursor;
|
|
||||||
private final FetchListener<List<CommentModel>> fetchListener;
|
|
||||||
|
|
||||||
public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener<List<CommentModel>> fetchListener) {
|
|
||||||
this.shortCode = shortCode;
|
|
||||||
this.endCursor = endCursor;
|
|
||||||
this.fetchListener = fetchListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
protected List<CommentModel> doInBackground(final Void... voids) {
|
|
||||||
/*
|
|
||||||
"https://www.instagram.com/graphql/query/?query_hash=97b41c52301f77ce508f55e66d17620e&variables=" + "{\"shortcode\":\"" + shortcode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
|
|
||||||
|
|
||||||
97b41c52301f77ce508f55e66d17620e -> for comments
|
|
||||||
51fdd02b67508306ad4484ff574a0b62 -> for child comments
|
|
||||||
|
|
||||||
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""}
|
|
||||||
*/
|
|
||||||
final List<CommentModel> commentModels = getParentComments();
|
|
||||||
if (commentModels != null) {
|
|
||||||
for (final CommentModel commentModel : commentModels) {
|
|
||||||
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
|
|
||||||
if (childCommentModels != null) {
|
|
||||||
final int childCommentsLen = childCommentModels.size();
|
|
||||||
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
|
|
||||||
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
|
|
||||||
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
|
|
||||||
commentModel.setChildCommentModels(remoteChildComments);
|
|
||||||
lastChild.setPageCursor(false, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return commentModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
if (fetchListener != null) fetchListener.doBefore();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(final List<CommentModel> result) {
|
|
||||||
if (fetchListener != null) fetchListener.onResult(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private synchronized List<CommentModel> getChildComments(final String commentId) {
|
|
||||||
final List<CommentModel> commentModels = new ArrayList<>();
|
|
||||||
String childEndCursor = "";
|
|
||||||
while (childEndCursor != null) {
|
|
||||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
|
|
||||||
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}";
|
|
||||||
try {
|
|
||||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
conn.setUseCaches(false);
|
|
||||||
conn.connect();
|
|
||||||
|
|
||||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
|
|
||||||
else {
|
|
||||||
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
|
|
||||||
.getJSONObject("comment")
|
|
||||||
.getJSONObject("edge_threaded_comments");
|
|
||||||
|
|
||||||
final JSONObject pageInfo = data.getJSONObject("page_info");
|
|
||||||
childEndCursor = pageInfo.getString("end_cursor");
|
|
||||||
if (TextUtils.isEmpty(childEndCursor)) childEndCursor = null;
|
|
||||||
|
|
||||||
final JSONArray childComments = data.optJSONArray("edges");
|
|
||||||
if (childComments != null) {
|
|
||||||
final int length = childComments.length();
|
|
||||||
for (int i = 0; i < length; ++i) {
|
|
||||||
final JSONObject childComment = childComments.getJSONObject(i).optJSONObject("node");
|
|
||||||
|
|
||||||
if (childComment != null) {
|
|
||||||
final JSONObject owner = childComment.getJSONObject("owner");
|
|
||||||
final User user = new User(
|
|
||||||
owner.optLong(Constants.EXTRAS_ID, 0),
|
|
||||||
owner.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
owner.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
|
|
||||||
false, false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null,
|
|
||||||
null, null, null);
|
|
||||||
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
|
|
||||||
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
|
|
||||||
childComment.getString("text"),
|
|
||||||
childComment.getLong("created_at"),
|
|
||||||
likedBy != null ? likedBy.optLong("count", 0) : 0,
|
|
||||||
childComment.getBoolean("viewer_has_liked"),
|
|
||||||
user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conn.disconnect();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
// if (logCollector != null)
|
|
||||||
// logCollector.appendException(e,
|
|
||||||
// LogCollector.LogFile.ASYNC_COMMENTS_FETCHER,
|
|
||||||
// "getChildComments",
|
|
||||||
// new Pair<>("commentModels.size", commentModels.size()));
|
|
||||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
|
||||||
if (fetchListener != null) fetchListener.onFailure(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return commentModels;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private synchronized List<CommentModel> getParentComments() {
|
|
||||||
final List<CommentModel> commentModels = new ArrayList<>();
|
|
||||||
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" +
|
|
||||||
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}";
|
|
||||||
try {
|
|
||||||
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
|
|
||||||
conn.setUseCaches(false);
|
|
||||||
conn.connect();
|
|
||||||
|
|
||||||
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null;
|
|
||||||
else {
|
|
||||||
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
|
|
||||||
.getJSONObject("shortcode_media")
|
|
||||||
.getJSONObject(
|
|
||||||
"edge_media_to_parent_comment");
|
|
||||||
|
|
||||||
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
|
|
||||||
final String foundEndCursor = pageInfo.optString("end_cursor");
|
|
||||||
final boolean hasNextPage = pageInfo.optBoolean("has_next_page", !TextUtils.isEmpty(foundEndCursor));
|
|
||||||
|
|
||||||
// final boolean containsToken = endCursor.contains("bifilter_token");
|
|
||||||
// if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) {
|
|
||||||
// final JSONObject endCursorObject = new JSONObject(endCursor);
|
|
||||||
// endCursor = endCursorObject.optString("cached_comments_cursor");
|
|
||||||
//
|
|
||||||
// if (!Utils.isEmpty(endCursor))
|
|
||||||
// endCursor = "{\\\"cached_comments_cursor\\\": \\\"" + endCursor + "\\\", ";
|
|
||||||
// else
|
|
||||||
// endCursor = "{";
|
|
||||||
//
|
|
||||||
// endCursor = endCursor + "\\\"bifilter_token\\\": \\\"" + endCursorObject.getString("bifilter_token") + "\\\"}";
|
|
||||||
// }
|
|
||||||
// else if (containsToken) endCursor = null;
|
|
||||||
|
|
||||||
final JSONArray comments = parentComments.getJSONArray("edges");
|
|
||||||
final int commentsLen = comments.length();
|
|
||||||
for (int i = 0; i < commentsLen; ++i) {
|
|
||||||
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node");
|
|
||||||
|
|
||||||
final JSONObject owner = comment.getJSONObject("owner");
|
|
||||||
final User user = new User(
|
|
||||||
owner.optLong(Constants.EXTRAS_ID, 0),
|
|
||||||
owner.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
owner.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
|
|
||||||
owner.optBoolean("is_verified"),
|
|
||||||
false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null,
|
|
||||||
null, null);
|
|
||||||
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
|
|
||||||
final String commentId = comment.getString(Constants.EXTRAS_ID);
|
|
||||||
final CommentModel commentModel = new CommentModel(commentId,
|
|
||||||
comment.getString("text"),
|
|
||||||
comment.getLong("created_at"),
|
|
||||||
likedBy != null ? likedBy.optLong("count", 0) : 0,
|
|
||||||
comment.getBoolean("viewer_has_liked"),
|
|
||||||
user);
|
|
||||||
if (i == 0 && !foundEndCursor.contains("tao_cursor"))
|
|
||||||
commentModel.setPageCursor(hasNextPage, TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor);
|
|
||||||
JSONObject tempJsonObject;
|
|
||||||
final JSONArray childCommentsArray;
|
|
||||||
final int childCommentsLen;
|
|
||||||
if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null &&
|
|
||||||
(childCommentsArray = tempJsonObject.optJSONArray("edges")) != null
|
|
||||||
&& (childCommentsLen = childCommentsArray.length()) > 0) {
|
|
||||||
|
|
||||||
final String childEndCursor;
|
|
||||||
final boolean childHasNextPage;
|
|
||||||
if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) {
|
|
||||||
childEndCursor = tempJsonObject.optString("end_cursor");
|
|
||||||
childHasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor));
|
|
||||||
} else {
|
|
||||||
childEndCursor = null;
|
|
||||||
childHasNextPage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<CommentModel> childCommentModels = new ArrayList<>();
|
|
||||||
for (int j = 0; j < childCommentsLen; ++j) {
|
|
||||||
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node");
|
|
||||||
|
|
||||||
tempJsonObject = childComment.getJSONObject("owner");
|
|
||||||
final User childUser = new User(
|
|
||||||
tempJsonObject.optLong(Constants.EXTRAS_ID, 0),
|
|
||||||
tempJsonObject.getString(Constants.EXTRAS_USERNAME),
|
|
||||||
null,
|
|
||||||
false,
|
|
||||||
tempJsonObject.getString("profile_pic_url"),
|
|
||||||
null,
|
|
||||||
new FriendshipStatus(false, false, false, false, false, false, false, false, false, false),
|
|
||||||
tempJsonObject.optBoolean("is_verified"), false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0,
|
|
||||||
null, null, null, null, null, null);
|
|
||||||
|
|
||||||
tempJsonObject = childComment.optJSONObject("edge_liked_by");
|
|
||||||
childCommentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
|
|
||||||
childComment.getString("text"),
|
|
||||||
childComment.getLong("created_at"),
|
|
||||||
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
|
|
||||||
childComment.getBoolean("viewer_has_liked"),
|
|
||||||
childUser));
|
|
||||||
}
|
|
||||||
childCommentModels.get(childCommentsLen - 1).setPageCursor(childHasNextPage, childEndCursor);
|
|
||||||
commentModel.setChildCommentModels(childCommentModels);
|
|
||||||
}
|
|
||||||
commentModels.add(commentModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.disconnect();
|
|
||||||
} catch (final Exception e) {
|
|
||||||
// if (logCollector != null)
|
|
||||||
// logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
|
|
||||||
// new Pair<>("commentModelsList.size", commentModels.size()));
|
|
||||||
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
|
|
||||||
if (fetchListener != null) fetchListener.onFailure(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return commentModels;
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,9 +12,6 @@ import awais.instagrabber.interfaces.FetchListener;
|
|||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
import awais.instagrabber.utils.NetworkUtils;
|
import awais.instagrabber.utils.NetworkUtils;
|
||||||
import awais.instagrabber.utils.ResponseBodyUtils;
|
import awais.instagrabber.utils.ResponseBodyUtils;
|
||||||
//import awaisomereport.LogCollector;
|
|
||||||
|
|
||||||
//import static awais.instagrabber.utils.Utils.logCollector;
|
|
||||||
|
|
||||||
public final class PostFetcher extends AsyncTask<Void, Void, Media> {
|
public final class PostFetcher extends AsyncTask<Void, Void, Media> {
|
||||||
private static final String TAG = "PostFetcher";
|
private static final String TAG = "PostFetcher";
|
||||||
|
@ -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,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();
|
||||||
@ -192,22 +198,25 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initSelf() {
|
private void initSelf() {
|
||||||
|
try {
|
||||||
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
|
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
|
||||||
mediaViewModel.getList().observe(lifeCycleOwner, list -> {
|
} catch (Exception e) {
|
||||||
if (list.size() <= 0) return;
|
Log.e(TAG, "initSelf: ", e);
|
||||||
feedAdapter.submitList(list, () -> {
|
}
|
||||||
|
if (mediaViewModel == null) return;
|
||||||
|
mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
|
||||||
// postDelayed(this::fetchMoreIfPossible, 1000);
|
// postDelayed(this::fetchMoreIfPossible, 1000);
|
||||||
if (!shouldScrollToTop) return;
|
if (!shouldScrollToTop) return;
|
||||||
smoothScrollToPosition(0);
|
|
||||||
shouldScrollToTop = false;
|
shouldScrollToTop = false;
|
||||||
});
|
post(() -> smoothScrollToPosition(0));
|
||||||
});
|
}));
|
||||||
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
postFetcher = new PostFetcher(postFetchService, fetchListener);
|
||||||
if (layoutPreferences.getHasGap()) {
|
if (layoutPreferences.getHasGap()) {
|
||||||
addItemDecoration(gridSpacingItemDecoration);
|
addItemDecoration(gridSpacingItemDecoration);
|
||||||
}
|
}
|
||||||
setHasFixedSize(true);
|
setHasFixedSize(true);
|
||||||
setNestedScrollingEnabled(true);
|
setNestedScrollingEnabled(true);
|
||||||
|
setItemAnimator(null);
|
||||||
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
|
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
|
||||||
if (postFetcher.hasMore()) {
|
if (postFetcher.hasMore()) {
|
||||||
postFetcher.fetch();
|
postFetcher.fetch();
|
||||||
@ -311,11 +320,12 @@ public class PostsRecyclerView extends RecyclerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
|
refresh = true;
|
||||||
if (lazyLoader != null) {
|
if (lazyLoader != null) {
|
||||||
lazyLoader.resetState();
|
lazyLoader.resetState();
|
||||||
}
|
}
|
||||||
if (postFetcher != null) {
|
if (postFetcher != null) {
|
||||||
mediaViewModel.getList().postValue(Collections.emptyList());
|
// mediaViewModel.getList().postValue(Collections.emptyList());
|
||||||
postFetcher.reset();
|
postFetcher.reset();
|
||||||
postFetcher.fetch();
|
postFetcher.fetch();
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,9 @@ public final class ProfilePicView extends CircularImageView {
|
|||||||
case SMALL:
|
case SMALL:
|
||||||
dimenRes = R.dimen.profile_pic_size_small;
|
dimenRes = R.dimen.profile_pic_size_small;
|
||||||
break;
|
break;
|
||||||
|
case SMALLER:
|
||||||
|
dimenRes = R.dimen.profile_pic_size_smaller;
|
||||||
|
break;
|
||||||
case TINY:
|
case TINY:
|
||||||
dimenRes = R.dimen.profile_pic_size_tiny;
|
dimenRes = R.dimen.profile_pic_size_tiny;
|
||||||
break;
|
break;
|
||||||
@ -113,7 +116,8 @@ public final class ProfilePicView extends CircularImageView {
|
|||||||
TINY(0),
|
TINY(0),
|
||||||
SMALL(1),
|
SMALL(1),
|
||||||
REGULAR(2),
|
REGULAR(2),
|
||||||
LARGE(3);
|
LARGE(3),
|
||||||
|
SMALLER(4);
|
||||||
|
|
||||||
private final int value;
|
private final int value;
|
||||||
private static final Map<Integer, Size> map = new HashMap<>();
|
private static final Map<Integer, Size> map = new HashMap<>();
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package awais.instagrabber.customviews;
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.text.InputFilter;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.emoji.widget.EmojiTextViewHelper;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -23,6 +25,8 @@ public class RamboTextViewV2 extends AutoLinkTextView {
|
|||||||
private final List<OnURLClickListener> onURLClickListeners = new ArrayList<>();
|
private final List<OnURLClickListener> onURLClickListeners = new ArrayList<>();
|
||||||
private final List<OnEmailClickListener> onEmailClickListeners = new ArrayList<>();
|
private final List<OnEmailClickListener> onEmailClickListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
private EmojiTextViewHelper emojiTextViewHelper;
|
||||||
|
|
||||||
public RamboTextViewV2(@NonNull final Context context,
|
public RamboTextViewV2(@NonNull final Context context,
|
||||||
@Nullable final AttributeSet attrs) {
|
@Nullable final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
@ -30,6 +34,7 @@ public class RamboTextViewV2 extends AutoLinkTextView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
private void init() {
|
||||||
|
getEmojiTextViewHelper().updateTransformationMethod();
|
||||||
addAutoLinkMode(MODE_HASHTAG.INSTANCE, MODE_MENTION.INSTANCE, MODE_EMAIL.INSTANCE, MODE_URL.INSTANCE);
|
addAutoLinkMode(MODE_HASHTAG.INSTANCE, MODE_MENTION.INSTANCE, MODE_EMAIL.INSTANCE, MODE_URL.INSTANCE);
|
||||||
onAutoLinkClick(autoLinkItem -> {
|
onAutoLinkClick(autoLinkItem -> {
|
||||||
final Mode mode = autoLinkItem.getMode();
|
final Mode mode = autoLinkItem.getMode();
|
||||||
@ -57,6 +62,26 @@ public class RamboTextViewV2 extends AutoLinkTextView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
onAutoLinkLongClick(autoLinkItem -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFilters(InputFilter[] filters) {
|
||||||
|
super.setFilters(getEmojiTextViewHelper().getFilters(filters));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAllCaps(boolean allCaps) {
|
||||||
|
super.setAllCaps(allCaps);
|
||||||
|
getEmojiTextViewHelper().setAllCaps(allCaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private EmojiTextViewHelper getEmojiTextViewHelper() {
|
||||||
|
if (emojiTextViewHelper == null) {
|
||||||
|
emojiTextViewHelper = new EmojiTextViewHelper(this);
|
||||||
|
}
|
||||||
|
return emojiTextViewHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addOnMentionClickListener(final OnMentionClickListener onMentionClickListener) {
|
public void addOnMentionClickListener(final OnMentionClickListener onMentionClickListener) {
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.emoji.widget.EmojiAppCompatTextView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://stackoverflow.com/a/31916731
|
||||||
|
*/
|
||||||
|
public class TextViewDrawableSize extends EmojiAppCompatTextView {
|
||||||
|
|
||||||
|
private int mDrawableWidth;
|
||||||
|
private int mDrawableHeight;
|
||||||
|
private boolean calledFromInit = false;
|
||||||
|
|
||||||
|
public TextViewDrawableSize(final Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextViewDrawableSize(final Context context, final AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextViewDrawableSize(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(@NonNull final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
final TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextViewDrawableSize, defStyleAttr, 0);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mDrawableWidth = array.getDimensionPixelSize(R.styleable.TextViewDrawableSize_compoundDrawableWidth, -1);
|
||||||
|
mDrawableHeight = array.getDimensionPixelSize(R.styleable.TextViewDrawableSize_compoundDrawableHeight, -1);
|
||||||
|
} finally {
|
||||||
|
array.recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mDrawableWidth > 0 || mDrawableHeight > 0) {
|
||||||
|
initCompoundDrawableSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initCompoundDrawableSize() {
|
||||||
|
final Drawable[] drawables = getCompoundDrawablesRelative();
|
||||||
|
for (Drawable drawable : drawables) {
|
||||||
|
if (drawable == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Rect realBounds = drawable.getBounds();
|
||||||
|
float scaleFactor = drawable.getIntrinsicHeight() / (float) drawable.getIntrinsicWidth();
|
||||||
|
|
||||||
|
float drawableWidth = drawable.getIntrinsicWidth();
|
||||||
|
float drawableHeight = drawable.getIntrinsicHeight();
|
||||||
|
|
||||||
|
if (mDrawableWidth > 0) {
|
||||||
|
// save scale factor of image
|
||||||
|
if (drawableWidth > mDrawableWidth) {
|
||||||
|
drawableWidth = mDrawableWidth;
|
||||||
|
drawableHeight = drawableWidth * scaleFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mDrawableHeight > 0) {
|
||||||
|
// save scale factor of image
|
||||||
|
if (drawableHeight > mDrawableHeight) {
|
||||||
|
drawableHeight = mDrawableHeight;
|
||||||
|
drawableWidth = drawableHeight / scaleFactor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realBounds.right = realBounds.left + Math.round(drawableWidth);
|
||||||
|
realBounds.bottom = realBounds.top + Math.round(drawableHeight);
|
||||||
|
|
||||||
|
drawable.setBounds(realBounds);
|
||||||
|
}
|
||||||
|
setCompoundDrawablesRelative(drawables[0], drawables[1], drawables[2], drawables[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCompoundDrawablesRelativeWithSize(@Nullable final Drawable start,
|
||||||
|
@Nullable final Drawable top,
|
||||||
|
@Nullable final Drawable end,
|
||||||
|
@Nullable final Drawable bottom) {
|
||||||
|
setCompoundDrawablesRelative(start, top, end, bottom);
|
||||||
|
initCompoundDrawableSize();
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package awais.instagrabber.customviews;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
|
import android.text.Spanned;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.content.res.AppCompatResources;
|
||||||
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
|
||||||
|
import awais.instagrabber.R;
|
||||||
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
|
public class UsernameTextView extends AppCompatTextView {
|
||||||
|
private static final String TAG = UsernameTextView.class.getSimpleName();
|
||||||
|
|
||||||
|
private final int drawableSize = Utils.convertDpToPx(24);
|
||||||
|
|
||||||
|
private boolean verified;
|
||||||
|
private VerticalImageSpan verifiedSpan;
|
||||||
|
|
||||||
|
public UsernameTextView(@NonNull final Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsernameTextView(@NonNull final Context context, @Nullable final AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UsernameTextView(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
try {
|
||||||
|
final Drawable verifiedDrawable = AppCompatResources.getDrawable(getContext(), R.drawable.verified);
|
||||||
|
final Drawable drawable = verifiedDrawable.mutate();
|
||||||
|
drawable.setBounds(0, 0, drawableSize, drawableSize);
|
||||||
|
verifiedSpan = new VerticalImageSpan(drawable);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "init: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(final CharSequence username) {
|
||||||
|
setUsername(username, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(final CharSequence username, final boolean verified) {
|
||||||
|
this.verified = verified;
|
||||||
|
final SpannableStringBuilder sb = new SpannableStringBuilder(username);
|
||||||
|
if (verified) {
|
||||||
|
try {
|
||||||
|
if (verifiedSpan != null) {
|
||||||
|
sb.append(" ");
|
||||||
|
sb.setSpan(verifiedSpan, sb.length() - 1, sb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "bind: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.setText(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isVerified() {
|
||||||
|
return verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerified(final boolean verified) {
|
||||||
|
setUsername(getText(), verified);
|
||||||
|
}
|
||||||
|
}
|
@ -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,123 +1,70 @@
|
|||||||
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.Handler;
|
|
||||||
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.view.ContextThemeWrapper;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
import androidx.appcompat.widget.PopupMenu;
|
|
||||||
|
|
||||||
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.controller.BaseControllerListener;
|
import com.facebook.drawee.controller.BaseControllerListener;
|
||||||
import com.facebook.drawee.interfaces.DraweeController;
|
|
||||||
import com.facebook.imagepipeline.image.ImageInfo;
|
import com.facebook.imagepipeline.image.ImageInfo;
|
||||||
import com.facebook.imagepipeline.request.ImageRequest;
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
import com.facebook.imagepipeline.request.ImageRequestBuilder;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.PlaybackParameters;
|
|
||||||
import com.google.android.exoplayer2.Player;
|
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.slider.LabelFormatter;
|
|
||||||
import com.google.android.material.slider.Slider;
|
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.LayoutExoCustomControlsBinding;
|
|
||||||
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
import awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.C.TIME_UNSET;
|
|
||||||
import static com.google.android.exoplayer2.Player.STATE_ENDED;
|
|
||||||
import static com.google.android.exoplayer2.Player.STATE_IDLE;
|
|
||||||
import static com.google.android.exoplayer2.Player.STATE_READY;
|
|
||||||
|
|
||||||
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 awais.instagrabber.databinding.LayoutVideoPlayerWithThumbnailBinding binding;
|
private final LayoutVideoPlayerWithThumbnailBinding binding;
|
||||||
private final float initialVolume;
|
private final float initialVolume;
|
||||||
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 awais.instagrabber.databinding.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 final View.OnClickListener rewOnClickListener = v -> {
|
private Object layoutManager;
|
||||||
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,
|
||||||
@ -126,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;
|
||||||
@ -134,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");
|
||||||
@ -151,14 +96,16 @@ public class VideoPlayerViewHelper implements Player.EventListener {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
setThumbnail();
|
setThumbnail();
|
||||||
setupControls();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setThumbnail() {
|
private void setThumbnail() {
|
||||||
binding.thumbnail.setAspectRatio(thumbnailAspectRatio);
|
binding.thumbnail.setAspectRatio(thumbnailAspectRatio);
|
||||||
final ImageRequest thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl))
|
ImageRequest thumbnailRequest = null;
|
||||||
.build();
|
if (thumbnailUrl != null) {
|
||||||
final DraweeController controller = Fresco.newDraweeControllerBuilder()
|
thumbnailRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(thumbnailUrl)).build();
|
||||||
|
}
|
||||||
|
final PipelineDraweeControllerBuilder builder = Fresco
|
||||||
|
.newDraweeControllerBuilder()
|
||||||
.setControllerListener(new BaseControllerListener<ImageInfo>() {
|
.setControllerListener(new BaseControllerListener<ImageInfo>() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(final String id, final Throwable throwable) {
|
public void onFailure(final String id, final Throwable throwable) {
|
||||||
@ -175,16 +122,17 @@ public class VideoPlayerViewHelper implements Player.EventListener {
|
|||||||
videoPlayerCallback.onThumbnailLoaded();
|
videoPlayerCallback.onThumbnailLoaded();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
.setImageRequest(thumbnailRequest)
|
if (thumbnailRequest != null) {
|
||||||
.build();
|
builder.setImageRequest(thumbnailRequest);
|
||||||
binding.thumbnail.setController(controller);
|
}
|
||||||
|
binding.thumbnail.setController(builder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@ -193,15 +141,15 @@ 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.setVolume(initialVolume);
|
player.setVolume(initialVolume);
|
||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
player.setRepeatMode(Player.REPEAT_MODE_ALL);
|
||||||
@ -209,123 +157,116 @@ 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.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FIT);
|
||||||
|
binding.playerView.setShowNextButton(false);
|
||||||
|
binding.playerView.setShowPreviousButton(false);
|
||||||
|
binding.playerView.setControllerOnFullScreenModeChangedListener(isFullScreen -> {
|
||||||
|
if (videoPlayerCallback == null) return;
|
||||||
|
videoPlayerCallback.onFullScreenModeChanged(isFullScreen, binding.playerView);
|
||||||
|
});
|
||||||
|
setupControllerView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupControls() {
|
private void setupControllerView() {
|
||||||
if (controlsBinding == null) return;
|
try {
|
||||||
binding.playerView.setUseController(false);
|
final StyledPlayerControlView controllerView = getStyledPlayerControlView();
|
||||||
if (player == null) {
|
if (controllerView == null) return;
|
||||||
enableControls(false);
|
layoutManager = setControlViewLayoutManager(controllerView);
|
||||||
// controlsBinding.playPause.setEnabled(true);
|
if (videoPlayerCallback != null && videoPlayerCallback.isInFullScreen()) {
|
||||||
// controlsBinding.playPause.setOnClickListener(new NoPlayerPlayPauseClickListener(binding.thumbnailParent));
|
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());
|
||||||
|
exoBasicControls.addView(mute, 0);
|
||||||
|
mute.setOnClickListener(muteOnClickListener);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "loadPlayer: ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Object setControlViewLayoutManager(@NonNull final StyledPlayerControlView controllerView)
|
||||||
|
throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
final Field controlViewLayoutManagerField = controllerView.getClass().getDeclaredField("controlViewLayoutManager");
|
||||||
|
controlViewLayoutManagerField.setAccessible(true);
|
||||||
|
return controlViewLayoutManagerField.get(controllerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setControllerViewToFullScreenMode(@NonNull final StyledPlayerControlView controllerView)
|
||||||
|
throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
|
||||||
|
// Exoplayer doesn't expose the fullscreen state, so using reflection
|
||||||
|
final Field fullScreenButtonField = controllerView.getClass().getDeclaredField("fullScreenButton");
|
||||||
|
fullScreenButtonField.setAccessible(true);
|
||||||
|
final ImageView fullScreenButton = (ImageView) fullScreenButtonField.get(controllerView);
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private StyledPlayerControlView getStyledPlayerControlView() throws NoSuchFieldException, IllegalAccessException {
|
||||||
|
final Field controller = binding.playerView.getClass().getDeclaredField("controller");
|
||||||
|
controller.setAccessible(true);
|
||||||
|
return (StyledPlayerControlView) controller.get(binding.playerView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTracksChanged(@NonNull TrackGroupArray trackGroups, @NonNull TrackSelectionArray trackSelections) {
|
||||||
|
if (trackGroups.isEmpty()) {
|
||||||
|
setHasAudio(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
enableControls(true);
|
boolean hasAudio = false;
|
||||||
updatePlayPauseDrawable(player.getPlayWhenReady());
|
for (int i = 0; i < trackGroups.length; i++) {
|
||||||
updateMuteIcon(player.getVolume());
|
for (int g = 0; g < trackGroups.get(i).length; g++) {
|
||||||
player.addListener(listener);
|
final String sampleMimeType = trackGroups.get(i).getFormat(g).sampleMimeType;
|
||||||
player.addAudioListener(audioListener);
|
if (sampleMimeType != null && sampleMimeType.contains("audio")) {
|
||||||
controlsBinding.timeline.addOnChangeListener(onChangeListener);
|
hasAudio = true;
|
||||||
controlsBinding.timeline.setLabelFormatter(labelFormatter);
|
break;
|
||||||
controlsBinding.playPause.setOnClickListener(onClickListener);
|
}
|
||||||
controlsBinding.mute.setOnClickListener(muteOnClickListener);
|
}
|
||||||
controlsBinding.rewWithAmount.setOnClickListener(rewOnClickListener);
|
}
|
||||||
controlsBinding.ffWithAmount.setOnClickListener(ffOnClickListener);
|
setHasAudio(hasAudio);
|
||||||
controlsBinding.speed.setOnClickListener(showMenu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTimeline() {
|
private void setHasAudio(final boolean hasAudio) {
|
||||||
final long duration = player.getDuration();
|
if (mute == null) return;
|
||||||
controlsBinding.timeline.setEnabled(true);
|
mute.setEnabled(hasAudio);
|
||||||
controlsBinding.timeline.setValueFrom(0);
|
mute.setAlpha(hasAudio ? 1f : 0.5f);
|
||||||
controlsBinding.timeline.setValueTo(duration);
|
updateMuteIcon(hasAudio ? 1f : 0f);
|
||||||
controlsBinding.fromTime.setText(TextUtils.millisToTimeString(0));
|
|
||||||
controlsBinding.toTime.setText(TextUtils.millisToTimeString(duration));
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
PopupMenu popup = getPopupMenu(anchor);
|
|
||||||
popup.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
private PopupMenu getPopupMenu(final View anchor) {
|
|
||||||
if (speedPopup != null) {
|
|
||||||
return speedPopup;
|
|
||||||
}
|
|
||||||
final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.popupMenuStyle);
|
|
||||||
// final ContextThemeWrapper themeWrapper = new ContextThemeWrapper(context, R.style.Widget_MaterialComponents_PopupMenu_Exoplayer);
|
|
||||||
speedPopup = new PopupMenu(themeWrapper, anchor);
|
|
||||||
speedPopup.getMenuInflater().inflate(R.menu.speed_menu, speedPopup.getMenu());
|
|
||||||
speedPopup.setOnMenuItemClickListener(item -> {
|
|
||||||
float nextSpeed;
|
|
||||||
int textResId;
|
|
||||||
int itemId = item.getItemId();
|
|
||||||
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) {
|
|
||||||
nextSpeed = 0.5f;
|
|
||||||
textResId = R.string.pt_five_x;
|
|
||||||
} else if (itemId == R.id.pt_seven_five_x) {
|
|
||||||
nextSpeed = 0.75f;
|
|
||||||
textResId = R.string.pt_seven_five_x;
|
|
||||||
} else if (itemId == R.id.one_x) {
|
|
||||||
nextSpeed = 1f;
|
|
||||||
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 (volume == 0) {
|
if (volume == 0) {
|
||||||
controlsBinding.mute.setIconResource(R.drawable.ic_volume_off_24_states);
|
mute.setImageResource(R.drawable.ic_volume_off_24);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
controlsBinding.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
|
||||||
@ -339,25 +280,24 @@ public class VideoPlayerViewHelper implements Player.EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(final ExoPlaybackException error) {
|
public void onPlayerError(@NonNull final ExoPlaybackException error) {
|
||||||
Log.e(TAG, "onPlayerError", error);
|
Log.e(TAG, "onPlayerError", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float toggleMute() {
|
private void toggleMute() {
|
||||||
if (player == null) return 0;
|
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);
|
||||||
return 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();
|
||||||
@ -366,84 +306,12 @@ 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 {
|
||||||
@ -458,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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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) {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,14 +22,16 @@ import java.util.List;
|
|||||||
import awais.instagrabber.db.dao.AccountDao;
|
import awais.instagrabber.db.dao.AccountDao;
|
||||||
import awais.instagrabber.db.dao.DMLastNotifiedDao;
|
import awais.instagrabber.db.dao.DMLastNotifiedDao;
|
||||||
import awais.instagrabber.db.dao.FavoriteDao;
|
import awais.instagrabber.db.dao.FavoriteDao;
|
||||||
|
import awais.instagrabber.db.dao.RecentSearchDao;
|
||||||
import awais.instagrabber.db.entities.Account;
|
import awais.instagrabber.db.entities.Account;
|
||||||
import awais.instagrabber.db.entities.DMLastNotified;
|
import awais.instagrabber.db.entities.DMLastNotified;
|
||||||
import awais.instagrabber.db.entities.Favorite;
|
import awais.instagrabber.db.entities.Favorite;
|
||||||
|
import awais.instagrabber.db.entities.RecentSearch;
|
||||||
import awais.instagrabber.models.enums.FavoriteType;
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
@Database(entities = {Account.class, Favorite.class, DMLastNotified.class},
|
@Database(entities = {Account.class, Favorite.class, DMLastNotified.class, RecentSearch.class},
|
||||||
version = 5)
|
version = 6)
|
||||||
@TypeConverters({Converters.class})
|
@TypeConverters({Converters.class})
|
||||||
public abstract class AppDatabase extends RoomDatabase {
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
private static final String TAG = AppDatabase.class.getSimpleName();
|
private static final String TAG = AppDatabase.class.getSimpleName();
|
||||||
@ -42,12 +44,14 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
|
|
||||||
public abstract DMLastNotifiedDao dmLastNotifiedDao();
|
public abstract DMLastNotifiedDao dmLastNotifiedDao();
|
||||||
|
|
||||||
|
public abstract RecentSearchDao recentSearchDao();
|
||||||
|
|
||||||
public static AppDatabase getDatabase(final Context context) {
|
public static AppDatabase getDatabase(final Context context) {
|
||||||
if (INSTANCE == null) {
|
if (INSTANCE == null) {
|
||||||
synchronized (AppDatabase.class) {
|
synchronized (AppDatabase.class) {
|
||||||
if (INSTANCE == null) {
|
if (INSTANCE == null) {
|
||||||
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
|
INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "cookiebox.db")
|
||||||
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,6 +160,21 @@ public abstract class AppDatabase extends RoomDatabase {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static final Migration MIGRATION_5_6 = new Migration(5, 6) {
|
||||||
|
@Override
|
||||||
|
public void migrate(@NonNull final SupportSQLiteDatabase database) {
|
||||||
|
database.execSQL("CREATE TABLE IF NOT EXISTS `recent_searches` (" +
|
||||||
|
"`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
|
||||||
|
"`ig_id` TEXT NOT NULL, " +
|
||||||
|
"`name` TEXT NOT NULL, " +
|
||||||
|
"`username` TEXT, " +
|
||||||
|
"`pic_url` TEXT, " +
|
||||||
|
"`type` TEXT NOT NULL, " +
|
||||||
|
"`last_searched_on` INTEGER NOT NULL)");
|
||||||
|
database.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS `index_recent_searches_ig_id_type` ON `recent_searches` (`ig_id`, `type`)");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static List<Favorite> backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
|
private static List<Favorite> backupOldFavorites(@NonNull final SupportSQLiteDatabase db) {
|
||||||
// check if old favorites table had the column query_display
|
// check if old favorites table had the column query_display
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package awais.instagrabber.db.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Delete;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import androidx.room.Update;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.db.entities.RecentSearch;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface RecentSearchDao {
|
||||||
|
|
||||||
|
@Query("SELECT * FROM recent_searches ORDER BY last_searched_on DESC")
|
||||||
|
List<RecentSearch> getAllRecentSearches();
|
||||||
|
|
||||||
|
@Query("SELECT * FROM recent_searches WHERE `ig_id` = :igId AND `type` = :type")
|
||||||
|
RecentSearch getRecentSearchByIgIdAndType(String igId, FavoriteType type);
|
||||||
|
|
||||||
|
@Query("SELECT * FROM recent_searches WHERE instr(`name`, :query) > 0")
|
||||||
|
List<RecentSearch> findRecentSearchesWithNameContaining(String query);
|
||||||
|
|
||||||
|
@Insert
|
||||||
|
Long insertRecentSearch(RecentSearch recentSearch);
|
||||||
|
|
||||||
|
@Update
|
||||||
|
void updateRecentSearch(RecentSearch recentSearch);
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
void deleteRecentSearch(RecentSearch recentSearch);
|
||||||
|
|
||||||
|
// @Query("DELETE from recent_searches")
|
||||||
|
// void deleteAllRecentSearches();
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package awais.instagrabber.db.datasources;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.db.AppDatabase;
|
||||||
|
import awais.instagrabber.db.dao.RecentSearchDao;
|
||||||
|
import awais.instagrabber.db.entities.RecentSearch;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
|
||||||
|
public class RecentSearchDataSource {
|
||||||
|
private static final String TAG = RecentSearchDataSource.class.getSimpleName();
|
||||||
|
|
||||||
|
private static RecentSearchDataSource INSTANCE;
|
||||||
|
|
||||||
|
private final RecentSearchDao recentSearchDao;
|
||||||
|
|
||||||
|
private RecentSearchDataSource(final RecentSearchDao recentSearchDao) {
|
||||||
|
this.recentSearchDao = recentSearchDao;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized RecentSearchDataSource getInstance(@NonNull Context context) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
synchronized (RecentSearchDataSource.class) {
|
||||||
|
if (INSTANCE == null) {
|
||||||
|
final AppDatabase database = AppDatabase.getDatabase(context);
|
||||||
|
INSTANCE = new RecentSearchDataSource(database.recentSearchDao());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecentSearch getRecentSearchByIgIdAndType(@NonNull final String igId, @NonNull final FavoriteType type) {
|
||||||
|
return recentSearchDao.getRecentSearchByIgIdAndType(igId, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public final List<RecentSearch> getAllRecentSearches() {
|
||||||
|
return recentSearchDao.getAllRecentSearches();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void insertOrUpdateRecentSearch(@NonNull final RecentSearch recentSearch) {
|
||||||
|
if (recentSearch.getId() != 0) {
|
||||||
|
recentSearchDao.updateRecentSearch(recentSearch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
recentSearchDao.insertRecentSearch(recentSearch);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void deleteRecentSearch(@NonNull final RecentSearch recentSearch) {
|
||||||
|
recentSearchDao.deleteRecentSearch(recentSearch);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
package awais.instagrabber.db.entities;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.room.ColumnInfo;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.Ignore;
|
||||||
|
import androidx.room.Index;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
import awais.instagrabber.repositories.responses.search.SearchItem;
|
||||||
|
|
||||||
|
@Entity(tableName = RecentSearch.TABLE_NAME, indices = {@Index(value = {RecentSearch.COL_IG_ID, RecentSearch.COL_TYPE}, unique = true)})
|
||||||
|
public class RecentSearch {
|
||||||
|
private static final String TAG = RecentSearch.class.getSimpleName();
|
||||||
|
|
||||||
|
public static final String TABLE_NAME = "recent_searches";
|
||||||
|
private static final String COL_ID = "id";
|
||||||
|
public static final String COL_IG_ID = "ig_id";
|
||||||
|
private static final String COL_NAME = "name";
|
||||||
|
private static final String COL_USERNAME = "username";
|
||||||
|
private static final String COL_PIC_URL = "pic_url";
|
||||||
|
public static final String COL_TYPE = "type";
|
||||||
|
private static final String COL_LAST_SEARCHED_ON = "last_searched_on";
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = COL_ID)
|
||||||
|
private final int id;
|
||||||
|
|
||||||
|
@ColumnInfo(name = COL_IG_ID)
|
||||||
|
@NonNull
|
||||||
|
private final String igId;
|
||||||
|
|
||||||
|
@ColumnInfo(name = COL_NAME)
|
||||||
|
@NonNull
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
@ColumnInfo(name = COL_USERNAME)
|
||||||
|
private final String username;
|
||||||
|
|
||||||
|
@ColumnInfo(name = COL_PIC_URL)
|
||||||
|
private final String picUrl;
|
||||||
|
|
||||||
|
@ColumnInfo(name = COL_TYPE)
|
||||||
|
@NonNull
|
||||||
|
private final FavoriteType type;
|
||||||
|
|
||||||
|
@ColumnInfo(name = COL_LAST_SEARCHED_ON)
|
||||||
|
@NonNull
|
||||||
|
private final LocalDateTime lastSearchedOn;
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public RecentSearch(final String igId,
|
||||||
|
final String name,
|
||||||
|
final String username,
|
||||||
|
final String picUrl,
|
||||||
|
final FavoriteType type,
|
||||||
|
final LocalDateTime lastSearchedOn) {
|
||||||
|
this(0, igId, name, username, picUrl, type, lastSearchedOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RecentSearch(final int id,
|
||||||
|
@NonNull final String igId,
|
||||||
|
@NonNull final String name,
|
||||||
|
final String username,
|
||||||
|
final String picUrl,
|
||||||
|
@NonNull final FavoriteType type,
|
||||||
|
@NonNull final LocalDateTime lastSearchedOn) {
|
||||||
|
this.id = id;
|
||||||
|
this.igId = igId;
|
||||||
|
this.name = name;
|
||||||
|
this.username = username;
|
||||||
|
this.picUrl = picUrl;
|
||||||
|
this.type = type;
|
||||||
|
this.lastSearchedOn = lastSearchedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getIgId() {
|
||||||
|
return igId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPicUrl() {
|
||||||
|
return picUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public FavoriteType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public LocalDateTime getLastSearchedOn() {
|
||||||
|
return lastSearchedOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final RecentSearch that = (RecentSearch) o;
|
||||||
|
return Objects.equals(igId, that.igId) &&
|
||||||
|
Objects.equals(name, that.name) &&
|
||||||
|
Objects.equals(username, that.username) &&
|
||||||
|
Objects.equals(picUrl, that.picUrl) &&
|
||||||
|
type == that.type &&
|
||||||
|
Objects.equals(lastSearchedOn, that.lastSearchedOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(igId, name, username, picUrl, type, lastSearchedOn);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RecentSearch{" +
|
||||||
|
"id=" + id +
|
||||||
|
", igId='" + igId + '\'' +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
", username='" + username + '\'' +
|
||||||
|
", picUrl='" + picUrl + '\'' +
|
||||||
|
", type=" + type +
|
||||||
|
", lastSearchedOn=" + lastSearchedOn +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static RecentSearch fromSearchItem(@NonNull final SearchItem searchItem) {
|
||||||
|
final FavoriteType type = searchItem.getType();
|
||||||
|
if (type == null) return null;
|
||||||
|
try {
|
||||||
|
final String igId;
|
||||||
|
final String name;
|
||||||
|
final String username;
|
||||||
|
final String picUrl;
|
||||||
|
switch (type) {
|
||||||
|
case USER:
|
||||||
|
igId = String.valueOf(searchItem.getUser().getPk());
|
||||||
|
name = searchItem.getUser().getFullName();
|
||||||
|
username = searchItem.getUser().getUsername();
|
||||||
|
picUrl = searchItem.getUser().getProfilePicUrl();
|
||||||
|
break;
|
||||||
|
case HASHTAG:
|
||||||
|
igId = searchItem.getHashtag().getId();
|
||||||
|
name = searchItem.getHashtag().getName();
|
||||||
|
username = null;
|
||||||
|
picUrl = null;
|
||||||
|
break;
|
||||||
|
case LOCATION:
|
||||||
|
igId = String.valueOf(searchItem.getPlace().getLocation().getPk());
|
||||||
|
name = searchItem.getPlace().getTitle();
|
||||||
|
username = null;
|
||||||
|
picUrl = null;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new RecentSearch(igId, name, username, picUrl, type, LocalDateTime.now());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "fromSearchItem: ", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package awais.instagrabber.db.repositories;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import awais.instagrabber.db.datasources.RecentSearchDataSource;
|
||||||
|
import awais.instagrabber.db.entities.RecentSearch;
|
||||||
|
import awais.instagrabber.models.enums.FavoriteType;
|
||||||
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
|
|
||||||
|
public class RecentSearchRepository {
|
||||||
|
private static final String TAG = RecentSearchRepository.class.getSimpleName();
|
||||||
|
|
||||||
|
private static RecentSearchRepository instance;
|
||||||
|
|
||||||
|
private final AppExecutors appExecutors;
|
||||||
|
private final RecentSearchDataSource recentSearchDataSource;
|
||||||
|
|
||||||
|
private RecentSearchRepository(final AppExecutors appExecutors, final RecentSearchDataSource recentSearchDataSource) {
|
||||||
|
this.appExecutors = appExecutors;
|
||||||
|
this.recentSearchDataSource = recentSearchDataSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RecentSearchRepository getInstance(final RecentSearchDataSource recentSearchDataSource) {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new RecentSearchRepository(AppExecutors.getInstance(), recentSearchDataSource);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getRecentSearch(@NonNull final String igId,
|
||||||
|
@NonNull final FavoriteType type,
|
||||||
|
final RepositoryCallback<RecentSearch> callback) {
|
||||||
|
// request on the I/O thread
|
||||||
|
appExecutors.diskIO().execute(() -> {
|
||||||
|
final RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type);
|
||||||
|
// notify on the main thread
|
||||||
|
appExecutors.mainThread().execute(() -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
if (recentSearch == null) {
|
||||||
|
callback.onDataNotAvailable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onSuccess(recentSearch);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void getAllRecentSearches(final RepositoryCallback<List<RecentSearch>> callback) {
|
||||||
|
// request on the I/O thread
|
||||||
|
appExecutors.diskIO().execute(() -> {
|
||||||
|
final List<RecentSearch> recentSearches = recentSearchDataSource.getAllRecentSearches();
|
||||||
|
// notify on the main thread
|
||||||
|
appExecutors.mainThread().execute(() -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
callback.onSuccess(recentSearches);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertOrUpdateRecentSearch(@NonNull final RecentSearch recentSearch,
|
||||||
|
final RepositoryCallback<Void> callback) {
|
||||||
|
insertOrUpdateRecentSearch(recentSearch.getIgId(), recentSearch.getName(), recentSearch.getUsername(), recentSearch.getPicUrl(),
|
||||||
|
recentSearch.getType(), callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insertOrUpdateRecentSearch(@NonNull final String igId,
|
||||||
|
@NonNull final String name,
|
||||||
|
final String username,
|
||||||
|
final String picUrl,
|
||||||
|
@NonNull final FavoriteType type,
|
||||||
|
final RepositoryCallback<Void> callback) {
|
||||||
|
// request on the I/O thread
|
||||||
|
appExecutors.diskIO().execute(() -> {
|
||||||
|
RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type);
|
||||||
|
recentSearch = recentSearch == null
|
||||||
|
? new RecentSearch(igId, name, username, picUrl, type, LocalDateTime.now())
|
||||||
|
: new RecentSearch(recentSearch.getId(), igId, name, username, picUrl, type, LocalDateTime.now());
|
||||||
|
recentSearchDataSource.insertOrUpdateRecentSearch(recentSearch);
|
||||||
|
// notify on the main thread
|
||||||
|
appExecutors.mainThread().execute(() -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
callback.onSuccess(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteRecentSearchByIgIdAndType(@NonNull final String igId,
|
||||||
|
@NonNull final FavoriteType type,
|
||||||
|
final RepositoryCallback<Void> callback) {
|
||||||
|
// request on the I/O thread
|
||||||
|
appExecutors.diskIO().execute(() -> {
|
||||||
|
final RecentSearch recentSearch = recentSearchDataSource.getRecentSearchByIgIdAndType(igId, type);
|
||||||
|
if (recentSearch != null) {
|
||||||
|
recentSearchDataSource.deleteRecentSearch(recentSearch);
|
||||||
|
}
|
||||||
|
// notify on the main thread
|
||||||
|
appExecutors.mainThread().execute(() -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
if (recentSearch == null) {
|
||||||
|
callback.onDataNotAvailable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
callback.onSuccess(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteRecentSearch(@NonNull final RecentSearch recentSearch,
|
||||||
|
final RepositoryCallback<Void> callback) {
|
||||||
|
// request on the I/O thread
|
||||||
|
appExecutors.diskIO().execute(() -> {
|
||||||
|
|
||||||
|
recentSearchDataSource.deleteRecentSearch(recentSearch);
|
||||||
|
// notify on the main thread
|
||||||
|
appExecutors.mainThread().execute(() -> {
|
||||||
|
if (callback == null) return;
|
||||||
|
callback.onSuccess(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -25,8 +24,10 @@ import awais.instagrabber.db.datasources.AccountDataSource;
|
|||||||
import awais.instagrabber.db.entities.Account;
|
import awais.instagrabber.db.entities.Account;
|
||||||
import awais.instagrabber.db.repositories.AccountRepository;
|
import awais.instagrabber.db.repositories.AccountRepository;
|
||||||
import awais.instagrabber.db.repositories.RepositoryCallback;
|
import awais.instagrabber.db.repositories.RepositoryCallback;
|
||||||
|
import awais.instagrabber.utils.AppExecutors;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
|
import awais.instagrabber.utils.ProcessPhoenix;
|
||||||
import awais.instagrabber.utils.TextUtils;
|
import awais.instagrabber.utils.TextUtils;
|
||||||
import awais.instagrabber.utils.Utils;
|
import awais.instagrabber.utils.Utils;
|
||||||
|
|
||||||
@ -55,9 +56,14 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
|
|||||||
}
|
}
|
||||||
CookieUtils.setupCookies(model.getCookie());
|
CookieUtils.setupCookies(model.getCookie());
|
||||||
settingsHelper.putString(Constants.COOKIE, model.getCookie());
|
settingsHelper.putString(Constants.COOKIE, model.getCookie());
|
||||||
final FragmentActivity activity = getActivity();
|
// final FragmentActivity activity = getActivity();
|
||||||
if (activity != null) activity.recreate();
|
// if (activity != null) activity.recreate();
|
||||||
dismiss();
|
// dismiss();
|
||||||
|
AppExecutors.getInstance().mainThread().execute(() -> {
|
||||||
|
final Context context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
ProcessPhoenix.triggerRebirth(context);
|
||||||
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> {
|
private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -1,487 +0,0 @@
|
|||||||
package awais.instagrabber.fragments;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextWatcher;
|
|
||||||
import android.text.style.RelativeSizeSpan;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.LinearLayoutCompat;
|
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
|
||||||
import androidx.navigation.NavController;
|
|
||||||
import androidx.navigation.NavDirections;
|
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
|
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import awais.instagrabber.BuildConfig;
|
|
||||||
import awais.instagrabber.R;
|
|
||||||
import awais.instagrabber.adapters.CommentsAdapter;
|
|
||||||
import awais.instagrabber.asyncs.CommentsFetcher;
|
|
||||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
|
|
||||||
import awais.instagrabber.databinding.FragmentCommentsBinding;
|
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
|
||||||
import awais.instagrabber.models.CommentModel;
|
|
||||||
import awais.instagrabber.repositories.responses.User;
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
|
||||||
import awais.instagrabber.utils.TextUtils;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
import awais.instagrabber.viewmodels.CommentsViewModel;
|
|
||||||
import awais.instagrabber.webservices.MediaService;
|
|
||||||
import awais.instagrabber.webservices.ServiceCallback;
|
|
||||||
|
|
||||||
import static android.content.Context.INPUT_METHOD_SERVICE;
|
|
||||||
|
|
||||||
public final class CommentsViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener {
|
|
||||||
private static final String TAG = "CommentsViewerFragment";
|
|
||||||
|
|
||||||
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
|
|
||||||
|
|
||||||
private CommentsAdapter commentsAdapter;
|
|
||||||
private FragmentCommentsBinding binding;
|
|
||||||
private LinearLayoutManager layoutManager;
|
|
||||||
private RecyclerLazyLoader lazyLoader;
|
|
||||||
private String shortCode;
|
|
||||||
private long authorUserId, userIdFromCookie;
|
|
||||||
private String endCursor = null;
|
|
||||||
private Resources resources;
|
|
||||||
private InputMethodManager imm;
|
|
||||||
private AppCompatActivity fragmentActivity;
|
|
||||||
private LinearLayoutCompat root;
|
|
||||||
private boolean shouldRefresh = true, hasNextPage = false;
|
|
||||||
private MediaService mediaService;
|
|
||||||
private String postId;
|
|
||||||
private AsyncTask<Void, Void, List<CommentModel>> currentlyRunning;
|
|
||||||
private CommentsViewModel commentsViewModel;
|
|
||||||
|
|
||||||
private final FetchListener<List<CommentModel>> fetchListener = new FetchListener<List<CommentModel>>() {
|
|
||||||
@Override
|
|
||||||
public void doBefore() {
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResult(final List<CommentModel> commentModels) {
|
|
||||||
if (commentModels != null && commentModels.size() > 0) {
|
|
||||||
endCursor = commentModels.get(0).getEndCursor();
|
|
||||||
hasNextPage = commentModels.get(0).hasNextPage();
|
|
||||||
List<CommentModel> list = commentsViewModel.getList().getValue();
|
|
||||||
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
|
|
||||||
// final int oldSize = list != null ? list.size() : 0;
|
|
||||||
list.addAll(commentModels);
|
|
||||||
commentsViewModel.getList().postValue(list);
|
|
||||||
}
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
stopCurrentExecutor(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Throwable t) {
|
|
||||||
stopCurrentExecutor(t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() {
|
|
||||||
@Override
|
|
||||||
public void onClick(final CommentModel comment) {
|
|
||||||
onCommentClick(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onHashtagClick(final String hashtag) {
|
|
||||||
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalHashTagFragment(hashtag);
|
|
||||||
NavHostFragment.findNavController(CommentsViewerFragment.this).navigate(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMentionClick(final String mention) {
|
|
||||||
openProfile(mention);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onURLClick(final String url) {
|
|
||||||
Utils.openURL(getContext(), url);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEmailClick(final String emailAddress) {
|
|
||||||
Utils.openEmailAddress(getContext(), emailAddress);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private final View.OnClickListener newCommentListener = v -> {
|
|
||||||
final Editable text = binding.commentText.getText();
|
|
||||||
final Context context = getContext();
|
|
||||||
if (context == null) return;
|
|
||||||
if (text == null || TextUtils.isEmpty(text.toString())) {
|
|
||||||
Toast.makeText(context, R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (userIdFromCookie == 0) return;
|
|
||||||
String replyToId = null;
|
|
||||||
final CommentModel commentModel = commentsAdapter.getSelected();
|
|
||||||
if (commentModel != null) {
|
|
||||||
replyToId = commentModel.getId();
|
|
||||||
}
|
|
||||||
mediaService.comment(postId, text.toString(), replyToId, new ServiceCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final Boolean result) {
|
|
||||||
commentsAdapter.clearSelection();
|
|
||||||
binding.commentText.setText("");
|
|
||||||
if (!result) {
|
|
||||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
Log.e(TAG, "Error during comment", t);
|
|
||||||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
fragmentActivity = (AppCompatActivity) getActivity();
|
|
||||||
final String deviceUuid = Utils.settingsHelper.getString(Constants.DEVICE_UUID);
|
|
||||||
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
|
|
||||||
userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
|
|
||||||
mediaService = MediaService.getInstance(deviceUuid, csrfToken, userIdFromCookie);
|
|
||||||
// setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
|
|
||||||
if (root != null) {
|
|
||||||
shouldRefresh = false;
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
binding = FragmentCommentsBinding.inflate(getLayoutInflater());
|
|
||||||
binding.swipeRefreshLayout.setEnabled(false);
|
|
||||||
binding.swipeRefreshLayout.setNestedScrollingEnabled(false);
|
|
||||||
root = binding.getRoot();
|
|
||||||
return root;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
|
|
||||||
if (!shouldRefresh) return;
|
|
||||||
init();
|
|
||||||
shouldRefresh = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
|
|
||||||
// inflater.inflate(R.menu.follow, menu);
|
|
||||||
// menu.findItem(R.id.action_compare).setVisible(false);
|
|
||||||
// final MenuItem menuSearch = menu.findItem(R.id.action_search);
|
|
||||||
// final SearchView searchView = (SearchView) menuSearch.getActionView();
|
|
||||||
// searchView.setQueryHint(getResources().getString(R.string.action_search));
|
|
||||||
// searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
|
||||||
// @Override
|
|
||||||
// public boolean onQueryTextSubmit(final String query) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Override
|
|
||||||
// public boolean onQueryTextChange(final String query) {
|
|
||||||
// // if (commentsAdapter != null) commentsAdapter.getFilter().filter(query);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRefresh() {
|
|
||||||
endCursor = null;
|
|
||||||
lazyLoader.resetState();
|
|
||||||
commentsViewModel.getList().postValue(Collections.emptyList());
|
|
||||||
stopCurrentExecutor(null);
|
|
||||||
currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init() {
|
|
||||||
if (getArguments() == null) return;
|
|
||||||
final CommentsViewerFragmentArgs fragmentArgs = CommentsViewerFragmentArgs.fromBundle(getArguments());
|
|
||||||
shortCode = fragmentArgs.getShortCode();
|
|
||||||
postId = fragmentArgs.getPostId();
|
|
||||||
authorUserId = fragmentArgs.getPostUserId();
|
|
||||||
// setTitle();
|
|
||||||
binding.swipeRefreshLayout.setOnRefreshListener(this);
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(true);
|
|
||||||
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
|
|
||||||
layoutManager = new LinearLayoutManager(getContext());
|
|
||||||
binding.rvComments.setLayoutManager(layoutManager);
|
|
||||||
commentsAdapter = new CommentsAdapter(commentCallback);
|
|
||||||
binding.rvComments.setAdapter(commentsAdapter);
|
|
||||||
commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList);
|
|
||||||
resources = getResources();
|
|
||||||
if (!TextUtils.isEmpty(cookie)) {
|
|
||||||
binding.commentField.setStartIconVisible(false);
|
|
||||||
binding.commentField.setEndIconVisible(false);
|
|
||||||
binding.commentField.setVisibility(View.VISIBLE);
|
|
||||||
binding.commentText.addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
|
||||||
binding.commentField.setStartIconVisible(s.length() > 0);
|
|
||||||
binding.commentField.setEndIconVisible(s.length() > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(final Editable s) {}
|
|
||||||
});
|
|
||||||
binding.commentField.setStartIconOnClickListener(v -> {
|
|
||||||
commentsAdapter.clearSelection();
|
|
||||||
binding.commentText.setText("");
|
|
||||||
});
|
|
||||||
binding.commentField.setEndIconOnClickListener(newCommentListener);
|
|
||||||
}
|
|
||||||
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
|
||||||
if (hasNextPage && !TextUtils.isEmpty(endCursor))
|
|
||||||
currentlyRunning = new CommentsFetcher(shortCode, endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
endCursor = null;
|
|
||||||
});
|
|
||||||
binding.rvComments.addOnScrollListener(lazyLoader);
|
|
||||||
stopCurrentExecutor(null);
|
|
||||||
onRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private void setTitle() {
|
|
||||||
// final ActionBar actionBar = fragmentActivity.getSupportActionBar();
|
|
||||||
// if (actionBar == null) return;
|
|
||||||
// actionBar.setTitle(R.string.title_comments);
|
|
||||||
// actionBar.setSubtitle(shortCode);
|
|
||||||
// }
|
|
||||||
|
|
||||||
private void onCommentClick(final CommentModel commentModel) {
|
|
||||||
final String username = commentModel.getProfileModel().getUsername();
|
|
||||||
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText());
|
|
||||||
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
|
||||||
|
|
||||||
String[] commentDialogList;
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(cookie)
|
|
||||||
&& userIdFromCookie != 0
|
|
||||||
&& (userIdFromCookie == commentModel.getProfileModel().getPk() || userIdFromCookie == authorUserId)) {
|
|
||||||
commentDialogList = new String[]{
|
|
||||||
resources.getString(R.string.open_profile),
|
|
||||||
resources.getString(R.string.comment_viewer_copy_comment),
|
|
||||||
resources.getString(R.string.comment_viewer_see_likers),
|
|
||||||
resources.getString(R.string.comment_viewer_reply_comment),
|
|
||||||
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
|
|
||||||
: resources.getString(R.string.comment_viewer_like_comment),
|
|
||||||
resources.getString(R.string.comment_viewer_translate_comment),
|
|
||||||
resources.getString(R.string.comment_viewer_delete_comment)
|
|
||||||
};
|
|
||||||
} else if (!TextUtils.isEmpty(cookie)) {
|
|
||||||
commentDialogList = new String[]{
|
|
||||||
resources.getString(R.string.open_profile),
|
|
||||||
resources.getString(R.string.comment_viewer_copy_comment),
|
|
||||||
resources.getString(R.string.comment_viewer_see_likers),
|
|
||||||
resources.getString(R.string.comment_viewer_reply_comment),
|
|
||||||
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
|
|
||||||
: resources.getString(R.string.comment_viewer_like_comment),
|
|
||||||
resources.getString(R.string.comment_viewer_translate_comment)
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
commentDialogList = new String[]{
|
|
||||||
resources.getString(R.string.open_profile),
|
|
||||||
resources.getString(R.string.comment_viewer_copy_comment),
|
|
||||||
resources.getString(R.string.comment_viewer_see_likers)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
final Context context = getContext();
|
|
||||||
if (context == null) return;
|
|
||||||
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
|
|
||||||
final User profileModel = commentModel.getProfileModel();
|
|
||||||
switch (which) {
|
|
||||||
case 0: // open profile
|
|
||||||
openProfile("@" + profileModel.getUsername());
|
|
||||||
break;
|
|
||||||
case 1: // copy comment
|
|
||||||
Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText());
|
|
||||||
break;
|
|
||||||
case 2: // see comment likers, this is surprisingly available to anons
|
|
||||||
final NavController navController = getNavController();
|
|
||||||
if (navController != null) {
|
|
||||||
final Bundle bundle = new Bundle();
|
|
||||||
bundle.putString("postId", commentModel.getId());
|
|
||||||
bundle.putBoolean("isComment", true);
|
|
||||||
navController.navigate(R.id.action_global_likesViewerFragment, bundle);
|
|
||||||
} else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
case 3: // reply to comment
|
|
||||||
commentsAdapter.setSelected(commentModel);
|
|
||||||
String mention = "@" + profileModel.getUsername() + " ";
|
|
||||||
binding.commentText.setText(mention);
|
|
||||||
binding.commentText.requestFocus();
|
|
||||||
binding.commentText.setSelection(mention.length());
|
|
||||||
binding.commentText.postDelayed(() -> {
|
|
||||||
imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
|
|
||||||
if (imm == null) return;
|
|
||||||
imm.showSoftInput(binding.commentText, 0);
|
|
||||||
}, 200);
|
|
||||||
break;
|
|
||||||
case 4: // like/unlike comment
|
|
||||||
if (!commentModel.getLiked()) {
|
|
||||||
mediaService.commentLike(commentModel.getId(), new ServiceCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final Boolean result) {
|
|
||||||
if (!result) {
|
|
||||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
commentsAdapter.setLiked(commentModel, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
Log.e(TAG, "Error liking comment", t);
|
|
||||||
try {
|
|
||||||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
catch(final Throwable e) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mediaService.commentUnlike(commentModel.getId(), new ServiceCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final Boolean result) {
|
|
||||||
if (!result) {
|
|
||||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
commentsAdapter.setLiked(commentModel, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
Log.e(TAG, "Error unliking comment", t);
|
|
||||||
try {
|
|
||||||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
catch(final Throwable e) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 5: // translate comment
|
|
||||||
mediaService.translate(commentModel.getId(), "2", new ServiceCallback<String>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final String result) {
|
|
||||||
if (TextUtils.isEmpty(result)) {
|
|
||||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
new AlertDialog.Builder(context)
|
|
||||||
.setTitle(username)
|
|
||||||
.setMessage(result)
|
|
||||||
.setPositiveButton(R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
Log.e(TAG, "Error translating comment", t);
|
|
||||||
try {
|
|
||||||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
catch(final Throwable e) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 6: // delete comment
|
|
||||||
if (userIdFromCookie == 0) return;
|
|
||||||
mediaService.deleteComment(
|
|
||||||
postId, commentModel.getId(),
|
|
||||||
new ServiceCallback<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public void onSuccess(final Boolean result) {
|
|
||||||
if (!result) {
|
|
||||||
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onRefresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(final Throwable t) {
|
|
||||||
Log.e(TAG, "Error deleting comment", t);
|
|
||||||
try {
|
|
||||||
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
catch(final Throwable e) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
new AlertDialog.Builder(context)
|
|
||||||
.setTitle(title)
|
|
||||||
.setItems(commentDialogList, profileDialogListener)
|
|
||||||
.setNegativeButton(R.string.cancel, null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void openProfile(final String username) {
|
|
||||||
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username);
|
|
||||||
NavHostFragment.findNavController(this).navigate(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopCurrentExecutor(final Throwable t) {
|
|
||||||
if (currentlyRunning != null) {
|
|
||||||
try {
|
|
||||||
currentlyRunning.cancel(true);
|
|
||||||
} catch (final Exception e) {
|
|
||||||
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (t != null) {
|
|
||||||
try {
|
|
||||||
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
|
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
|
||||||
}
|
|
||||||
catch(Throwable e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private NavController getNavController() {
|
|
||||||
NavController navController = null;
|
|
||||||
try {
|
|
||||||
navController = NavHostFragment.findNavController(this);
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
Log.e(TAG, "navigateToProfile", e);
|
|
||||||
}
|
|
||||||
return navController;
|
|
||||||
}
|
|
||||||
}
|
|
@ -41,7 +41,9 @@ public class FavoritesFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
public void onCreate(@Nullable final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
|
final Context context = getContext();
|
||||||
|
if (context == null) return;
|
||||||
|
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@ -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,12 +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.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;
|
||||||
@ -60,6 +63,7 @@ import awais.instagrabber.repositories.requests.StoryViewerOptions;
|
|||||||
import awais.instagrabber.repositories.responses.Hashtag;
|
import awais.instagrabber.repositories.responses.Hashtag;
|
||||||
import awais.instagrabber.repositories.responses.Location;
|
import awais.instagrabber.repositories.responses.Location;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
@ -74,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;
|
||||||
@ -86,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;
|
||||||
@ -213,7 +214,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
final View mainPostImage,
|
final View mainPostImage,
|
||||||
final int position) {
|
final int position) {
|
||||||
if (opening) return;
|
if (opening) return;
|
||||||
if (TextUtils.isEmpty(feedModel.getUser().getUsername())) {
|
final User user = feedModel.getUser();
|
||||||
|
if (user == null) return;
|
||||||
|
if (TextUtils.isEmpty(user.getUsername())) {
|
||||||
opening = true;
|
opening = true;
|
||||||
new PostFetcher(feedModel.getCode(), newFeedModel -> {
|
new PostFetcher(feedModel.getCode(), newFeedModel -> {
|
||||||
opening = false;
|
opening = false;
|
||||||
@ -223,15 +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);
|
|
||||||
}
|
|
||||||
builder.build().show(getChildFragmentManager(), "post_view");
|
|
||||||
opening = false;
|
opening = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -301,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,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());
|
||||||
@ -396,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() {
|
||||||
@ -403,8 +407,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
|
|||||||
try {
|
try {
|
||||||
Toast.makeText(getContext(), R.string.error_loading_hashtag, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), R.string.error_loading_hashtag, Toast.LENGTH_SHORT).show();
|
||||||
binding.swipeRefreshLayout.setEnabled(false);
|
binding.swipeRefreshLayout.setEnabled(false);
|
||||||
}
|
} catch (Exception ignored) {}
|
||||||
catch (Exception ignored) {}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTitle();
|
setTitle();
|
||||||
@ -423,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,
|
||||||
@ -449,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) {
|
||||||
@ -552,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())
|
||||||
|
@ -11,6 +11,7 @@ import android.widget.Toast;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ import awais.instagrabber.webservices.ServiceCallback;
|
|||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
import static awais.instagrabber.utils.Utils.settingsHelper;
|
||||||
|
|
||||||
public final class LikesViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener {
|
public final class LikesViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener {
|
||||||
private static final String TAG = "LikesViewerFragment";
|
private static final String TAG = LikesViewerFragment.class.getSimpleName();
|
||||||
|
|
||||||
private FragmentLikesBinding binding;
|
private FragmentLikesBinding binding;
|
||||||
private RecyclerLazyLoader lazyLoader;
|
private RecyclerLazyLoader lazyLoader;
|
||||||
@ -58,6 +59,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
|
|||||||
});
|
});
|
||||||
binding.rvLikes.setAdapter(likesAdapter);
|
binding.rvLikes.setAdapter(likesAdapter);
|
||||||
binding.rvLikes.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.rvLikes.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.rvLikes.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL));
|
||||||
binding.swipeRefreshLayout.setRefreshing(false);
|
binding.swipeRefreshLayout.setRefreshing(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +73,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private final ServiceCallback<GraphQLUserListFetchResponse> acb = new ServiceCallback<GraphQLUserListFetchResponse>() {
|
private final ServiceCallback<GraphQLUserListFetchResponse> anonCb = new ServiceCallback<GraphQLUserListFetchResponse>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final GraphQLUserListFetchResponse result) {
|
public void onSuccess(final GraphQLUserListFetchResponse result) {
|
||||||
endCursor = result.getNextMaxId();
|
endCursor = result.getNextMaxId();
|
||||||
@ -127,7 +129,7 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
|
|||||||
public void onRefresh() {
|
public void onRefresh() {
|
||||||
if (isComment && !isLoggedIn) {
|
if (isComment && !isLoggedIn) {
|
||||||
lazyLoader.resetState();
|
lazyLoader.resetState();
|
||||||
graphQLService.fetchCommentLikers(postId, null, acb);
|
graphQLService.fetchCommentLikers(postId, null, anonCb);
|
||||||
} else mediaService.fetchLikes(postId, isComment, cb);
|
} else mediaService.fetchLikes(postId, isComment, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,9 +143,10 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme
|
|||||||
if (isComment && !isLoggedIn) {
|
if (isComment && !isLoggedIn) {
|
||||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
|
final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
|
||||||
binding.rvLikes.setLayoutManager(layoutManager);
|
binding.rvLikes.setLayoutManager(layoutManager);
|
||||||
|
binding.rvLikes.addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.HORIZONTAL));
|
||||||
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
|
||||||
if (!TextUtils.isEmpty(endCursor))
|
if (!TextUtils.isEmpty(endCursor))
|
||||||
graphQLService.fetchCommentLikers(postId, endCursor, acb);
|
graphQLService.fetchCommentLikers(postId, endCursor, anonCb);
|
||||||
endCursor = null;
|
endCursor = null;
|
||||||
});
|
});
|
||||||
binding.rvLikes.addOnScrollListener(lazyLoader);
|
binding.rvLikes.addOnScrollListener(lazyLoader);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package awais.instagrabber.fragments;
|
package awais.instagrabber.fragments;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@ -21,12 +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.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;
|
||||||
@ -56,6 +59,7 @@ import awais.instagrabber.models.enums.FavoriteType;
|
|||||||
import awais.instagrabber.repositories.requests.StoryViewerOptions;
|
import awais.instagrabber.repositories.requests.StoryViewerOptions;
|
||||||
import awais.instagrabber.repositories.responses.Location;
|
import awais.instagrabber.repositories.responses.Location;
|
||||||
import awais.instagrabber.repositories.responses.Media;
|
import awais.instagrabber.repositories.responses.Media;
|
||||||
|
import awais.instagrabber.repositories.responses.User;
|
||||||
import awais.instagrabber.utils.Constants;
|
import awais.instagrabber.utils.Constants;
|
||||||
import awais.instagrabber.utils.CookieUtils;
|
import awais.instagrabber.utils.CookieUtils;
|
||||||
import awais.instagrabber.utils.DownloadUtils;
|
import awais.instagrabber.utils.DownloadUtils;
|
||||||
@ -70,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;
|
||||||
@ -80,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;
|
||||||
@ -204,7 +205,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
|
|||||||
final View mainPostImage,
|
final View mainPostImage,
|
||||||
final int position) {
|
final int position) {
|
||||||
if (opening) return;
|
if (opening) return;
|
||||||
if (TextUtils.isEmpty(feedModel.getUser().getUsername())) {
|
final User user = feedModel.getUser();
|
||||||
|
if (user == null) return;
|
||||||
|
if (TextUtils.isEmpty(user.getUsername())) {
|
||||||
opening = true;
|
opening = true;
|
||||||
new PostFetcher(feedModel.getCode(), newFeedModel -> {
|
new PostFetcher(feedModel.getCode(), newFeedModel -> {
|
||||||
opening = false;
|
opening = false;
|
||||||
@ -214,16 +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);
|
|
||||||
}
|
|
||||||
builder.build().show(getChildFragmentManager(), "post_view");
|
|
||||||
opening = false;
|
opening = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -295,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,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());
|
||||||
@ -386,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() {
|
||||||
@ -399,8 +402,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
|
|||||||
try {
|
try {
|
||||||
Toast.makeText(getContext(), R.string.error_loading_location, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), R.string.error_loading_location, Toast.LENGTH_SHORT).show();
|
||||||
binding.swipeRefreshLayout.setEnabled(false);
|
binding.swipeRefreshLayout.setEnabled(false);
|
||||||
}
|
} catch (Exception ignored) {}
|
||||||
catch (Exception ignored) {}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTitle();
|
setTitle();
|
||||||
@ -456,9 +458,16 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
|
|||||||
if (!locationModel.getGeo().startsWith("geo:0.0,0.0?z=17")) {
|
if (!locationModel.getGeo().startsWith("geo:0.0,0.0?z=17")) {
|
||||||
locationDetailsBinding.btnMap.setVisibility(View.VISIBLE);
|
locationDetailsBinding.btnMap.setVisibility(View.VISIBLE);
|
||||||
locationDetailsBinding.btnMap.setOnClickListener(v -> {
|
locationDetailsBinding.btnMap.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setData(Uri.parse(locationModel.getGeo()));
|
intent.setData(Uri.parse(locationModel.getGeo()));
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(context, R.string.no_external_map_app, Toast.LENGTH_LONG).show();
|
||||||
|
Log.e(TAG, "setupLocationDetails: ", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "setupLocationDetails: ", e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
locationDetailsBinding.btnMap.setVisibility(View.GONE);
|
locationDetailsBinding.btnMap.setVisibility(View.GONE);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user