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

Merge branch 'austinhuang0131:master' into stamatiap/development

This commit is contained in:
Stamatia Papageorgiou 2021-05-16 10:09:08 +03:00
commit cdb566a160
48 changed files with 2135 additions and 721 deletions

View File

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

View File

@ -15,10 +15,11 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: set up JDK 1.8 - name: set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: 1.8 distribution: 'zulu'
java-version: '8'
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew

View File

@ -16,9 +16,10 @@ jobs:
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: set up JDK 1.8 - name: set up JDK 1.8
uses: actions/setup-java@v1 uses: actions/setup-java@v2
with: with:
java-version: 1.8 distribution: 'zulu'
java-version: '8'
- name: Grant execute permission for gradlew - name: Grant execute permission for gradlew
run: chmod +x gradlew run: chmod +x gradlew

View File

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

View File

@ -12,7 +12,7 @@ def getGitHash = { ->
} }
android { android {
compileSdkVersion 29 compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId 'me.austinhuang.instagrabber' applicationId 'me.austinhuang.instagrabber'
@ -165,8 +165,6 @@ dependencies {
implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.recyclerview:recyclerview:1.2.0" 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"
@ -180,6 +178,9 @@ dependencies {
implementation 'com.google.guava:guava:27.0.1-android' implementation 'com.google.guava:guava:27.0.1-android'
def core_version = "1.6.0-alpha03"
implementation "androidx.core:core:$core_version"
// Room // Room
def room_version = "2.2.6" def room_version = "2.2.6"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"

View File

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

View File

@ -31,6 +31,9 @@ import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.provider.FontRequest; import androidx.core.provider.FontRequest;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.emoji.text.EmojiCompat; import androidx.emoji.text.EmojiCompat;
import androidx.emoji.text.FontRequestEmojiCompatConfig; import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -61,6 +64,7 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.asyncs.PostFetcher; import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.emoji.EmojiVariantManager; import awais.instagrabber.customviews.emoji.EmojiVariantManager;
import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter; import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.ActivityMainBinding; import awais.instagrabber.databinding.ActivityMainBinding;
import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.fragments.PostViewV2Fragment;
@ -137,11 +141,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
instance = this; instance = this;
binding = ActivityMainBinding.inflate(getLayoutInflater()); binding = ActivityMainBinding.inflate(getLayoutInflater());
setupCookie(); setupCookie();
if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) {
getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
setContentView(binding.getRoot()); setContentView(binding.getRoot());
final Toolbar toolbar = binding.toolbar; final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar); setSupportActionBar(toolbar);
final RootViewDeferringInsetsCallback deferringInsetsCallback = new RootViewDeferringInsetsCallback(
WindowInsetsCompat.Type.systemBars(),
WindowInsetsCompat.Type.ime()
);
ViewCompat.setWindowInsetsAnimationCallback(binding.getRoot(), deferringInsetsCallback);
ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), deferringInsetsCallback);
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
createNotificationChannels(); createNotificationChannels();
try { try {
final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams(); final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams();

View File

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

View File

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

View File

@ -0,0 +1,246 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.WindowInsetsAnimation;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.NestedScrollingParent3;
import androidx.core.view.NestedScrollingParentHelper;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.Arrays;
import awais.instagrabber.customviews.helpers.SimpleImeAnimationController;
import awais.instagrabber.utils.ViewUtils;
import static androidx.core.view.ViewCompat.TYPE_TOUCH;
public final class InsetsAnimationLinearLayout extends LinearLayout implements NestedScrollingParent3 {
private final NestedScrollingParentHelper nestedScrollingParentHelper = new NestedScrollingParentHelper(this);
private final SimpleImeAnimationController imeAnimController = new SimpleImeAnimationController();
private final int[] tempIntArray2 = new int[2];
private final int[] startViewLocation = new int[2];
private View currentNestedScrollingChild;
private int dropNextY;
private boolean scrollImeOffScreenWhenVisible = true;
private boolean scrollImeOnScreenWhenNotVisible = true;
private boolean scrollImeOffScreenWhenVisibleOnFling = false;
private boolean scrollImeOnScreenWhenNotVisibleOnFling = false;
public InsetsAnimationLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public InsetsAnimationLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public final boolean getScrollImeOffScreenWhenVisible() {
return scrollImeOffScreenWhenVisible;
}
public final void setScrollImeOffScreenWhenVisible(boolean scrollImeOffScreenWhenVisible) {
this.scrollImeOffScreenWhenVisible = scrollImeOffScreenWhenVisible;
}
public final boolean getScrollImeOnScreenWhenNotVisible() {
return scrollImeOnScreenWhenNotVisible;
}
public final void setScrollImeOnScreenWhenNotVisible(boolean scrollImeOnScreenWhenNotVisible) {
this.scrollImeOnScreenWhenNotVisible = scrollImeOnScreenWhenNotVisible;
}
public boolean getScrollImeOffScreenWhenVisibleOnFling() {
return scrollImeOffScreenWhenVisibleOnFling;
}
public void setScrollImeOffScreenWhenVisibleOnFling(final boolean scrollImeOffScreenWhenVisibleOnFling) {
this.scrollImeOffScreenWhenVisibleOnFling = scrollImeOffScreenWhenVisibleOnFling;
}
public boolean getScrollImeOnScreenWhenNotVisibleOnFling() {
return scrollImeOnScreenWhenNotVisibleOnFling;
}
public void setScrollImeOnScreenWhenNotVisibleOnFling(final boolean scrollImeOnScreenWhenNotVisibleOnFling) {
this.scrollImeOnScreenWhenNotVisibleOnFling = scrollImeOnScreenWhenNotVisibleOnFling;
}
public SimpleImeAnimationController getImeAnimController() {
return imeAnimController;
}
@Override
public boolean onStartNestedScroll(@NonNull final View child,
@NonNull final View target,
final int axes,
final int type) {
return (axes & SCROLL_AXIS_VERTICAL) != 0 && type == TYPE_TOUCH;
}
@Override
public void onNestedScrollAccepted(@NonNull final View child,
@NonNull final View target,
final int axes,
final int type) {
nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type);
currentNestedScrollingChild = child;
}
@Override
public void onNestedPreScroll(@NonNull final View target,
final int dx,
final int dy,
@NonNull final int[] consumed,
final int type) {
if (imeAnimController.isInsetAnimationRequestPending()) {
consumed[0] = dx;
consumed[1] = dy;
} else {
int deltaY = dy;
if (dropNextY != 0) {
consumed[1] = dropNextY;
deltaY = dy - dropNextY;
dropNextY = 0;
}
if (deltaY < 0) {
if (imeAnimController.isInsetAnimationInProgress()) {
consumed[1] -= imeAnimController.insetBy(-deltaY);
} else if (scrollImeOffScreenWhenVisible && !imeAnimController.isInsetAnimationRequestPending()) {
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
if (rootWindowInsets != null) {
if (rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
startControlRequest();
consumed[1] = deltaY;
}
}
}
}
}
}
@Override
public void onNestedScroll(@NonNull final View target,
final int dxConsumed,
final int dyConsumed,
final int dxUnconsumed,
final int dyUnconsumed,
final int type,
@NonNull final int[] consumed) {
if (dyUnconsumed > 0) {
if (imeAnimController.isInsetAnimationInProgress()) {
consumed[1] = -imeAnimController.insetBy(-dyUnconsumed);
} else if (scrollImeOnScreenWhenNotVisible && !imeAnimController.isInsetAnimationRequestPending()) {
WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
if (rootWindowInsets != null) {
if (!rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
startControlRequest();
consumed[1] = dyUnconsumed;
}
}
}
}
}
@Override
public boolean onNestedFling(@NonNull final View target,
final float velocityX,
final float velocityY,
final boolean consumed) {
if (imeAnimController.isInsetAnimationInProgress()) {
imeAnimController.animateToFinish(velocityY);
return true;
} else {
boolean imeVisible = false;
final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this);
if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) {
imeVisible = true;
}
if (velocityY > 0 && scrollImeOnScreenWhenNotVisibleOnFling && !imeVisible) {
imeAnimController.startAndFling(this, velocityY);
return true;
} else if (velocityY < 0 && scrollImeOffScreenWhenVisibleOnFling && imeVisible) {
imeAnimController.startAndFling(this, velocityY);
return true;
} else {
return false;
}
}
}
@Override
public void onStopNestedScroll(@NonNull final View target, final int type) {
nestedScrollingParentHelper.onStopNestedScroll(target, type);
if (imeAnimController.isInsetAnimationInProgress() && !imeAnimController.isInsetAnimationFinishing()) {
imeAnimController.animateToFinish(null);
}
reset();
}
@Override
public void dispatchWindowInsetsAnimationPrepare(@NonNull final WindowInsetsAnimation animation) {
super.dispatchWindowInsetsAnimationPrepare(animation);
ViewUtils.suppressLayoutCompat(this, false);
}
private void startControlRequest() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
return;
}
ViewUtils.suppressLayoutCompat(this, true);
if (currentNestedScrollingChild != null) {
currentNestedScrollingChild.getLocationInWindow(startViewLocation);
}
imeAnimController.startControlRequest(this, windowInsetsAnimationControllerCompat -> onControllerReady());
}
private void onControllerReady() {
if (currentNestedScrollingChild != null) {
imeAnimController.insetBy(0);
int[] location = tempIntArray2;
currentNestedScrollingChild.getLocationInWindow(location);
dropNextY = location[1] - startViewLocation[1];
}
}
private void reset() {
dropNextY = 0;
Arrays.fill(startViewLocation, 0);
ViewUtils.suppressLayoutCompat(this, false);
}
@Override
public void onNestedScrollAccepted(@NonNull final View child,
@NonNull final View target,
final int axes) {
onNestedScrollAccepted(child, target, axes, TYPE_TOUCH);
}
@Override
public void onNestedScroll(@NonNull final View target,
final int dxConsumed,
final int dyConsumed,
final int dxUnconsumed,
final int dyUnconsumed,
final int type) {
onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, tempIntArray2);
}
@Override
public void onStopNestedScroll(@NonNull final View target) {
onStopNestedScroll(target, TYPE_TOUCH);
}
}

View File

@ -0,0 +1,33 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowInsets;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
public class InsetsNotifyingCoordinatorLayout extends CoordinatorLayout {
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context) {
super(context);
}
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++) {
getChildAt(index).dispatchApplyWindowInsets(insets);
}
return insets;
}
}

View File

@ -0,0 +1,35 @@
package awais.instagrabber.customviews;
import android.content.Context;
import android.util.AttributeSet;
import android.view.WindowInsets;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
public class InsetsNotifyingLinearLayout extends LinearLayout {
public InsetsNotifyingLinearLayout(final Context context) {
super(context);
}
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs) {
super(context, attrs);
}
public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public InsetsNotifyingLinearLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int childCount = getChildCount();
for (int index = 0; index < childCount; index++) {
getChildAt(index).dispatchApplyWindowInsets(insets);
}
return insets;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -29,6 +30,7 @@ import androidx.appcompat.widget.PopupMenu;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.constraintlayout.widget.ConstraintLayout; import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.PermissionChecker; import androidx.core.content.PermissionChecker;
import androidx.core.view.WindowCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
@ -100,7 +102,6 @@ import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG; import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.getAttrValue;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostViewV2Fragment extends Fragment implements EditTextDialogFragment.EditTextDialogFragmentCallback { public class PostViewV2Fragment extends Fragment implements EditTextDialogFragment.EditTextDialogFragmentCallback {
@ -131,6 +132,9 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private boolean isInFullScreenMode; private boolean isInFullScreenMode;
private StyledPlayerView playerView; private StyledPlayerView playerView;
private int playerViewOriginalHeight; private int playerViewOriginalHeight;
private Drawable originalRootBackground;
private ColorStateList originalLikeColorStateList;
private ColorStateList originalSaveColorStateList;
private final Observer<Object> backStackSavedStateObserver = result -> { private final Observer<Object> backStackSavedStateObserver = result -> {
if (result == null) return; if (result == null) return;
@ -141,7 +145,6 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
// clear result // clear result
backStackSavedStateResultLiveData.postValue(null); backStackSavedStateResultLiveData.postValue(null);
}; };
private Drawable originalRootBackground;
public void setOnDeleteListener(final OnDeleteListener onDeleteListener) { public void setOnDeleteListener(final OnDeleteListener onDeleteListener) {
if (onDeleteListener == null) return; if (onDeleteListener == null) return;
@ -441,6 +444,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
} }
private void setupLike() { private void setupLike() {
originalLikeColorStateList = bottom.like.getIconTint();
final boolean likableMedia = viewModel.hasPk() /*&& viewModel.getMedia().isCommentLikesEnabled()*/; final boolean likableMedia = viewModel.hasPk() /*&& viewModel.getMedia().isCommentLikesEnabled()*/;
if (!likableMedia) { if (!likableMedia) {
bottom.like.setVisibility(View.GONE); bottom.like.setVisibility(View.GONE);
@ -503,25 +507,25 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private void setLikedResources(final boolean liked) { private void setLikedResources(final boolean liked) {
final int iconResource; final int iconResource;
final int tintResource; final ColorStateList tintColorStateList;
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
final Resources resources = context.getResources(); final Resources resources = context.getResources();
if (resources == null) return; if (resources == null) return;
if (liked) { if (liked) {
iconResource = R.drawable.ic_like; iconResource = R.drawable.ic_like;
tintResource = resources.getColor(R.color.red_600); tintColorStateList = ColorStateList.valueOf(resources.getColor(R.color.red_600));
// textResId = R.string.unlike_without_count;
} else { } else {
iconResource = R.drawable.ic_not_liked; iconResource = R.drawable.ic_not_liked;
tintResource = getAttrValue(context, R.attr.colorPrimary); tintColorStateList = originalLikeColorStateList != null ? originalLikeColorStateList
// textResId = R.string.like_without_count; : ColorStateList.valueOf(resources.getColor(R.color.white));
} }
bottom.like.setIconResource(iconResource); bottom.like.setIconResource(iconResource);
bottom.like.setIconTint(ColorStateList.valueOf(tintResource)); bottom.like.setIconTint(tintColorStateList);
} }
private void setupSave() { private void setupSave() {
originalSaveColorStateList = bottom.save.getIconTint();
if (!viewModel.isLoggedIn() || !viewModel.hasPk() || !viewModel.getMedia().canViewerSave()) { if (!viewModel.isLoggedIn() || !viewModel.hasPk() || !viewModel.getMedia().canViewerSave()) {
bottom.save.setVisibility(View.GONE); bottom.save.setVisibility(View.GONE);
return; return;
@ -574,22 +578,21 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
private void setSavedResources(final boolean saved) { private void setSavedResources(final boolean saved) {
final int iconResource; final int iconResource;
final int tintResource; final ColorStateList tintColorStateList;
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
final Resources resources = context.getResources(); final Resources resources = context.getResources();
if (resources == null) return; if (resources == null) return;
if (saved) { if (saved) {
iconResource = R.drawable.ic_bookmark; iconResource = R.drawable.ic_bookmark;
tintResource = resources.getColor(R.color.blue_700); tintColorStateList = ColorStateList.valueOf(resources.getColor(R.color.blue_700));
// textResId = R.string.saved;
} else { } else {
iconResource = R.drawable.ic_round_bookmark_border_24; iconResource = R.drawable.ic_round_bookmark_border_24;
tintResource = getAttrValue(context, R.attr.colorPrimary); tintColorStateList = originalSaveColorStateList != null ? originalSaveColorStateList
// textResId = R.string.save; : ColorStateList.valueOf(resources.getColor(R.color.white));
} }
bottom.save.setIconResource(iconResource); bottom.save.setIconResource(iconResource);
bottom.save.setIconTint(ColorStateList.valueOf(tintResource)); bottom.save.setIconTint(tintColorStateList);
} }
private void setupProfilePic(final User user) { private void setupProfilePic(final User user) {
@ -1427,8 +1430,10 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
if (toolbar != null) { if (toolbar != null) {
toolbar.setVisibility(View.VISIBLE); toolbar.setVisibility(View.VISIBLE);
} }
final View decorView = activity.getWindow().getDecorView(); final Window window = activity.getWindow();
final View decorView = window.getDecorView();
decorView.setSystemUiVisibility(originalSystemUi); decorView.setSystemUiVisibility(originalSystemUi);
WindowCompat.setDecorFitsSystemWindows(window, false);
isInFullScreenMode = false; isInFullScreenMode = false;
} }

View File

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

View File

@ -120,6 +120,7 @@ public class StoryViewerFragment extends Fragment {
private View root; private View root;
private FragmentStoryViewerBinding binding; private FragmentStoryViewerBinding binding;
private String currentStoryUsername; private String currentStoryUsername;
private String highlightTitle;
private StoriesAdapter storiesAdapter; private StoriesAdapter storiesAdapter;
private SwipeEvent swipeEvent; private SwipeEvent swipeEvent;
private GestureDetectorCompat gestureDetector; private GestureDetectorCompat gestureDetector;
@ -275,7 +276,9 @@ public class StoryViewerFragment extends Fragment {
@Override @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
releasePlayer(); if (player != null) {
player.pause();
}
} }
@Override @Override
@ -749,7 +752,7 @@ public class StoryViewerFragment extends Fragment {
final HighlightModel model = models.get(currentFeedStoryIndex); final HighlightModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId(); currentStoryMediaId = model.getId();
fetchOptions = StoryViewerOptions.forHighlight(model.getId()); fetchOptions = StoryViewerOptions.forHighlight(model.getId());
currentStoryUsername = model.getTitle(); highlightTitle = model.getTitle();
break; break;
} }
case FEED_STORY_POSITION: { case FEED_STORY_POSITION: {
@ -849,8 +852,8 @@ public class StoryViewerFragment extends Fragment {
if (type == Type.HIGHLIGHT) { if (type == Type.HIGHLIGHT) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
actionBarTitle = options.getName(); actionBarTitle = highlightTitle;
actionBar.setTitle(options.getName()); actionBar.setTitle(highlightTitle);
} }
} else if (hasUsername) { } else if (hasUsername) {
currentStoryUsername = currentStoryUsername.replace("@", ""); currentStoryUsername = currentStoryUsername.replace("@", "");

View File

@ -16,6 +16,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.view.menu.ActionMenuItemView;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
@ -27,6 +28,7 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.badge.BadgeDrawable; import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.badge.BadgeUtils; import com.google.android.material.badge.BadgeUtils;
import com.google.android.material.internal.ToolbarUtils;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import java.util.List; import java.util.List;
@ -102,7 +104,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
super.onPause(); super.onPause();
unregisterReceiver(); unregisterReceiver();
isPendingRequestTotalBadgeAttached = false; isPendingRequestTotalBadgeAttached = false;
if (pendingRequestTotalBadgeDrawable != null) { @SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
if (pendingRequestTotalBadgeDrawable != null && menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId()); BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
pendingRequestTotalBadgeDrawable = null; pendingRequestTotalBadgeDrawable = null;
} }
@ -217,7 +221,11 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh
pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context); pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context);
} }
if (count == null || count == 0) { if (count == null || count == 0) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId()); @SuppressLint("RestrictedApi") final ActionMenuItemView menuItemView = ToolbarUtils
.getActionMenuItemView(fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
if (menuItemView != null) {
BadgeUtils.detachBadgeDrawable(pendingRequestTotalBadgeDrawable, fragmentActivity.getToolbar(), pendingRequestsMenuItem.getItemId());
}
isPendingRequestTotalBadgeAttached = false; isPendingRequestTotalBadgeAttached = false;
pendingRequestTotalBadgeDrawable.setNumber(0); pendingRequestTotalBadgeDrawable.setNumber(0);
pendingRequestsMenuItem.setVisible(false); pendingRequestsMenuItem.setVisible(false);

View File

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

View File

@ -337,6 +337,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@Override
public void onResume() {
super.onResume();
binding.getRoot().postDelayed(feedStoriesAdapter::notifyDataSetChanged, 1000);
}
@Override @Override
public void onRefresh() { public void onRefresh() {
binding.feedRecyclerView.refresh(); binding.feedRecyclerView.refresh();
@ -418,15 +424,16 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
private void fetchStories() { private void fetchStories() {
if (storiesFetching) return;
// final String cookie = settingsHelper.getString(Constants.COOKIE); // final String cookie = settingsHelper.getString(Constants.COOKIE);
storiesFetching = true; storiesFetching = true;
updateSwipeRefreshState(); updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override @Override
public void onSuccess(final List<FeedStoryModel> result) { public void onSuccess(final List<FeedStoryModel> result) {
storiesFetching = false;
feedStoriesViewModel.getList().postValue(result); feedStoriesViewModel.getList().postValue(result);
feedStoriesAdapter.submitList(result); feedStoriesAdapter.submitList(result);
storiesFetching = false;
if (storyListMenu != null) storyListMenu.setVisible(true); if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState(); updateSwipeRefreshState();
} }
@ -451,8 +458,10 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
public void scrollToTop() { public void scrollToTop() {
binding.feedRecyclerView.smoothScrollToPosition(0); if (binding != null) {
// binding.storiesContainer.setExpanded(true); binding.feedRecyclerView.smoothScrollToPosition(0);
// binding.storiesContainer.setExpanded(true);
}
} }
private boolean isSafeToNavigate(final NavController navController) { private boolean isSafeToNavigate(final NavController navController) {

View File

@ -407,7 +407,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
chainingMenuItem = menu.findItem(R.id.chaining); chainingMenuItem = menu.findItem(R.id.chaining);
if (chainingMenuItem != null) { if (chainingMenuItem != null) {
chainingMenuItem.setVisible(isNotMe); chainingMenuItem.setVisible(isNotMe && profileModel.hasChaining());
} }
} }
@ -527,7 +527,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onRefresh() { public void onRefresh() {
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.GONE); profileDetailsBinding.countsDivider.getRoot().setVisibility(View.GONE);
profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE);
fetchProfileDetails(); fetchProfileDetails();
} }
@ -662,17 +662,21 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return; return;
} }
if (!postsSetupDone) { final long profileId = profileModel.getPk();
setupPosts(); if (!isReallyPrivate()) {
} else { if (!postsSetupDone) {
binding.postsRecyclerView.refresh(); setupPosts();
}
else {
binding.postsRecyclerView.refresh();
}
if (isLoggedIn) {
fetchStoryAndHighlights(profileId);
}
} }
profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE); profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE);
final long profileId = profileModel.getPk();
if (isLoggedIn) {
fetchStoryAndHighlights(profileId);
}
setupButtons(profileId); setupButtons(profileId);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() { favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@ -744,7 +748,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl()); profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getProfilePicUrl());
profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE); profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE);
profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.VISIBLE); profileDetailsBinding.countsDivider.getRoot().setVisibility(View.VISIBLE);
final long followersCount = profileModel.getFollowerCount(); final long followersCount = profileModel.getFollowerCount();
final long followingCount = profileModel.getFollowingCount(); final long followingCount = profileModel.getFollowingCount();
@ -905,6 +909,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage1.setImageResource(R.drawable.lock); binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc); binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE); binding.privatePage.setVisibility(View.VISIBLE);
binding.privatePage1.setVisibility(View.VISIBLE);
binding.privatePage2.setVisibility(View.VISIBLE);
binding.postsRecyclerView.setVisibility(View.GONE); binding.postsRecyclerView.setVisibility(View.GONE);
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
} }
@ -970,7 +976,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.unmute_posts : R.string.mute_posts); mutePostsMenuItem.setTitle(profileModel.getFriendshipStatus().isMuting() ? R.string.unmute_posts : R.string.mute_posts);
} }
if (chainingMenuItem != null) { if (chainingMenuItem != null) {
chainingMenuItem.setVisible(true); chainingMenuItem.setVisible(profileModel.hasChaining());
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -63,7 +63,7 @@ public class NavigationExtensions {
selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId()); selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId());
final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId); final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId);
isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag); isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag);
bottomNavigationView.setOnNavigationItemSelectedListener(item -> { bottomNavigationView.setOnItemSelectedListener(item -> {
if (fragmentManager.isStateSaved()) { if (fragmentManager.isStateSaved()) {
return false; return false;
} }
@ -169,7 +169,7 @@ public class NavigationExtensions {
private static void setupItemReselected(final BottomNavigationView bottomNavigationView, private static void setupItemReselected(final BottomNavigationView bottomNavigationView,
final SparseArray<String> graphIdToTagMap, final SparseArray<String> graphIdToTagMap,
final FragmentManager fragmentManager) { final FragmentManager fragmentManager) {
bottomNavigationView.setOnNavigationItemReselectedListener(item -> { bottomNavigationView.setOnItemReselectedListener(item -> {
final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId());
final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag); final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag);
if (fragmentByTag == null) { if (fragmentByTag == null) {

View File

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

View File

@ -38,6 +38,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.database.ExoDatabaseProvider;
@ -562,4 +564,39 @@ public final class Utils {
display.getRealSize(size); display.getRealSize(size);
return size; return size;
} }
public static <F, S> LiveData<Pair<F, S>> zipLiveData(@NonNull final LiveData<F> firstLiveData,
@NonNull final LiveData<S> secondLiveData) {
final ZippedLiveData<F, S> zippedLiveData = new ZippedLiveData<>();
zippedLiveData.addFirstSource(firstLiveData);
zippedLiveData.addSecondSource(secondLiveData);
return zippedLiveData;
}
public static class ZippedLiveData<F, S> extends MediatorLiveData<Pair<F, S>> {
private F lastF;
private S lastS;
private void update() {
F localLastF = lastF;
S localLastS = lastS;
if (localLastF != null && localLastS != null) {
setValue(new Pair<>(localLastF, localLastS));
}
}
public void addFirstSource(@NonNull final LiveData<F> firstLiveData) {
addSource(firstLiveData, f -> {
lastF = f;
update();
});
}
public void addSecondSource(@NonNull final LiveData<S> secondLiveData) {
addSource(secondLiveData, s -> {
lastS = s;
update();
});
}
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,312 +1,315 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <awais.instagrabber.customviews.InsetsAnimationLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false"> android:clipToPadding="false"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/chats" android:id="@+id/chats"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1"
android:scrollbars="none" android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@id/chats_barrier"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/layout_dm_base" /> tools:listitem="@layout/layout_dm_base" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/chats_barrier" android:id="@+id/input_holder"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content">
app:barrierDirection="bottom" />
<View <androidx.constraintlayout.widget.Barrier
android:id="@+id/reply_bg" android:id="@+id/chats_barrier"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@drawable/bg_input" app:barrierDirection="bottom" />
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/reply_info"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView <View
android:id="@+id/reply_info" android:id="@+id/reply_bg"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:ellipsize="end" android:background="@drawable/bg_input"
android:paddingStart="16dp" android:visibility="gone"
android:paddingTop="8dp" app:layout_constraintBottom_toBottomOf="@id/input_bg"
android:paddingEnd="16dp" app:layout_constraintEnd_toEndOf="@id/input_bg"
android:paddingBottom="4dp" app:layout_constraintStart_toStartOf="@id/input_bg"
android:singleLine="true" app:layout_constraintTop_toTopOf="@id/reply_info"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" tools:visibility="gone" />
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:text="Replying to yourself"
tools:visibility="gone" />
<androidx.emoji.widget.EmojiAppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/reply_preview_text" android:id="@+id/reply_info"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:ellipsize="end" android:ellipsize="end"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="4dp" android:paddingTop="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingBottom="8dp" android:paddingBottom="4dp"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/input_bg" app:layout_constraintBottom_toTopOf="@id/reply_preview_text"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image" app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg" app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/reply_info" app:layout_constraintTop_toBottomOf="@id/chats_barrier"
app:layout_goneMarginTop="8dp" tools:text="Replying to yourself"
tools:text="Post" tools:visibility="gone" />
tools:visibility="gone" />
<com.facebook.drawee.view.SimpleDraweeView <androidx.emoji.widget.EmojiAppCompatTextView
android:id="@+id/reply_preview_image" android:id="@+id/reply_preview_text"
android:layout_width="@dimen/dm_inbox_avatar_size_small" android:layout_width="0dp"
android:layout_height="@dimen/dm_inbox_avatar_size_small" android:layout_height="wrap_content"
android:layout_marginEnd="8dp" android:ellipsize="end"
android:visibility="gone" android:paddingStart="16dp"
app:actualImageScaleType="centerCrop" android:paddingTop="4dp"
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text" android:paddingEnd="16dp"
app:layout_constraintEnd_toStartOf="@id/reply_cancel" android:paddingBottom="8dp"
app:layout_constraintStart_toEndOf="@id/reply_preview_text" android:singleLine="true"
app:layout_constraintTop_toTopOf="@id/reply_info" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
tools:background="@mipmap/ic_launcher" android:visibility="gone"
tools:visibility="gone" /> app:layout_constraintBottom_toTopOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/reply_preview_image"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/reply_info"
app:layout_goneMarginTop="8dp"
tools:text="Post"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageView <com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/reply_cancel" android:id="@+id/reply_preview_image"
android:layout_width="24dp" android:layout_width="@dimen/dm_inbox_avatar_size_small"
android:layout_height="24dp" android:layout_height="@dimen/dm_inbox_avatar_size_small"
android:layout_marginEnd="12dp" android:layout_marginEnd="8dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/reply_preview_text" app:actualImageScaleType="centerCrop"
app:layout_constraintEnd_toEndOf="@id/input_bg" app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
app:layout_constraintStart_toEndOf="@id/reply_preview_image" app:layout_constraintEnd_toStartOf="@id/reply_cancel"
app:layout_constraintTop_toTopOf="@id/reply_info" app:layout_constraintStart_toEndOf="@id/reply_preview_text"
app:srcCompat="@drawable/ic_close_24" app:layout_constraintTop_toTopOf="@id/reply_info"
tools:visibility="gone" /> tools:background="@mipmap/ic_launcher"
tools:visibility="gone" />
<!--<androidx.constraintlayout.widget.Group--> <androidx.appcompat.widget.AppCompatImageView
<!-- android:id="@+id/reply_group"--> android:id="@+id/reply_cancel"
<!-- android:layout_width="0dp"--> android:layout_width="24dp"
<!-- android:layout_height="0dp"--> android:layout_height="24dp"
<!-- android:visibility="gone"--> android:layout_marginEnd="12dp"
<!-- app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"--> android:visibility="gone"
<!-- tools:visibility="visible" />--> app:layout_constraintBottom_toBottomOf="@id/reply_preview_text"
app:layout_constraintEnd_toEndOf="@id/input_bg"
app:layout_constraintStart_toEndOf="@id/reply_preview_image"
app:layout_constraintTop_toTopOf="@id/reply_info"
app:srcCompat="@drawable/ic_close_24"
tools:visibility="gone" />
<View <!--<androidx.constraintlayout.widget.Group-->
android:id="@+id/input_bg" <!-- android:id="@+id/reply_group"-->
android:layout_width="0dp" <!-- android:layout_width="0dp"-->
android:layout_height="0dp" <!-- android:layout_height="0dp"-->
android:layout_marginStart="4dp" <!-- android:visibility="gone"-->
android:layout_marginEnd="4dp" <!-- app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"-->
android:background="@drawable/bg_input" <!-- tools:visibility="visible" />-->
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton <View
android:id="@+id/emoji_toggle" android:id="@+id/input_bg"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets" android:layout_width="0dp"
android:layout_width="24dp" android:layout_height="0dp"
android:layout_height="24dp" android:layout_marginStart="4dp"
android:layout_marginStart="8dp" android:layout_marginEnd="4dp"
android:layout_marginEnd="2dp" android:background="@drawable/bg_input"
android:background="@android:color/transparent" android:visibility="gone"
android:scrollbars="none" app:layout_constraintBottom_toBottomOf="@id/input"
android:visibility="gone" app:layout_constraintEnd_toStartOf="@id/send"
app:icon="@drawable/ic_face_24" app:layout_constraintStart_toStartOf="parent"
app:iconGravity="textStart" app:layout_constraintTop_toTopOf="@id/input"
app:iconSize="24dp" tools:visibility="visible" />
app:iconTint="@color/grey_700"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/input"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/input"
app:rippleColor="@color/grey_500"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
app:strokeColor="@color/black"
app:strokeWidth="1dp"
tools:visibility="visible" />
<awais.instagrabber.customviews.KeyNotifyingEmojiEditText <com.google.android.material.button.MaterialButton
android:id="@+id/input" android:id="@+id/emoji_toggle"
android:layout_width="0dp" style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_height="wrap_content" android:layout_width="24dp"
android:layout_marginStart="4dp" android:layout_height="24dp"
android:background="@android:color/transparent" android:layout_marginStart="8dp"
android:hint="@string/message" android:layout_marginEnd="2dp"
android:paddingTop="12dp" android:background="@android:color/transparent"
android:paddingBottom="12dp" android:scrollbars="none"
android:textColor="?dmInputTextColor" android:visibility="gone"
android:textColorHint="@color/grey_500" app:icon="@drawable/ic_face_24"
android:visibility="gone" app:iconGravity="textStart"
app:layout_constraintBottom_toBottomOf="parent" app:iconSize="24dp"
app:layout_constraintEnd_toStartOf="@id/gif" app:iconTint="@color/grey_700"
app:layout_constraintStart_toEndOf="@id/emoji_toggle" app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintTop_toBottomOf="@id/reply_preview_text" app:layout_constraintEnd_toStartOf="@id/input"
app:layout_goneMarginBottom="4dp" app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_goneMarginEnd="24dp" app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" /> app:rippleColor="@color/grey_500"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
app:strokeColor="@color/black"
app:strokeWidth="1dp"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <awais.instagrabber.customviews.KeyNotifyingEmojiEditText
android:id="@+id/gif" android:id="@+id/input"
android:layout_width="32dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="wrap_content"
android:background="@android:color/transparent" android:layout_marginStart="4dp"
android:scaleType="fitCenter" android:background="@android:color/transparent"
android:visibility="gone" android:hint="@string/message"
app:layout_constraintBottom_toBottomOf="@id/input_bg" android:paddingTop="12dp"
app:layout_constraintEnd_toStartOf="@id/camera" android:paddingBottom="12dp"
app:layout_constraintStart_toEndOf="@id/input" android:textColor="?dmInputTextColor"
app:layout_constraintTop_toTopOf="@id/input" android:textColorHint="@color/grey_500"
app:srcCompat="@drawable/ic_round_gif_24" android:visibility="gone"
tools:visibility="visible" /> app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/gif"
app:layout_constraintStart_toEndOf="@id/emoji_toggle"
app:layout_constraintTop_toBottomOf="@id/reply_preview_text"
app:layout_goneMarginBottom="4dp"
app:layout_goneMarginEnd="24dp"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/camera" android:id="@+id/gif"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="4dp" android:background="@android:color/transparent"
android:background="@android:color/transparent" android:scaleType="fitCenter"
android:paddingStart="4dp" android:visibility="gone"
android:paddingEnd="4dp" app:layout_constraintBottom_toBottomOf="@id/input_bg"
android:scaleType="fitCenter" app:layout_constraintEnd_toStartOf="@id/camera"
android:visibility="gone" app:layout_constraintStart_toEndOf="@id/input"
app:layout_constraintBottom_toBottomOf="@id/input_bg" app:layout_constraintTop_toTopOf="@id/input"
app:layout_constraintEnd_toStartOf="@id/gallery" app:srcCompat="@drawable/ic_round_gif_24"
app:layout_constraintStart_toEndOf="@id/gif" tools:visibility="visible" />
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_camera_24"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gallery" android:id="@+id/camera"
android:layout_width="32dp" android:layout_width="32dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginEnd="16dp" android:background="@android:color/transparent"
android:background="@android:color/transparent" android:paddingStart="4dp"
android:paddingStart="4dp" android:paddingEnd="4dp"
android:paddingEnd="4dp" android:scaleType="fitCenter"
android:scaleType="fitCenter" android:visibility="gone"
android:src="@drawable/ic_image_24" app:layout_constraintBottom_toBottomOf="@id/input_bg"
android:visibility="gone" app:layout_constraintEnd_toStartOf="@id/gallery"
app:layout_constraintBottom_toBottomOf="@id/input_bg" app:layout_constraintStart_toEndOf="@id/gif"
app:layout_constraintEnd_toStartOf="@id/send" app:layout_constraintTop_toTopOf="@id/input"
app:layout_constraintStart_toEndOf="@id/camera" app:srcCompat="@drawable/ic_camera_24"
app:layout_constraintTop_toTopOf="@id/input" tools:visibility="visible" />
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordView <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/record_view" android:id="@+id/gallery"
android:layout_width="0dp" android:layout_width="32dp"
android:layout_height="0dp" android:layout_height="0dp"
android:visibility="gone" android:layout_marginStart="4dp"
app:counter_time_color="@color/white" android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="@id/input_bg" android:background="@android:color/transparent"
app:layout_constraintEnd_toEndOf="@id/input_bg" android:paddingStart="4dp"
app:layout_constraintStart_toStartOf="@id/input" android:paddingEnd="4dp"
app:layout_constraintTop_toBottomOf="@id/chats_barrier" android:scaleType="fitCenter"
app:slide_to_cancel_arrow="@drawable/recv_ic_arrow" android:src="@drawable/ic_image_24"
app:slide_to_cancel_arrow_color="@color/white" android:visibility="gone"
app:slide_to_cancel_bounds="0dp" app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:slide_to_cancel_margin_right="16dp" app:layout_constraintEnd_toStartOf="@id/send"
app:slide_to_cancel_text="Slide To Cancel" app:layout_constraintStart_toEndOf="@id/camera"
app:slide_to_cancel_text_color="@color/white" app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" /> tools:visibility="visible" />
<awais.instagrabber.customviews.RecordButton <awais.instagrabber.customviews.RecordView
android:id="@+id/send" android:id="@+id/record_view"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets" android:layout_width="0dp"
android:layout_width="48dp" android:layout_height="0dp"
android:layout_height="48dp" android:visibility="gone"
android:visibility="gone" app:counter_time_color="@color/white"
app:backgroundTint="@color/blue_900" app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:elevation="4dp" app:layout_constraintEnd_toEndOf="@id/input_bg"
app:icon="@drawable/avd_mic_to_send_anim" app:layout_constraintStart_toStartOf="@id/input"
app:iconGravity="textStart" app:layout_constraintTop_toBottomOf="@id/chats_barrier"
app:iconSize="24dp" app:slide_to_cancel_arrow="@drawable/recv_ic_arrow"
app:iconTint="@color/white" app:slide_to_cancel_arrow_color="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg" app:slide_to_cancel_bounds="0dp"
app:layout_constraintEnd_toEndOf="parent" app:slide_to_cancel_margin_right="16dp"
app:layout_constraintStart_toEndOf="@id/input_bg" app:slide_to_cancel_text="Slide To Cancel"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle" app:slide_to_cancel_text_color="@color/white"
tools:visibility="visible" /> tools:visibility="visible" />
<awais.instagrabber.customviews.RecordButton
android:id="@+id/send"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="48dp"
android:layout_height="48dp"
android:visibility="gone"
app:backgroundTint="@color/blue_900"
app:elevation="4dp"
app:icon="@drawable/avd_mic_to_send_anim"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="@color/white"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/input_bg"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="8dp"
android:text="@string/accept_request_from_user"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/decline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/decline"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/accept"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/accept"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>
<awais.instagrabber.customviews.emoji.EmojiPicker <awais.instagrabber.customviews.emoji.EmojiPicker
android:id="@+id/emoji_picker" android:id="@+id/emoji_picker"
android:layout_width="0dp"
android:layout_height="250dp"
android:translationY="250dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="250dp"
android:paddingTop="16dp" android:layout_marginBottom="-250dp"
android:paddingBottom="8dp" android:alpha="0"
android:text="@string/accept_request_from_user"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/decline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/decline"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/red_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/accept"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:padding="16dp"
android:text="@string/accept"
android:textAlignment="center"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/decline" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question" </awais.instagrabber.customviews.InsetsAnimationLinearLayout>
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -11,7 +11,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior"> app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
@ -19,6 +18,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="?actionBarSize"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2" app:spanCount="2"
tools:itemCount="10" tools:itemCount="10"

View File

@ -19,7 +19,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/header"> app:layout_constraintTop_toBottomOf="@id/header">

View File

@ -19,7 +19,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="?attr/actionBarSize"
app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/header"> app:layout_constraintTop_toBottomOf="@id/header">

View File

@ -38,7 +38,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="top" android:layout_gravity="top"
android:layout_marginTop="@dimen/private_page_margins"
android:gravity="center" android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone" android:visibility="gone"
@ -48,6 +47,8 @@
android:id="@+id/privatePage1" android:id="@+id/privatePage1"
android:layout_width="@dimen/private_page_size" android:layout_width="@dimen/private_page_size"
android:layout_height="@dimen/private_page_size" android:layout_height="@dimen/private_page_size"
android:visibility="gone"
tools:visibility="visible"
app:srcCompat="@drawable/lock" /> app:srcCompat="@drawable/lock" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
@ -55,6 +56,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:gravity="center"
android:visibility="gone"
tools:visibility="visible"
android:text="@string/priv_acc" android:text="@string/priv_acc"
android:textAppearance="@style/TextAppearance.AppCompat.Large" /> android:textAppearance="@style/TextAppearance.AppCompat.Large" />
</LinearLayout> </LinearLayout>

View File

@ -43,7 +43,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@+id/title" android:layout_below="@+id/title"
android:ellipsize="end"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textSize="15sp" android:textSize="15sp"
android:visibility="visible" android:visibility="visible"

View File

@ -15,10 +15,11 @@
android:transitionName="profile_pic" android:transitionName="profile_pic"
android:visibility="invisible" android:visibility="invisible"
app:actualImageScaleType="centerCrop" app:actualImageScaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="@id/btnTagged" app:layout_constraintBottom_toTopOf="@id/top_barrier"
app:layout_constraintEnd_toStartOf="@id/btnFollow" app:layout_constraintEnd_toStartOf="@id/btnFollow"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
tools:foreground="@mipmap/ic_launcher" tools:foreground="@mipmap/ic_launcher"
tools:visibility="visible" /> tools:visibility="visible" />
@ -112,7 +113,7 @@
app:chipBackgroundColor="@null" app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_outline_person_pin_24" app:chipIcon="@drawable/ic_outline_person_pin_24"
app:chipIconTint="@color/deep_orange_800" app:chipIconTint="@color/deep_orange_800"
app:layout_constraintBottom_toTopOf="@+id/mainFullName" app:layout_constraintBottom_toTopOf="@+id/top_barrier"
app:layout_constraintStart_toEndOf="@id/mainProfileImage" app:layout_constraintStart_toEndOf="@id/mainProfileImage"
app:layout_constraintTop_toBottomOf="@id/fav_chip" app:layout_constraintTop_toBottomOf="@id/fav_chip"
app:rippleColor="@color/deep_orange_400" app:rippleColor="@color/deep_orange_400"
@ -128,12 +129,18 @@
app:chipBackgroundColor="@null" app:chipBackgroundColor="@null"
app:chipIcon="@drawable/ic_round_send_24" app:chipIcon="@drawable/ic_round_send_24"
app:chipIconTint="@color/green" app:chipIconTint="@color/green"
app:layout_constraintBottom_toTopOf="@+id/mainFullName" app:layout_constraintBottom_toTopOf="@+id/top_barrier"
app:layout_constraintStart_toEndOf="@id/btnTagged" app:layout_constraintStart_toEndOf="@id/btnTagged"
app:layout_constraintTop_toBottomOf="@id/fav_chip" app:layout_constraintTop_toBottomOf="@id/fav_chip"
app:rippleColor="@color/green" app:rippleColor="@color/green"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/top_barrier"
android:layout_width="0dp"
android:layout_height="0dp"
app:barrierDirection="bottom" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
android:id="@+id/mainFullName" android:id="@+id/mainFullName"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@ -146,40 +153,37 @@
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/mainBiography"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnTagged" app:layout_constraintTop_toBottomOf="@id/top_barrier"
tools:text="Austin Huang" /> tools:text="Austin Huang" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isVerified" android:id="@+id/isVerified"
android:layout_width="30dp" android:layout_width="30dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/mainFullName" app:layout_constraintBottom_toBottomOf="@id/mainFullName"
app:layout_constraintStart_toEndOf="@id/mainFullName" app:layout_constraintStart_toEndOf="@id/mainFullName"
app:layout_constraintTop_toBottomOf="@id/btnTagged" app:layout_constraintTop_toTopOf="@id/mainFullName"
app:srcCompat="@drawable/verified" app:srcCompat="@drawable/verified"
tools:visibility="visible" /> tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/isPrivate" android:id="@+id/isPrivate"
android:layout_width="25dp" android:layout_width="25dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:layout_gravity="center" android:layout_gravity="center"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:tint="@color/red_500" android:tint="@color/red_500"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/mainFullName" app:layout_constraintBottom_toBottomOf="@id/mainFullName"
app:layout_constraintStart_toEndOf="@id/isVerified" app:layout_constraintStart_toEndOf="@id/isVerified"
app:layout_constraintTop_toBottomOf="@id/btnTagged" app:layout_constraintTop_toTopOf="@id/mainFullName"
app:srcCompat="@drawable/lock" app:srcCompat="@drawable/lock"
tools:visibility="visible" /> tools:visibility="visible" />
@ -233,6 +237,7 @@
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/profileContext"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainBiography" app:layout_constraintTop_toBottomOf="@id/mainBiography"
@ -254,7 +259,7 @@
android:textSize="12sp" android:textSize="12sp"
android:textStyle="italic" android:textStyle="italic"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/counts_barrier" app:layout_constraintBottom_toTopOf="@id/counts_divider"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/mainUrl" app:layout_constraintTop_toBottomOf="@id/mainUrl"
@ -262,11 +267,12 @@
tools:visibility="visible" /> tools:visibility="visible" />
<include <include
android:id="@+id/counts_barrier" android:id="@+id/counts_divider"
layout="@layout/item_pref_divider" layout="@layout/item_pref_divider"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="1dp" android:layout_height="1dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/mainPostCount"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/profileContext" app:layout_constraintTop_toBottomOf="@id/profileContext"
@ -285,7 +291,7 @@
app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" app:layout_constraintBottom_toBottomOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/mainFollowers" app:layout_constraintEnd_toStartOf="@id/mainFollowers"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/counts_barrier" app:layout_constraintTop_toBottomOf="@id/counts_divider"
tools:text="35\nPosts" /> tools:text="35\nPosts" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
@ -302,7 +308,7 @@
app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" app:layout_constraintBottom_toBottomOf="@id/highlights_barrier"
app:layout_constraintEnd_toStartOf="@id/mainFollowing" app:layout_constraintEnd_toStartOf="@id/mainFollowing"
app:layout_constraintStart_toEndOf="@id/mainPostCount" app:layout_constraintStart_toEndOf="@id/mainPostCount"
app:layout_constraintTop_toBottomOf="@id/counts_barrier" app:layout_constraintTop_toBottomOf="@id/counts_divider"
tools:text="68\nFollowers" /> tools:text="68\nFollowers" />
<androidx.appcompat.widget.AppCompatTextView <androidx.appcompat.widget.AppCompatTextView
@ -319,7 +325,7 @@
app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" app:layout_constraintBottom_toBottomOf="@id/highlights_barrier"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/mainFollowers" app:layout_constraintStart_toEndOf="@id/mainFollowers"
app:layout_constraintTop_toBottomOf="@id/counts_barrier" app:layout_constraintTop_toBottomOf="@id/counts_divider"
tools:text="64\nFollowing" /> tools:text="64\nFollowing" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier

View File

@ -19,9 +19,9 @@
<color name="btn_lightpink_text_color">@color/text_color_light</color> <color name="btn_lightpink_text_color">@color/text_color_light</color>
<color name="btn_orange_background">#FF5500</color> <color name="btn_orange_background">#FF5500</color>
<color name="btn_orange_text_color">#FFFFFFFF</color> <color name="btn_orange_text_color">@color/white</color>
<color name="btn_lightorange_background">#FFBB00</color> <color name="btn_lightorange_background">#FFBB00</color>
<color name="btn_lightorange_text_color">#FF000000</color> <color name="btn_lightorange_text_color">@color/black</color>
<color name="dm_profile_button_color">#efefef</color> <color name="dm_profile_button_color">#efefef</color>

View File

@ -9,6 +9,13 @@
motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/privatePage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/private_page_margins"
android:visibility="visible"
motion:layout_constraintTop_toBottomOf="@+id/header" />
</ConstraintSet> </ConstraintSet>
<ConstraintSet android:id="@+id/end"> <ConstraintSet android:id="@+id/end">
<Constraint <Constraint

View File

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.android.tools.build:gradle:4.2.1'
def nav_version = "2.3.5" def nav_version = "2.3.5"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
} }

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7 distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d
distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists