mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-31 03:25:34 +00:00 
			
		
		
		
	Merge branch 'austinhuang0131:master' into stamatiap/development
This commit is contained in:
		
						commit
						cdb566a160
					
				| @ -79,6 +79,25 @@ | ||||
|         "code" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "stamatiap", | ||||
|       "name": "Stamatia Papageorgiou", | ||||
|       "avatar_url": "https://avatars.githubusercontent.com/u/57223967?v=4", | ||||
|       "profile": "https://github.com/stamatiap", | ||||
|       "contributions": [ | ||||
|         "code", | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "The-EDev", | ||||
|       "name": "Farook Al-Sammarraie", | ||||
|       "avatar_url": "https://avatars.githubusercontent.com/u/60552923?v=4", | ||||
|       "profile": "https://github.com/The-EDev", | ||||
|       "contributions": [ | ||||
|         "code" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "Zopieux", | ||||
|       "name": "Alexandre Macabies", | ||||
|  | ||||
							
								
								
									
										7
									
								
								.github/workflows/github_nightly_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/github_nightly_release.yml
									
									
									
									
										vendored
									
									
								
							| @ -15,10 +15,11 @@ jobs: | ||||
|       uses: actions/checkout@v2 | ||||
|      | ||||
|     - name: set up JDK 1.8 | ||||
|       uses: actions/setup-java@v1 | ||||
|       uses: actions/setup-java@v2 | ||||
|       with: | ||||
|         java-version: 1.8 | ||||
|      | ||||
|         distribution: 'zulu' | ||||
|         java-version: '8' | ||||
| 
 | ||||
|     - name: Grant execute permission for gradlew | ||||
|       run: chmod +x gradlew | ||||
|      | ||||
|  | ||||
							
								
								
									
										5
									
								
								.github/workflows/github_pre_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/github_pre_release.yml
									
									
									
									
										vendored
									
									
								
							| @ -16,9 +16,10 @@ jobs: | ||||
|       uses: actions/checkout@v2 | ||||
| 
 | ||||
|     - name: set up JDK 1.8 | ||||
|       uses: actions/setup-java@v1 | ||||
|       uses: actions/setup-java@v2 | ||||
|       with: | ||||
|         java-version: 1.8 | ||||
|         distribution: 'zulu' | ||||
|         java-version: '8' | ||||
| 
 | ||||
|     - name: Grant execute permission for gradlew | ||||
|       run: chmod +x gradlew | ||||
|  | ||||
							
								
								
									
										28
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								README.md
									
									
									
									
									
								
							| @ -9,7 +9,7 @@ | ||||
| [](http://makeapullrequest.com) | ||||
| [](./LICENSE) | ||||
| [](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> | ||||
| [](#contributors) | ||||
| [](#contributors) | ||||
| <!-- ALL-CONTRIBUTORS-BADGE:END --> | ||||
| 
 | ||||
| Instagram client; previously known as InstaGrabber. | ||||
| @ -63,49 +63,53 @@ Prominent contributors are listed here in the [all-contributors](https://allcont | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/MeLlamoPablo"><img src="https://avatars.githubusercontent.com/u/11708035?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pablo Rodríguez</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=MeLlamoPablo" title="Code">💻</a></td> | ||||
|     <td align="center"><a href="https://github.com/stamatiap"><img src="https://avatars.githubusercontent.com/u/57223967?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stamatia Papageorgiou</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=stamatiap" title="Code">💻</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/The-EDev"><img src="https://avatars.githubusercontent.com/u/60552923?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Farook Al-Sammarraie</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=The-EDev" title="Code">💻</a></td> | ||||
|     <td align="center"><a href="https://github.com/Zopieux"><img src="https://avatars.githubusercontent.com/u/81353?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexandre Macabies</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=Zopieux" title="Code">💻</a></td> | ||||
|     <td align="center"><a href="https://snajdovski.github.io"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td> | ||||
|     <td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td> | ||||
|     <td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="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/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/avtkal"><img src="https://avatars.githubusercontent.com/u/63205014?v=4?s=100" width="100px;" alt=""/><br /><sub><b>avtkal</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/dimitrist19"><img src="https://avatars.githubusercontent.com/u/56406468?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Dimitris T</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/GenosseFlosse"><img src="https://avatars.githubusercontent.com/u/59205524?v=4?s=100" width="100px;" alt=""/><br /><sub><b>GenosseFlosse</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/Pyrobauve"><img src="https://avatars.githubusercontent.com/u/48654473?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Pyrobauve</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://gitlab.com/sandboiii"><img src="https://avatars.githubusercontent.com/u/17468894?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alexey Peschany</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/Sitavi"><img src="https://avatars.githubusercontent.com/u/80586127?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sitavi</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/wokija"><img src="https://avatars.githubusercontent.com/u/14982166?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wokija</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/ZDVokoun"><img src="https://avatars.githubusercontent.com/u/76393152?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ZDVokoun</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|   </tr> | ||||
| @ -121,7 +125,7 @@ This app's predecessor, InstaGrabber, was originally made by [@AwaisKing](https: | ||||
| 
 | ||||
|     Barinsta | ||||
|     Copyright (C) 2020-2021  Austin Huang <im@austinhuang.me> | ||||
|                              Ammar Githam <ammargitham786@gmail.com> | ||||
|                              Ammar Githam <ammar.githam@outlook.com> | ||||
| 
 | ||||
|     This program is free software: you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|  | ||||
| @ -12,7 +12,7 @@ def getGitHash = { -> | ||||
| } | ||||
| 
 | ||||
| android { | ||||
|     compileSdkVersion 29 | ||||
|     compileSdkVersion 30 | ||||
| 
 | ||||
|     defaultConfig { | ||||
|         applicationId 'me.austinhuang.instagrabber' | ||||
| @ -165,8 +165,6 @@ dependencies { | ||||
|     implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version" | ||||
|     implementation "com.google.android.exoplayer:exoplayer-ui:$exoplayer_version" | ||||
| 
 | ||||
|     implementation "androidx.appcompat:appcompat:$appcompat_version" | ||||
|     implementation "androidx.appcompat:appcompat-resources:$appcompat_version" | ||||
|     implementation "androidx.recyclerview:recyclerview:1.2.0" | ||||
|     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' | ||||
|     implementation "androidx.viewpager2:viewpager2:1.0.0" | ||||
| @ -180,6 +178,9 @@ dependencies { | ||||
| 
 | ||||
|     implementation 'com.google.guava:guava:27.0.1-android' | ||||
| 
 | ||||
|     def core_version = "1.6.0-alpha03" | ||||
|     implementation "androidx.core:core:$core_version" | ||||
| 
 | ||||
|     // Room | ||||
|     def room_version = "2.2.6" | ||||
|     implementation "androidx.room:room-runtime:$room_version" | ||||
|  | ||||
| @ -27,8 +27,7 @@ | ||||
|         <activity | ||||
|             android:name=".activities.MainActivity" | ||||
|             android:launchMode="singleTop" | ||||
|             android:taskAffinity=".Main" | ||||
|             android:windowSoftInputMode="adjustResize"> | ||||
|             android:taskAffinity=".Main"> | ||||
|             <intent-filter> | ||||
|                 <action android:name="android.intent.action.MAIN" /> | ||||
|                 <action android:name="android.intent.action.VIEW" /> | ||||
|  | ||||
| @ -31,6 +31,9 @@ import androidx.appcompat.widget.Toolbar; | ||||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | ||||
| import androidx.core.app.NotificationManagerCompat; | ||||
| import androidx.core.provider.FontRequest; | ||||
| import androidx.core.view.ViewCompat; | ||||
| import androidx.core.view.WindowCompat; | ||||
| import androidx.core.view.WindowInsetsCompat; | ||||
| import androidx.emoji.text.EmojiCompat; | ||||
| import androidx.emoji.text.FontRequestEmojiCompatConfig; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
| @ -61,6 +64,7 @@ import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.asyncs.PostFetcher; | ||||
| import awais.instagrabber.customviews.emoji.EmojiVariantManager; | ||||
| import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback; | ||||
| import awais.instagrabber.customviews.helpers.TextWatcherAdapter; | ||||
| import awais.instagrabber.databinding.ActivityMainBinding; | ||||
| import awais.instagrabber.fragments.PostViewV2Fragment; | ||||
| @ -137,11 +141,19 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage | ||||
|         instance = this; | ||||
|         binding = ActivityMainBinding.inflate(getLayoutInflater()); | ||||
|         setupCookie(); | ||||
|         if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) | ||||
|         if (settingsHelper.getBoolean(Constants.FLAG_SECURE)) { | ||||
|             getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); | ||||
|         } | ||||
|         setContentView(binding.getRoot()); | ||||
|         final Toolbar toolbar = binding.toolbar; | ||||
|         setSupportActionBar(toolbar); | ||||
|         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(); | ||||
|         try { | ||||
|             final CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) binding.bottomNavView.getLayoutParams(); | ||||
|  | ||||
| @ -23,23 +23,29 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St | ||||
|     private List<FeedStoryModel> list; | ||||
| 
 | ||||
|     private final Filter filter = new Filter() { | ||||
|         @Nullable | ||||
|         @NonNull | ||||
|         @Override | ||||
|         protected FilterResults performFiltering(final CharSequence filter) { | ||||
|             final boolean isFilterEmpty = TextUtils.isEmpty(filter); | ||||
|             final String query = isFilterEmpty ? null : filter.toString().toLowerCase(); | ||||
| 
 | ||||
|             for (FeedStoryModel item : list) { | ||||
|                 if (isFilterEmpty) item.setShown(true); | ||||
|                 else item.setShown(item.getProfileModel().getUsername().toLowerCase().contains(query)); | ||||
|             final String query = TextUtils.isEmpty(filter) ? null : filter.toString().toLowerCase(); | ||||
|             List<FeedStoryModel> filteredList = list; | ||||
|             if (list != null && query != null) { | ||||
|                 filteredList = list.stream() | ||||
|                                    .filter(feedStoryModel -> feedStoryModel.getProfileModel() | ||||
|                                                                            .getUsername() | ||||
|                                                                            .toLowerCase() | ||||
|                                                                            .contains(query)) | ||||
|                                    .collect(Collectors.toList()); | ||||
|             } | ||||
|             return null; | ||||
|             final FilterResults filterResults = new FilterResults(); | ||||
|             filterResults.count = filteredList != null ? filteredList.size() : 0; | ||||
|             filterResults.values = filteredList; | ||||
|             return filterResults; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         protected void publishResults(final CharSequence constraint, final FilterResults results) { | ||||
|             submitList(list); | ||||
|             notifyDataSetChanged(); | ||||
|             //noinspection unchecked | ||||
|             submitList((List<FeedStoryModel>) results.values, true); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| @ -65,10 +71,16 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St | ||||
|         return filter; | ||||
|     } | ||||
| 
 | ||||
|     private void submitList(@Nullable final List<FeedStoryModel> list, final boolean isFiltered) { | ||||
|         if (!isFiltered) { | ||||
|             this.list = list; | ||||
|         } | ||||
|         super.submitList(list); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void submitList(final List<FeedStoryModel> list) { | ||||
|         super.submitList(list.stream().filter(i -> i.isShown()).collect(Collectors.toList())); | ||||
|         this.list = list; | ||||
|         submitList(list, false); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
| @ -82,11 +94,11 @@ public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, St | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { | ||||
|         final FeedStoryModel model = getItem(position); | ||||
|         holder.bind(model, position, listener); | ||||
|         holder.bind(model, listener); | ||||
|     } | ||||
| 
 | ||||
|     public interface OnFeedStoryClickListener { | ||||
|         void onFeedStoryClick(final FeedStoryModel model, final int position); | ||||
|         void onFeedStoryClick(final FeedStoryModel model); | ||||
| 
 | ||||
|         void onProfileClick(final String username); | ||||
|     } | ||||
|  | ||||
| @ -20,7 +20,6 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder { | ||||
|     } | ||||
| 
 | ||||
|     public void bind(final FeedStoryModel model, | ||||
|                      final int position, | ||||
|                      final OnFeedStoryClickListener notificationClickListener) { | ||||
|         if (model == null) return; | ||||
| 
 | ||||
| @ -53,7 +52,7 @@ public final class StoryListViewHolder extends RecyclerView.ViewHolder { | ||||
| 
 | ||||
|         itemView.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onFeedStoryClick(model, position); | ||||
|             notificationClickListener.onFeedStoryClick(model); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,246 @@ | ||||
| package awais.instagrabber.customviews; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.os.Build; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.View; | ||||
| import android.view.WindowInsetsAnimation; | ||||
| import android.widget.LinearLayout; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.view.NestedScrollingParent3; | ||||
| import androidx.core.view.NestedScrollingParentHelper; | ||||
| import androidx.core.view.ViewCompat; | ||||
| import androidx.core.view.WindowInsetsCompat; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| import awais.instagrabber.customviews.helpers.SimpleImeAnimationController; | ||||
| import awais.instagrabber.utils.ViewUtils; | ||||
| 
 | ||||
| import static androidx.core.view.ViewCompat.TYPE_TOUCH; | ||||
| 
 | ||||
| public final class InsetsAnimationLinearLayout extends LinearLayout implements NestedScrollingParent3 { | ||||
|     private final NestedScrollingParentHelper nestedScrollingParentHelper = new NestedScrollingParentHelper(this); | ||||
|     private final SimpleImeAnimationController imeAnimController = new SimpleImeAnimationController(); | ||||
|     private final int[] tempIntArray2 = new int[2]; | ||||
|     private final int[] startViewLocation = new int[2]; | ||||
| 
 | ||||
|     private View currentNestedScrollingChild; | ||||
|     private int dropNextY; | ||||
|     private boolean scrollImeOffScreenWhenVisible = true; | ||||
|     private boolean scrollImeOnScreenWhenNotVisible = true; | ||||
|     private boolean scrollImeOffScreenWhenVisibleOnFling = false; | ||||
|     private boolean scrollImeOnScreenWhenNotVisibleOnFling = false; | ||||
| 
 | ||||
|     public InsetsAnimationLinearLayout(final Context context, @Nullable final AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|     } | ||||
| 
 | ||||
|     public InsetsAnimationLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|     } | ||||
| 
 | ||||
|     public final boolean getScrollImeOffScreenWhenVisible() { | ||||
|         return scrollImeOffScreenWhenVisible; | ||||
|     } | ||||
| 
 | ||||
|     public final void setScrollImeOffScreenWhenVisible(boolean scrollImeOffScreenWhenVisible) { | ||||
|         this.scrollImeOffScreenWhenVisible = scrollImeOffScreenWhenVisible; | ||||
|     } | ||||
| 
 | ||||
|     public final boolean getScrollImeOnScreenWhenNotVisible() { | ||||
|         return scrollImeOnScreenWhenNotVisible; | ||||
|     } | ||||
| 
 | ||||
|     public final void setScrollImeOnScreenWhenNotVisible(boolean scrollImeOnScreenWhenNotVisible) { | ||||
|         this.scrollImeOnScreenWhenNotVisible = scrollImeOnScreenWhenNotVisible; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getScrollImeOffScreenWhenVisibleOnFling() { | ||||
|         return scrollImeOffScreenWhenVisibleOnFling; | ||||
|     } | ||||
| 
 | ||||
|     public void setScrollImeOffScreenWhenVisibleOnFling(final boolean scrollImeOffScreenWhenVisibleOnFling) { | ||||
|         this.scrollImeOffScreenWhenVisibleOnFling = scrollImeOffScreenWhenVisibleOnFling; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getScrollImeOnScreenWhenNotVisibleOnFling() { | ||||
|         return scrollImeOnScreenWhenNotVisibleOnFling; | ||||
|     } | ||||
| 
 | ||||
|     public void setScrollImeOnScreenWhenNotVisibleOnFling(final boolean scrollImeOnScreenWhenNotVisibleOnFling) { | ||||
|         this.scrollImeOnScreenWhenNotVisibleOnFling = scrollImeOnScreenWhenNotVisibleOnFling; | ||||
|     } | ||||
| 
 | ||||
|     public SimpleImeAnimationController getImeAnimController() { | ||||
|         return imeAnimController; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onStartNestedScroll(@NonNull final View child, | ||||
|                                        @NonNull final View target, | ||||
|                                        final int axes, | ||||
|                                        final int type) { | ||||
|         return (axes & SCROLL_AXIS_VERTICAL) != 0 && type == TYPE_TOUCH; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNestedScrollAccepted(@NonNull final View child, | ||||
|                                        @NonNull final View target, | ||||
|                                        final int axes, | ||||
|                                        final int type) { | ||||
|         nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type); | ||||
|         currentNestedScrollingChild = child; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNestedPreScroll(@NonNull final View target, | ||||
|                                   final int dx, | ||||
|                                   final int dy, | ||||
|                                   @NonNull final int[] consumed, | ||||
|                                   final int type) { | ||||
|         if (imeAnimController.isInsetAnimationRequestPending()) { | ||||
|             consumed[0] = dx; | ||||
|             consumed[1] = dy; | ||||
|         } else { | ||||
|             int deltaY = dy; | ||||
|             if (dropNextY != 0) { | ||||
|                 consumed[1] = dropNextY; | ||||
|                 deltaY = dy - dropNextY; | ||||
|                 dropNextY = 0; | ||||
|             } | ||||
| 
 | ||||
|             if (deltaY < 0) { | ||||
|                 if (imeAnimController.isInsetAnimationInProgress()) { | ||||
|                     consumed[1] -= imeAnimController.insetBy(-deltaY); | ||||
|                 } else if (scrollImeOffScreenWhenVisible && !imeAnimController.isInsetAnimationRequestPending()) { | ||||
|                     WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this); | ||||
|                     if (rootWindowInsets != null) { | ||||
|                         if (rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) { | ||||
|                             startControlRequest(); | ||||
|                             consumed[1] = deltaY; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNestedScroll(@NonNull final View target, | ||||
|                                final int dxConsumed, | ||||
|                                final int dyConsumed, | ||||
|                                final int dxUnconsumed, | ||||
|                                final int dyUnconsumed, | ||||
|                                final int type, | ||||
|                                @NonNull final int[] consumed) { | ||||
|         if (dyUnconsumed > 0) { | ||||
|             if (imeAnimController.isInsetAnimationInProgress()) { | ||||
|                 consumed[1] = -imeAnimController.insetBy(-dyUnconsumed); | ||||
|             } else if (scrollImeOnScreenWhenNotVisible && !imeAnimController.isInsetAnimationRequestPending()) { | ||||
|                 WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this); | ||||
|                 if (rootWindowInsets != null) { | ||||
|                     if (!rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) { | ||||
|                         startControlRequest(); | ||||
|                         consumed[1] = dyUnconsumed; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onNestedFling(@NonNull final View target, | ||||
|                                  final float velocityX, | ||||
|                                  final float velocityY, | ||||
|                                  final boolean consumed) { | ||||
|         if (imeAnimController.isInsetAnimationInProgress()) { | ||||
|             imeAnimController.animateToFinish(velocityY); | ||||
|             return true; | ||||
|         } else { | ||||
|             boolean imeVisible = false; | ||||
|             final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(this); | ||||
|             if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime())) { | ||||
|                 imeVisible = true; | ||||
|             } | ||||
|             if (velocityY > 0 && scrollImeOnScreenWhenNotVisibleOnFling && !imeVisible) { | ||||
|                 imeAnimController.startAndFling(this, velocityY); | ||||
|                 return true; | ||||
|             } else if (velocityY < 0 && scrollImeOffScreenWhenVisibleOnFling && imeVisible) { | ||||
|                 imeAnimController.startAndFling(this, velocityY); | ||||
|                 return true; | ||||
|             } else { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStopNestedScroll(@NonNull final View target, final int type) { | ||||
|         nestedScrollingParentHelper.onStopNestedScroll(target, type); | ||||
|         if (imeAnimController.isInsetAnimationInProgress() && !imeAnimController.isInsetAnimationFinishing()) { | ||||
|             imeAnimController.animateToFinish(null); | ||||
|         } | ||||
|         reset(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void dispatchWindowInsetsAnimationPrepare(@NonNull final WindowInsetsAnimation animation) { | ||||
|         super.dispatchWindowInsetsAnimationPrepare(animation); | ||||
|         ViewUtils.suppressLayoutCompat(this, false); | ||||
|     } | ||||
| 
 | ||||
|     private void startControlRequest() { | ||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { | ||||
|             return; | ||||
|         } | ||||
|         ViewUtils.suppressLayoutCompat(this, true); | ||||
|         if (currentNestedScrollingChild != null) { | ||||
|             currentNestedScrollingChild.getLocationInWindow(startViewLocation); | ||||
|         } | ||||
|         imeAnimController.startControlRequest(this, windowInsetsAnimationControllerCompat -> onControllerReady()); | ||||
|     } | ||||
| 
 | ||||
|     private void onControllerReady() { | ||||
|         if (currentNestedScrollingChild != null) { | ||||
|             imeAnimController.insetBy(0); | ||||
|             int[] location = tempIntArray2; | ||||
|             currentNestedScrollingChild.getLocationInWindow(location); | ||||
|             dropNextY = location[1] - startViewLocation[1]; | ||||
|         } | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private void reset() { | ||||
|         dropNextY = 0; | ||||
|         Arrays.fill(startViewLocation, 0); | ||||
|         ViewUtils.suppressLayoutCompat(this, false); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNestedScrollAccepted(@NonNull final View child, | ||||
|                                        @NonNull final View target, | ||||
|                                        final int axes) { | ||||
|         onNestedScrollAccepted(child, target, axes, TYPE_TOUCH); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onNestedScroll(@NonNull final View target, | ||||
|                                final int dxConsumed, | ||||
|                                final int dyConsumed, | ||||
|                                final int dxUnconsumed, | ||||
|                                final int dyUnconsumed, | ||||
|                                final int type) { | ||||
|         onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, tempIntArray2); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onStopNestedScroll(@NonNull final View target) { | ||||
|         onStopNestedScroll(target, TYPE_TOUCH); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -0,0 +1,33 @@ | ||||
| package awais.instagrabber.customviews; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.WindowInsets; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | ||||
| 
 | ||||
| public class InsetsNotifyingCoordinatorLayout extends CoordinatorLayout { | ||||
| 
 | ||||
|     public InsetsNotifyingCoordinatorLayout(@NonNull final Context context) { | ||||
|         super(context); | ||||
|     } | ||||
| 
 | ||||
|     public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|     } | ||||
| 
 | ||||
|     public InsetsNotifyingCoordinatorLayout(@NonNull final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public WindowInsets onApplyWindowInsets(WindowInsets insets) { | ||||
|         int childCount = getChildCount(); | ||||
|         for (int index = 0; index < childCount; index++) { | ||||
|             getChildAt(index).dispatchApplyWindowInsets(insets); | ||||
|         } | ||||
|         return insets; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,35 @@ | ||||
| package awais.instagrabber.customviews; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.util.AttributeSet; | ||||
| import android.view.WindowInsets; | ||||
| import android.widget.LinearLayout; | ||||
| 
 | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| public class InsetsNotifyingLinearLayout extends LinearLayout { | ||||
|     public InsetsNotifyingLinearLayout(final Context context) { | ||||
|         super(context); | ||||
|     } | ||||
| 
 | ||||
|     public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs) { | ||||
|         super(context, attrs); | ||||
|     } | ||||
| 
 | ||||
|     public InsetsNotifyingLinearLayout(final Context context, @Nullable final AttributeSet attrs, final int defStyleAttr) { | ||||
|         super(context, attrs, defStyleAttr); | ||||
|     } | ||||
| 
 | ||||
|     public InsetsNotifyingLinearLayout(final Context context, final AttributeSet attrs, final int defStyleAttr, final int defStyleRes) { | ||||
|         super(context, attrs, defStyleAttr, defStyleRes); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public WindowInsets onApplyWindowInsets(WindowInsets insets) { | ||||
|         int childCount = getChildCount(); | ||||
|         for (int index = 0; index < childCount; index++) { | ||||
|             getChildAt(index).dispatchApplyWindowInsets(insets); | ||||
|         } | ||||
|         return insets; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,139 @@ | ||||
| package awais.instagrabber.customviews.helpers;/* | ||||
|  * Copyright 2020 The Android Open Source Project | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| import android.view.View; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.graphics.Insets; | ||||
| import androidx.core.view.OnApplyWindowInsetsListener; | ||||
| import androidx.core.view.ViewCompat; | ||||
| import androidx.core.view.WindowInsetsAnimationCompat; | ||||
| import androidx.core.view.WindowInsetsCompat; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * A class which extends/implements both [WindowInsetsAnimationCompat.Callback] and | ||||
|  * [View.OnApplyWindowInsetsListener], which should be set on the root view in your layout. | ||||
|  * <p> | ||||
|  * This class enables the root view is selectively defer handling any insets which match | ||||
|  * [deferredInsetTypes], to enable better looking [WindowInsetsAnimationCompat]s. | ||||
|  * <p> | ||||
|  * An example is the following: when a [WindowInsetsAnimationCompat] is started, the system will dispatch | ||||
|  * a [WindowInsetsCompat] instance which contains the end state of the animation. For the scenario of | ||||
|  * the IME being animated in, that means that the insets contains the IME height. If the view's | ||||
|  * [View.OnApplyWindowInsetsListener] simply always applied the combination of | ||||
|  * [WindowInsetsCompat.Type.ime] and [WindowInsetsCompat.Type.systemBars] using padding, the viewport of any | ||||
|  * child views would then be smaller. This results in us animating a smaller (padded-in) view into | ||||
|  * a larger viewport. Visually, this results in the views looking clipped. | ||||
|  * <p> | ||||
|  * This class allows us to implement a different strategy for the above scenario, by selectively | ||||
|  * deferring the [WindowInsetsCompat.Type.ime] insets until the [WindowInsetsAnimationCompat] is ended. | ||||
|  * For the above example, you would create a [RootViewDeferringInsetsCallback] like so: | ||||
|  * <p> | ||||
|  * ``` | ||||
|  * val callback = RootViewDeferringInsetsCallback( | ||||
|  * persistentInsetTypes = WindowInsetsCompat.Type.systemBars(), | ||||
|  * deferredInsetTypes = WindowInsetsCompat.Type.ime() | ||||
|  * ) | ||||
|  * ``` | ||||
|  * <p> | ||||
|  * This class is not limited to just IME animations, and can work with any [WindowInsetsCompat.Type]s. | ||||
|  */ | ||||
| public class RootViewDeferringInsetsCallback extends WindowInsetsAnimationCompat.Callback implements OnApplyWindowInsetsListener { | ||||
| 
 | ||||
|     private final int persistentInsetTypes; | ||||
|     private final int deferredInsetTypes; | ||||
|     @Nullable | ||||
|     private View view = null; | ||||
|     @Nullable | ||||
|     private WindowInsetsCompat lastWindowInsets = null; | ||||
|     private boolean deferredInsets = false; | ||||
| 
 | ||||
|     /** | ||||
|      * @param persistentInsetTypes the bitmask of any inset types which should always be handled | ||||
|      *                             through padding the attached view | ||||
|      * @param deferredInsetTypes   the bitmask of insets types which should be deferred until after | ||||
|      *                             any related [WindowInsetsAnimationCompat]s have ended | ||||
|      */ | ||||
|     public RootViewDeferringInsetsCallback(final int persistentInsetTypes, final int deferredInsetTypes) { | ||||
|         super(DISPATCH_MODE_CONTINUE_ON_SUBTREE); | ||||
|         if ((persistentInsetTypes & deferredInsetTypes) != 0) { | ||||
|             throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " + | ||||
|                                                        "any of same WindowInsetsCompat.Type values"); | ||||
|         } | ||||
|         this.persistentInsetTypes = persistentInsetTypes; | ||||
|         this.deferredInsetTypes = deferredInsetTypes; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public WindowInsetsCompat onApplyWindowInsets(@NonNull final View v, @NonNull final WindowInsetsCompat windowInsets) { | ||||
|         // Store the view and insets for us in onEnd() below | ||||
|         view = v; | ||||
|         lastWindowInsets = windowInsets; | ||||
| 
 | ||||
|         final int types = deferredInsets | ||||
|                           // When the deferred flag is enabled, we only use the systemBars() insets | ||||
|                           ? persistentInsetTypes | ||||
|                           // Otherwise we handle the combination of the the systemBars() and ime() insets | ||||
|                           : persistentInsetTypes | deferredInsetTypes; | ||||
| 
 | ||||
|         // Finally we apply the resolved insets by setting them as padding | ||||
|         final Insets typeInsets = windowInsets.getInsets(types); | ||||
|         v.setPadding(typeInsets.left, typeInsets.top, typeInsets.right, typeInsets.bottom); | ||||
| 
 | ||||
|         // We return the new WindowInsetsCompat.CONSUMED to stop the insets being dispatched any | ||||
|         // further into the view hierarchy. This replaces the deprecated | ||||
|         // WindowInsetsCompat.consumeSystemWindowInsets() and related functions. | ||||
|         return WindowInsetsCompat.CONSUMED; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onPrepare(WindowInsetsAnimationCompat animation) { | ||||
|         if ((animation.getTypeMask() & deferredInsetTypes) != 0) { | ||||
|             // We defer the WindowInsetsCompat.Type.ime() insets if the IME is currently not visible. | ||||
|             // This results in only the WindowInsetsCompat.Type.systemBars() being applied, allowing | ||||
|             // the scrolling view to remain at it's larger size. | ||||
|             deferredInsets = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets, | ||||
|                                          @NonNull final List<WindowInsetsAnimationCompat> runningAnims) { | ||||
|         // This is a no-op. We don't actually want to handle any WindowInsetsAnimations | ||||
|         return insets; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) { | ||||
|         if (deferredInsets && (animation.getTypeMask() & deferredInsetTypes) != 0) { | ||||
|             // If we deferred the IME insets and an IME animation has finished, we need to reset | ||||
|             // the flag | ||||
|             deferredInsets = false; | ||||
| 
 | ||||
|             // And finally dispatch the deferred insets to the view now. | ||||
|             // Ideally we would just call view.requestApplyInsets() and let the normal dispatch | ||||
|             // cycle happen, but this happens too late resulting in a visual flicker. | ||||
|             // Instead we manually dispatch the most recent WindowInsets to the view. | ||||
|             if (lastWindowInsets != null && view != null) { | ||||
|                 ViewCompat.dispatchApplyWindowInsets(view, lastWindowInsets); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,443 @@ | ||||
| /* | ||||
|  * Copyright 2020 The Android Open Source Project | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| package awais.instagrabber.customviews.helpers; | ||||
| 
 | ||||
| import android.os.CancellationSignal; | ||||
| import android.util.Log; | ||||
| import android.view.View; | ||||
| import android.view.animation.LinearInterpolator; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.graphics.Insets; | ||||
| import androidx.core.view.ViewCompat; | ||||
| import androidx.core.view.WindowInsetsAnimationControlListenerCompat; | ||||
| import androidx.core.view.WindowInsetsAnimationControllerCompat; | ||||
| import androidx.core.view.WindowInsetsCompat; | ||||
| import androidx.core.view.WindowInsetsControllerCompat; | ||||
| import androidx.dynamicanimation.animation.FloatPropertyCompat; | ||||
| import androidx.dynamicanimation.animation.SpringAnimation; | ||||
| import androidx.dynamicanimation.animation.SpringForce; | ||||
| 
 | ||||
| import awais.instagrabber.utils.ViewUtils; | ||||
| 
 | ||||
| /** | ||||
|  * A wrapper around the [WindowInsetsAnimationControllerCompat] APIs in AndroidX Core, to simplify | ||||
|  * the implementation of common use-cases around the IME. | ||||
|  * <p> | ||||
|  * See [InsetsAnimationLinearLayout] and [InsetsAnimationTouchListener] for examples of how | ||||
|  * to use this class. | ||||
|  */ | ||||
| public class SimpleImeAnimationController { | ||||
|     private static final String TAG = SimpleImeAnimationController.class.getSimpleName(); | ||||
|     /** | ||||
|      * Scroll threshold for determining whether to animating to the end state, or to the start state. | ||||
|      * Currently 15% of the total swipe distance distance | ||||
|      */ | ||||
|     private static final float SCROLL_THRESHOLD = 0.15f; | ||||
| 
 | ||||
|     @Nullable | ||||
|     private WindowInsetsAnimationControllerCompat insetsAnimationController = null; | ||||
|     @Nullable | ||||
|     private CancellationSignal pendingRequestCancellationSignal = null; | ||||
|     @Nullable | ||||
|     private OnRequestReadyListener pendingRequestOnReadyListener; | ||||
|     /** | ||||
|      * True if the IME was shown at the start of the current animation. | ||||
|      */ | ||||
|     private boolean isImeShownAtStart = false; | ||||
|     @Nullable | ||||
|     private SpringAnimation currentSpringAnimation = null; | ||||
|     private WindowInsetsAnimationControlListenerCompat fwdListener; | ||||
| 
 | ||||
|     /** | ||||
|      * A LinearInterpolator instance we can re-use across listeners. | ||||
|      */ | ||||
|     private final LinearInterpolator linearInterpolator = new LinearInterpolator(); | ||||
|     /* To take control of the an WindowInsetsAnimation, we need to pass in a listener to | ||||
|        controlWindowInsetsAnimation() in startControlRequest(). The listener created here | ||||
|        keeps track of the current WindowInsetsAnimationController and resets our state. */ | ||||
|     private final WindowInsetsAnimationControlListenerCompat animationControlListener = new WindowInsetsAnimationControlListenerCompat() { | ||||
|         /** | ||||
|          * Once the request is ready, call our [onRequestReady] function | ||||
|          */ | ||||
|         @Override | ||||
|         public void onReady(@NonNull final WindowInsetsAnimationControllerCompat controller, final int types) { | ||||
|             onRequestReady(controller); | ||||
|             if (fwdListener != null) { | ||||
|                 fwdListener.onReady(controller, types); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * If the request is finished, we should reset our internal state | ||||
|          */ | ||||
|         @Override | ||||
|         public void onFinished(@NonNull final WindowInsetsAnimationControllerCompat controller) { | ||||
|             reset(); | ||||
|             if (fwdListener != null) { | ||||
|                 fwdListener.onFinished(controller); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * If the request is cancelled, we should reset our internal state | ||||
|          */ | ||||
|         @Override | ||||
|         public void onCancelled(@Nullable final WindowInsetsAnimationControllerCompat controller) { | ||||
|             reset(); | ||||
|             if (fwdListener != null) { | ||||
|                 fwdListener.onCancelled(controller); | ||||
|             } | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     /** | ||||
|      * Start a control request to the [view]s [android.view.WindowInsetsController]. This should | ||||
|      * be called once the view is in a position to take control over the position of the IME. | ||||
|      * | ||||
|      * @param view                   The view which is triggering this request | ||||
|      * @param onRequestReadyListener optional listener which will be called when the request is ready and | ||||
|      *                               the animation can proceed | ||||
|      */ | ||||
|     public void startControlRequest(@NonNull final View view, | ||||
|                                     @Nullable final OnRequestReadyListener onRequestReadyListener) { | ||||
|         if (isInsetAnimationInProgress()) { | ||||
|             Log.w(TAG, "startControlRequest: Animation in progress. Can not start a new request to controlWindowInsetsAnimation()"); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Keep track of the IME insets, and the IME visibility, at the start of the request | ||||
|         final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view); | ||||
|         if (rootWindowInsets != null) { | ||||
|             isImeShownAtStart = rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime()); | ||||
|         } | ||||
| 
 | ||||
|         // Create a cancellation signal, which we pass to controlWindowInsetsAnimation() below | ||||
|         pendingRequestCancellationSignal = new CancellationSignal(); | ||||
|         // Keep reference to the onReady callback | ||||
|         pendingRequestOnReadyListener = onRequestReadyListener; | ||||
| 
 | ||||
|         // Finally we make a controlWindowInsetsAnimation() request: | ||||
|         final WindowInsetsControllerCompat windowInsetsController = ViewCompat.getWindowInsetsController(view); | ||||
|         if (windowInsetsController != null) { | ||||
|             windowInsetsController.controlWindowInsetsAnimation( | ||||
|                     // We're only catering for IME animations in this listener | ||||
|                     WindowInsetsCompat.Type.ime(), | ||||
|                     // Animation duration. This is not used by the system, and is only passed to any | ||||
|                     // WindowInsetsAnimation.Callback set on views. We pass in -1 to indicate that we're | ||||
|                     // not starting a finite animation, and that this is completely controlled by | ||||
|                     // the user's touch. | ||||
|                     -1, | ||||
|                     // The time interpolator used in calculating the animation progress. The fraction value | ||||
|                     // we passed into setInsetsAndAlpha() which be passed into this interpolator before | ||||
|                     // being used by the system to inset the IME. LinearInterpolator is a good type | ||||
|                     // to use for scrolling gestures. | ||||
|                     linearInterpolator, | ||||
|                     // A cancellation signal, which allows us to cancel the request to control | ||||
|                     pendingRequestCancellationSignal, | ||||
|                     // The WindowInsetsAnimationControlListener | ||||
|                     animationControlListener | ||||
|             ); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Start a control request to the [view]s [android.view.WindowInsetsController], similar to | ||||
|      * [startControlRequest], but immediately fling to a finish using [velocityY] once ready. | ||||
|      * <p> | ||||
|      * This function is useful for fire-and-forget operations to animate the IME. | ||||
|      * | ||||
|      * @param view      The view which is triggering this request | ||||
|      * @param velocityY the velocity of the touch gesture which caused this call | ||||
|      */ | ||||
|     public void startAndFling(@NonNull final View view, final float velocityY) { | ||||
|         startControlRequest(view, null); | ||||
|         animateToFinish(velocityY); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the inset position of the IME by the given [dy] value. This value will be coerced | ||||
|      * into the hidden and shown inset values. | ||||
|      * <p> | ||||
|      * This function should only be called if [isInsetAnimationInProgress] returns true. | ||||
|      * | ||||
|      * @return the amount of [dy] consumed by the inset animation, in pixels | ||||
|      */ | ||||
|     public int insetBy(final int dy) { | ||||
|         if (insetsAnimationController == null) { | ||||
|             throw new IllegalStateException("Current WindowInsetsAnimationController is null." + | ||||
|                                                     "This should only be called if isAnimationInProgress() returns true"); | ||||
|         } | ||||
|         final WindowInsetsAnimationControllerCompat controller = insetsAnimationController; | ||||
| 
 | ||||
|         // Call updateInsetTo() with the new inset value | ||||
|         return insetTo(controller.getCurrentInsets().bottom - dy); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the inset position of the IME to be the given [inset] value. This value will be | ||||
|      * coerced into the hidden and shown inset values. | ||||
|      * <p> | ||||
|      * This function should only be called if [isInsetAnimationInProgress] returns true. | ||||
|      * | ||||
|      * @return the distance moved by the inset animation, in pixels | ||||
|      */ | ||||
|     public int insetTo(final int inset) { | ||||
|         if (insetsAnimationController == null) { | ||||
|             throw new IllegalStateException("Current WindowInsetsAnimationController is null." + | ||||
|                                                     "This should only be called if isAnimationInProgress() returns true"); | ||||
|         } | ||||
|         final WindowInsetsAnimationControllerCompat controller = insetsAnimationController; | ||||
| 
 | ||||
|         final int hiddenBottom = controller.getHiddenStateInsets().bottom; | ||||
|         final int shownBottom = controller.getShownStateInsets().bottom; | ||||
|         final int startBottom = isImeShownAtStart ? shownBottom : hiddenBottom; | ||||
|         final int endBottom = isImeShownAtStart ? hiddenBottom : shownBottom; | ||||
| 
 | ||||
|         // We coerce the given inset within the limits of the hidden and shown insets | ||||
|         final int coercedBottom = coerceIn(inset, hiddenBottom, shownBottom); | ||||
| 
 | ||||
|         final int consumedDy = controller.getCurrentInsets().bottom - coercedBottom; | ||||
| 
 | ||||
|         // Finally update the insets in the WindowInsetsAnimationController using | ||||
|         // setInsetsAndAlpha(). | ||||
|         controller.setInsetsAndAlpha( | ||||
|                 // Here we update the animating insets. This is what controls where the IME is displayed. | ||||
|                 // It is also passed through to views via their WindowInsetsAnimation.Callback. | ||||
|                 Insets.of(0, 0, 0, coercedBottom), | ||||
|                 // This controls the alpha value. We don't want to alter the alpha so use 1f | ||||
|                 1f, | ||||
|                 // Finally we calculate the animation progress fraction. This value is passed through | ||||
|                 // to any WindowInsetsAnimation.Callbacks, but it is not used by the system. | ||||
|                 (coercedBottom - startBottom) / (float) (endBottom - startBottom) | ||||
|         ); | ||||
| 
 | ||||
|         return consumedDy; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return `true` if an inset animation is in progress. | ||||
|      */ | ||||
|     public boolean isInsetAnimationInProgress() { | ||||
|         return insetsAnimationController != null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return `true` if an inset animation is currently finishing. | ||||
|      */ | ||||
|     public boolean isInsetAnimationFinishing() { | ||||
|         return currentSpringAnimation != null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return `true` if a request to control an inset animation is in progress. | ||||
|      */ | ||||
|     public boolean isInsetAnimationRequestPending() { | ||||
|         return pendingRequestCancellationSignal != null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Cancel the current [WindowInsetsAnimationControllerCompat]. We immediately finish | ||||
|      * the animation, reverting back to the state at the start of the gesture. | ||||
|      */ | ||||
|     public void cancel() { | ||||
|         if (insetsAnimationController != null) { | ||||
|             insetsAnimationController.finish(isImeShownAtStart); | ||||
|         } | ||||
|         if (pendingRequestCancellationSignal != null) { | ||||
|             pendingRequestCancellationSignal.cancel(); | ||||
|         } | ||||
|         if (currentSpringAnimation != null) { | ||||
|             // Cancel the current spring animation | ||||
|             currentSpringAnimation.cancel(); | ||||
|         } | ||||
|         reset(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Finish the current [WindowInsetsAnimationControllerCompat] immediately. | ||||
|      */ | ||||
|     public void finish() { | ||||
|         final WindowInsetsAnimationControllerCompat controller = insetsAnimationController; | ||||
| 
 | ||||
|         if (controller == null) { | ||||
|             // If we don't currently have a controller, cancel any pending request and return | ||||
|             if (pendingRequestCancellationSignal != null) { | ||||
|                 pendingRequestCancellationSignal.cancel(); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final int current = controller.getCurrentInsets().bottom; | ||||
|         final int shown = controller.getShownStateInsets().bottom; | ||||
|         final int hidden = controller.getHiddenStateInsets().bottom; | ||||
| 
 | ||||
|         // The current inset matches either the shown/hidden inset, finish() immediately | ||||
|         if (current == shown) { | ||||
|             controller.finish(true); | ||||
|         } else if (current == hidden) { | ||||
|             controller.finish(false); | ||||
|         } else { | ||||
|             // Otherwise, we'll look at the current position... | ||||
|             if (controller.getCurrentFraction() >= SCROLL_THRESHOLD) { | ||||
|                 // If the IME is past the 'threshold' we snap to the toggled state | ||||
|                 controller.finish(!isImeShownAtStart); | ||||
|             } else { | ||||
|                 // ...otherwise, we snap back to the original visibility | ||||
|                 controller.finish(isImeShownAtStart); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Finish the current [WindowInsetsAnimationControllerCompat]. We finish the animation, | ||||
|      * animating to the end state if necessary. | ||||
|      * | ||||
|      * @param velocityY the velocity of the touch gesture which caused this call to [animateToFinish]. | ||||
|      *                  Can be `null` if velocity is not available. | ||||
|      */ | ||||
|     public void animateToFinish(@Nullable final Float velocityY) { | ||||
|         final WindowInsetsAnimationControllerCompat controller = insetsAnimationController; | ||||
| 
 | ||||
|         if (controller == null) { | ||||
|             // If we don't currently have a controller, cancel any pending request and return | ||||
|             if (pendingRequestCancellationSignal != null) { | ||||
|                 pendingRequestCancellationSignal.cancel(); | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         final int current = controller.getCurrentInsets().bottom; | ||||
|         final int shown = controller.getShownStateInsets().bottom; | ||||
|         final int hidden = controller.getHiddenStateInsets().bottom; | ||||
| 
 | ||||
|         if (velocityY != null) { | ||||
|             // If we have a velocity, we can use it's direction to determine | ||||
|             // the visibility. Upwards == visible | ||||
|             animateImeToVisibility(velocityY > 0, velocityY); | ||||
|         } else if (current == shown) { | ||||
|             // The current inset matches either the shown/hidden inset, finish() immediately | ||||
|             controller.finish(true); | ||||
|         } else if (current == hidden) { | ||||
|             controller.finish(false); | ||||
|         } else { | ||||
|             // Otherwise, we'll look at the current position... | ||||
|             if (controller.getCurrentFraction() >= SCROLL_THRESHOLD) { | ||||
|                 // If the IME is past the 'threshold' we animate it to the toggled state | ||||
|                 animateImeToVisibility(!isImeShownAtStart, null); | ||||
|             } else { | ||||
|                 // ...otherwise, we animate it back to the original visibility | ||||
|                 animateImeToVisibility(isImeShownAtStart, null); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void onRequestReady(@NonNull final WindowInsetsAnimationControllerCompat controller) { | ||||
|         // The request is ready, so clear out the pending cancellation signal | ||||
|         pendingRequestCancellationSignal = null; | ||||
|         // Store the current WindowInsetsAnimationController | ||||
|         insetsAnimationController = controller; | ||||
| 
 | ||||
|         // Call any pending callback | ||||
|         if (pendingRequestOnReadyListener != null) { | ||||
|             pendingRequestOnReadyListener.onRequestReady(controller); | ||||
|         } | ||||
|         pendingRequestOnReadyListener = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Resets all of our internal state. | ||||
|      */ | ||||
|     private void reset() { | ||||
|         // Clear all of our internal state | ||||
|         insetsAnimationController = null; | ||||
|         pendingRequestCancellationSignal = null; | ||||
|         isImeShownAtStart = false; | ||||
|         if (currentSpringAnimation != null) { | ||||
|             currentSpringAnimation.cancel(); | ||||
|         } | ||||
|         currentSpringAnimation = null; | ||||
|         pendingRequestOnReadyListener = null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Animate the IME to a given visibility. | ||||
|      * | ||||
|      * @param visible   `true` to animate the IME to it's fully shown state, `false` to it's | ||||
|      *                  fully hidden state. | ||||
|      * @param velocityY the velocity of the touch gesture which caused this call. Can be `null` | ||||
|      *                  if velocity is not available. | ||||
|      */ | ||||
|     private void animateImeToVisibility(final boolean visible, @Nullable final Float velocityY) { | ||||
|         if (insetsAnimationController == null) { | ||||
|             throw new IllegalStateException("Controller should not be null"); | ||||
|         } | ||||
|         final WindowInsetsAnimationControllerCompat controller = insetsAnimationController; | ||||
| 
 | ||||
|         final FloatPropertyCompat<Object> property = new FloatPropertyCompat<Object>("property") { | ||||
|             @Override | ||||
|             public float getValue(final Object object) { | ||||
|                 return controller.getCurrentInsets().bottom; | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void setValue(final Object object, final float value) { | ||||
|                 if (insetsAnimationController == null) { | ||||
|                     return; | ||||
|                 } | ||||
|                 insetTo((int) value); | ||||
|             } | ||||
|         }; | ||||
|         final float finalPosition = visible ? controller.getShownStateInsets().bottom | ||||
|                                             : controller.getHiddenStateInsets().bottom; | ||||
|         final SpringForce force = new SpringForce(finalPosition) | ||||
|                 // Tweak the damping value, to remove any bounciness. | ||||
|                 .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY) | ||||
|                 // The stiffness value controls the strength of the spring animation, which | ||||
|                 // controls the speed. Medium (the default) is a good value, but feel free to | ||||
|                 // play around with this value. | ||||
|                 .setStiffness(SpringForce.STIFFNESS_MEDIUM); | ||||
|         ViewUtils.springAnimationOf(this, property, finalPosition) | ||||
|                  .setSpring(force) | ||||
|                  .setStartVelocity(velocityY != null ? velocityY : 0) | ||||
|                  .addEndListener((animation, canceled, value, velocity) -> { | ||||
|                      if (animation == currentSpringAnimation) { | ||||
|                          currentSpringAnimation = null; | ||||
|                      } | ||||
|                      // Once the animation has ended, finish the controller | ||||
|                      finish(); | ||||
|                  }).start(); | ||||
|     } | ||||
| 
 | ||||
|     private int coerceIn(final int v, final int min, final int max) { | ||||
|         if (v >= min && v <= max) { | ||||
|             return v; | ||||
|         } | ||||
|         if (v < min) { | ||||
|             return min; | ||||
|         } | ||||
|         return max; | ||||
|     } | ||||
| 
 | ||||
|     public void setAnimationControlListener(final WindowInsetsAnimationControlListenerCompat listener) { | ||||
|         fwdListener = listener; | ||||
|     } | ||||
| 
 | ||||
|     public interface OnRequestReadyListener { | ||||
|         void onRequestReady(WindowInsetsAnimationControllerCompat windowInsetsAnimationControllerCompat); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,128 @@ | ||||
| /* | ||||
|  * Copyright 2020 The Android Open Source Project | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *      http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package awais.instagrabber.customviews.helpers; | ||||
| 
 | ||||
| import android.view.View; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.core.graphics.Insets; | ||||
| import androidx.core.view.ViewCompat; | ||||
| import androidx.core.view.WindowInsetsAnimationCompat; | ||||
| import androidx.core.view.WindowInsetsCompat; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| /** | ||||
|  * A [WindowInsetsAnimationCompat.Callback] which will translate/move the given view during any | ||||
|  * inset animations of the given inset type. | ||||
|  * <p> | ||||
|  * This class works in tandem with [RootViewDeferringInsetsCallback] to support the deferring of | ||||
|  * certain [WindowInsetsCompat.Type] values during a [WindowInsetsAnimationCompat], provided in | ||||
|  * [deferredInsetTypes]. The values passed into this constructor should match those which | ||||
|  * the [RootViewDeferringInsetsCallback] is created with. | ||||
|  */ | ||||
| public class TranslateDeferringInsetsAnimationCallback extends WindowInsetsAnimationCompat.Callback { | ||||
|     private final View view; | ||||
|     private final int persistentInsetTypes; | ||||
|     private final int deferredInsetTypes; | ||||
| 
 | ||||
|     private boolean shouldTranslate = true; | ||||
|     private int kbHeight; | ||||
| 
 | ||||
|     public TranslateDeferringInsetsAnimationCallback(final View view, | ||||
|                                                      final int persistentInsetTypes, | ||||
|                                                      final int deferredInsetTypes) { | ||||
|         this(view, persistentInsetTypes, deferredInsetTypes, DISPATCH_MODE_STOP); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param view                 the view to translate from it's start to end state | ||||
|      * @param persistentInsetTypes the bitmask of any inset types which were handled as part of the | ||||
|      *                             layout | ||||
|      * @param deferredInsetTypes   the bitmask of insets types which should be deferred until after | ||||
|      *                             any [WindowInsetsAnimationCompat]s have ended | ||||
|      * @param dispatchMode         The dispatch mode for this callback. | ||||
|      *                             See [WindowInsetsAnimationCompat.Callback.getDispatchMode]. | ||||
|      */ | ||||
|     public TranslateDeferringInsetsAnimationCallback(final View view, | ||||
|                                                      final int persistentInsetTypes, | ||||
|                                                      final int deferredInsetTypes, | ||||
|                                                      final int dispatchMode) { | ||||
|         super(dispatchMode); | ||||
|         if ((persistentInsetTypes & deferredInsetTypes) != 0) { | ||||
|             throw new IllegalArgumentException("persistentInsetTypes and deferredInsetTypes can not contain " + | ||||
|                                                        "any of same WindowInsetsCompat.Type values"); | ||||
|         } | ||||
|         this.view = view; | ||||
|         this.persistentInsetTypes = persistentInsetTypes; | ||||
|         this.deferredInsetTypes = deferredInsetTypes; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public WindowInsetsCompat onProgress(@NonNull final WindowInsetsCompat insets, | ||||
|                                          @NonNull final List<WindowInsetsAnimationCompat> runningAnimations) { | ||||
|         // onProgress() is called when any of the running animations progress... | ||||
| 
 | ||||
|         // First we get the insets which are potentially deferred | ||||
|         final Insets typesInset = insets.getInsets(deferredInsetTypes); | ||||
|         // Then we get the persistent inset types which are applied as padding during layout | ||||
|         final Insets otherInset = insets.getInsets(persistentInsetTypes); | ||||
| 
 | ||||
|         // Now that we subtract the two insets, to calculate the difference. We also coerce | ||||
|         // the insets to be >= 0, to make sure we don't use negative insets. | ||||
|         final Insets subtract = Insets.subtract(typesInset, otherInset); | ||||
|         final Insets diff = Insets.max(subtract, Insets.NONE); | ||||
| 
 | ||||
|         // The resulting `diff` insets contain the values for us to apply as a translation | ||||
|         // to the view | ||||
|         view.setTranslationX(diff.left - diff.right); | ||||
|         view.setTranslationY(shouldTranslate ? diff.top - diff.bottom : -kbHeight); | ||||
| 
 | ||||
|         return insets; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onEnd(@NonNull final WindowInsetsAnimationCompat animation) { | ||||
|         try { | ||||
|             final WindowInsetsCompat rootWindowInsets = ViewCompat.getRootWindowInsets(view); | ||||
|             if (kbHeight == 0) { | ||||
|                 if (rootWindowInsets == null) return; | ||||
|                 final Insets imeInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.ime()); | ||||
|                 final Insets navBarInsets = rootWindowInsets.getInsets(WindowInsetsCompat.Type.navigationBars()); | ||||
|                 kbHeight = imeInsets.bottom - navBarInsets.bottom; | ||||
|             } | ||||
|             // Once the animation has ended, reset the translation values | ||||
|             view.setTranslationX(0f); | ||||
|             final boolean visible = rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsetsCompat.Type.ime()); | ||||
|             float translationY = 0; | ||||
|             if (!shouldTranslate) { | ||||
|                 translationY = -kbHeight; | ||||
|                 if (visible) { | ||||
|                     translationY = 0; | ||||
|                 } | ||||
|             } | ||||
|             view.setTranslationY(translationY); | ||||
|         } finally { | ||||
|             shouldTranslate = true; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setShouldTranslate(final boolean shouldTranslate) { | ||||
|         this.shouldTranslate = shouldTranslate; | ||||
|     } | ||||
| } | ||||
| @ -2,6 +2,7 @@ package awais.instagrabber.fragments; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| @ -102,8 +103,13 @@ public class FavoritesFragment extends Fragment { | ||||
|                     // Log.d(TAG, "locationId: " + locationId); | ||||
|                     final NavController navController = NavHostFragment.findNavController(this); | ||||
|                     final Bundle bundle = new Bundle(); | ||||
|                     bundle.putString("locationId", locationId); | ||||
|                     navController.navigate(R.id.action_global_locationFragment, bundle); | ||||
|                     try { | ||||
|                         bundle.putLong("locationId", Long.parseLong(locationId)); | ||||
|                         navController.navigate(R.id.action_global_locationFragment, bundle); | ||||
|                     } catch (Exception e) { | ||||
|                         Log.e(TAG, "init: ", e); | ||||
|                         return; | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|                 case HASHTAG: { | ||||
|  | ||||
| @ -18,6 +18,7 @@ import android.view.MenuItem; | ||||
| import android.view.MotionEvent; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.view.Window; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| @ -29,6 +30,7 @@ import androidx.appcompat.widget.PopupMenu; | ||||
| import androidx.appcompat.widget.Toolbar; | ||||
| import androidx.constraintlayout.widget.ConstraintLayout; | ||||
| import androidx.core.content.PermissionChecker; | ||||
| import androidx.core.view.WindowCompat; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.fragment.app.FragmentActivity; | ||||
| 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.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP; | ||||
| import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | ||||
| import static awais.instagrabber.utils.Utils.getAttrValue; | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class PostViewV2Fragment extends Fragment implements EditTextDialogFragment.EditTextDialogFragmentCallback { | ||||
| @ -131,6 +132,9 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme | ||||
|     private boolean isInFullScreenMode; | ||||
|     private StyledPlayerView playerView; | ||||
|     private int playerViewOriginalHeight; | ||||
|     private Drawable originalRootBackground; | ||||
|     private ColorStateList originalLikeColorStateList; | ||||
|     private ColorStateList originalSaveColorStateList; | ||||
| 
 | ||||
|     private final Observer<Object> backStackSavedStateObserver = result -> { | ||||
|         if (result == null) return; | ||||
| @ -141,7 +145,6 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme | ||||
|         // clear result | ||||
|         backStackSavedStateResultLiveData.postValue(null); | ||||
|     }; | ||||
|     private Drawable originalRootBackground; | ||||
| 
 | ||||
|     public void setOnDeleteListener(final OnDeleteListener onDeleteListener) { | ||||
|         if (onDeleteListener == null) return; | ||||
| @ -441,6 +444,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme | ||||
|     } | ||||
| 
 | ||||
|     private void setupLike() { | ||||
|         originalLikeColorStateList = bottom.like.getIconTint(); | ||||
|         final boolean likableMedia = viewModel.hasPk() /*&& viewModel.getMedia().isCommentLikesEnabled()*/; | ||||
|         if (!likableMedia) { | ||||
|             bottom.like.setVisibility(View.GONE); | ||||
| @ -503,25 +507,25 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme | ||||
| 
 | ||||
|     private void setLikedResources(final boolean liked) { | ||||
|         final int iconResource; | ||||
|         final int tintResource; | ||||
|         final ColorStateList tintColorStateList; | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         final Resources resources = context.getResources(); | ||||
|         if (resources == null) return; | ||||
|         if (liked) { | ||||
|             iconResource = R.drawable.ic_like; | ||||
|             tintResource = resources.getColor(R.color.red_600); | ||||
|             // textResId = R.string.unlike_without_count; | ||||
|             tintColorStateList = ColorStateList.valueOf(resources.getColor(R.color.red_600)); | ||||
|         } else { | ||||
|             iconResource = R.drawable.ic_not_liked; | ||||
|             tintResource = getAttrValue(context, R.attr.colorPrimary); | ||||
|             // textResId = R.string.like_without_count; | ||||
|             tintColorStateList = originalLikeColorStateList != null ? originalLikeColorStateList | ||||
|                                                                     : ColorStateList.valueOf(resources.getColor(R.color.white)); | ||||
|         } | ||||
|         bottom.like.setIconResource(iconResource); | ||||
|         bottom.like.setIconTint(ColorStateList.valueOf(tintResource)); | ||||
|         bottom.like.setIconTint(tintColorStateList); | ||||
|     } | ||||
| 
 | ||||
|     private void setupSave() { | ||||
|         originalSaveColorStateList = bottom.save.getIconTint(); | ||||
|         if (!viewModel.isLoggedIn() || !viewModel.hasPk() || !viewModel.getMedia().canViewerSave()) { | ||||
|             bottom.save.setVisibility(View.GONE); | ||||
|             return; | ||||
| @ -574,22 +578,21 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme | ||||
| 
 | ||||
|     private void setSavedResources(final boolean saved) { | ||||
|         final int iconResource; | ||||
|         final int tintResource; | ||||
|         final ColorStateList tintColorStateList; | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         final Resources resources = context.getResources(); | ||||
|         if (resources == null) return; | ||||
|         if (saved) { | ||||
|             iconResource = R.drawable.ic_bookmark; | ||||
|             tintResource = resources.getColor(R.color.blue_700); | ||||
|             // textResId = R.string.saved; | ||||
|             tintColorStateList = ColorStateList.valueOf(resources.getColor(R.color.blue_700)); | ||||
|         } else { | ||||
|             iconResource = R.drawable.ic_round_bookmark_border_24; | ||||
|             tintResource = getAttrValue(context, R.attr.colorPrimary); | ||||
|             // textResId = R.string.save; | ||||
|             tintColorStateList = originalSaveColorStateList != null ? originalSaveColorStateList | ||||
|                                                                     : ColorStateList.valueOf(resources.getColor(R.color.white)); | ||||
|         } | ||||
|         bottom.save.setIconResource(iconResource); | ||||
|         bottom.save.setIconTint(ColorStateList.valueOf(tintResource)); | ||||
|         bottom.save.setIconTint(tintColorStateList); | ||||
|     } | ||||
| 
 | ||||
|     private void setupProfilePic(final User user) { | ||||
| @ -1427,8 +1430,10 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme | ||||
|         if (toolbar != null) { | ||||
|             toolbar.setVisibility(View.VISIBLE); | ||||
|         } | ||||
|         final View decorView = activity.getWindow().getDecorView(); | ||||
|         final Window window = activity.getWindow(); | ||||
|         final View decorView = window.getDecorView(); | ||||
|         decorView.setSystemUiVisibility(originalSystemUi); | ||||
|         WindowCompat.setDecorFitsSystemWindows(window, false); | ||||
|         isInFullScreenMode = false; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -23,9 +23,12 @@ import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| 
 | ||||
| import com.google.common.collect.Iterables; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.FeedStoriesListAdapter; | ||||
| @ -58,15 +61,17 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr | ||||
|     private StoriesService storiesService; | ||||
|     private Context context; | ||||
|     private String type; | ||||
|     private String currentQuery; | ||||
|     private String endCursor = null; | ||||
|     private FeedStoriesListAdapter adapter; | ||||
|     private MenuItem menuSearch; | ||||
| 
 | ||||
|     private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() { | ||||
|         @Override | ||||
|         public void onFeedStoryClick(final FeedStoryModel model, final int position) { | ||||
|         public void onFeedStoryClick(final FeedStoryModel model) { | ||||
|             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 | ||||
|                     .actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); | ||||
|             NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); | ||||
| @ -153,7 +158,7 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(@NonNull final Menu menu, final MenuInflater inflater) { | ||||
|         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(); | ||||
|         searchView.setQueryHint(getResources().getString(R.string.action_search)); | ||||
|         searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { | ||||
| @ -166,7 +171,6 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr | ||||
|             @Override | ||||
|             public boolean onQueryTextChange(final String query) { | ||||
|                 if (adapter != null) { | ||||
|                     currentQuery = query; | ||||
|                     adapter.getFilter().filter(query); | ||||
|                 } | ||||
|                 return true; | ||||
|  | ||||
| @ -120,6 +120,7 @@ public class StoryViewerFragment extends Fragment { | ||||
|     private View root; | ||||
|     private FragmentStoryViewerBinding binding; | ||||
|     private String currentStoryUsername; | ||||
|     private String highlightTitle; | ||||
|     private StoriesAdapter storiesAdapter; | ||||
|     private SwipeEvent swipeEvent; | ||||
|     private GestureDetectorCompat gestureDetector; | ||||
| @ -275,7 +276,9 @@ public class StoryViewerFragment extends Fragment { | ||||
|     @Override | ||||
|     public void onPause() { | ||||
|         super.onPause(); | ||||
|         releasePlayer(); | ||||
|         if (player != null) { | ||||
|             player.pause(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
| @ -749,7 +752,7 @@ public class StoryViewerFragment extends Fragment { | ||||
|                 final HighlightModel model = models.get(currentFeedStoryIndex); | ||||
|                 currentStoryMediaId = model.getId(); | ||||
|                 fetchOptions = StoryViewerOptions.forHighlight(model.getId()); | ||||
|                 currentStoryUsername = model.getTitle(); | ||||
|                 highlightTitle = model.getTitle(); | ||||
|                 break; | ||||
|             } | ||||
|             case FEED_STORY_POSITION: { | ||||
| @ -849,8 +852,8 @@ public class StoryViewerFragment extends Fragment { | ||||
|         if (type == Type.HIGHLIGHT) { | ||||
|             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|             if (actionBar != null) { | ||||
|                 actionBarTitle = options.getName(); | ||||
|                 actionBar.setTitle(options.getName()); | ||||
|                 actionBarTitle = highlightTitle; | ||||
|                 actionBar.setTitle(highlightTitle); | ||||
|             } | ||||
|         } else if (hasUsername) { | ||||
|             currentStoryUsername = currentStoryUsername.replace("@", ""); | ||||
|  | ||||
| @ -16,6 +16,7 @@ import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.view.menu.ActionMenuItemView; | ||||
| import androidx.coordinatorlayout.widget.CoordinatorLayout; | ||||
| import androidx.fragment.app.Fragment; | ||||
| 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.BadgeUtils; | ||||
| import com.google.android.material.internal.ToolbarUtils; | ||||
| import com.google.android.material.snackbar.Snackbar; | ||||
| 
 | ||||
| import java.util.List; | ||||
| @ -102,7 +104,9 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh | ||||
|         super.onPause(); | ||||
|         unregisterReceiver(); | ||||
|         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()); | ||||
|             pendingRequestTotalBadgeDrawable = null; | ||||
|         } | ||||
| @ -217,7 +221,11 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh | ||||
|             pendingRequestTotalBadgeDrawable = BadgeDrawable.create(context); | ||||
|         } | ||||
|         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; | ||||
|             pendingRequestTotalBadgeDrawable.setNumber(0); | ||||
|             pendingRequestsMenuItem.setVisible(false); | ||||
|  | ||||
| @ -15,6 +15,7 @@ import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.text.Editable; | ||||
| import android.util.Log; | ||||
| import android.util.Pair; | ||||
| import android.view.KeyEvent; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| @ -25,11 +26,18 @@ import android.view.ViewGroup; | ||||
| import android.view.inputmethod.InputMethodManager; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.activity.OnBackPressedCallback; | ||||
| import androidx.activity.OnBackPressedDispatcher; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.constraintlayout.widget.ConstraintLayout; | ||||
| import androidx.appcompat.view.menu.ActionMenuItemView; | ||||
| 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.lifecycle.LiveData; | ||||
| 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.BadgeUtils; | ||||
| import com.google.android.material.internal.ToolbarUtils; | ||||
| import com.google.android.material.snackbar.Snackbar; | ||||
| 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.viewholder.directmessages.DirectItemViewHolder; | ||||
| import awais.instagrabber.animations.CubicBezierInterpolator; | ||||
| import awais.instagrabber.customviews.InsetsAnimationLinearLayout; | ||||
| import awais.instagrabber.customviews.KeyNotifyingEmojiEditText; | ||||
| import awais.instagrabber.customviews.RecordView; | ||||
| import awais.instagrabber.customviews.Tooltip; | ||||
| import awais.instagrabber.customviews.emoji.Emoji; | ||||
| import awais.instagrabber.customviews.emoji.EmojiBottomSheetDialog; | ||||
| import awais.instagrabber.customviews.emoji.EmojiPicker; | ||||
| import awais.instagrabber.customviews.helpers.ControlFocusInsetsAnimationCallback; | ||||
| import awais.instagrabber.customviews.helpers.EmojiPickerInsetsAnimationCallback; | ||||
| import awais.instagrabber.customviews.helpers.HeaderItemDecoration; | ||||
| import awais.instagrabber.customviews.helpers.HeightProvider; | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge; | ||||
| import awais.instagrabber.customviews.helpers.SimpleImeAnimationController; | ||||
| import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCallback; | ||||
| import awais.instagrabber.customviews.helpers.TextWatcherAdapter; | ||||
| import awais.instagrabber.customviews.helpers.TranslateDeferringInsetsAnimationCallback; | ||||
| import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding; | ||||
| import awais.instagrabber.dialogs.DirectItemReactionDialogFragment; | ||||
| import awais.instagrabber.dialogs.GifPickerBottomDialogFragment; | ||||
| @ -111,9 +125,6 @@ import awais.instagrabber.viewmodels.AppStateViewModel; | ||||
| import awais.instagrabber.viewmodels.DirectThreadViewModel; | ||||
| 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, | ||||
|         EmojiPicker.OnEmojiClickListener { | ||||
|     private static final String TAG = DirectMessageThreadFragment.class.getSimpleName(); | ||||
| @ -125,7 +136,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|     private DirectItemsAdapter itemsAdapter; | ||||
|     private MainActivity fragmentActivity; | ||||
|     private DirectThreadViewModel viewModel; | ||||
|     private ConstraintLayout root; | ||||
|     private InsetsAnimationLinearLayout root; | ||||
|     private boolean shouldRefresh = true; | ||||
|     private List<DirectItemOrHeader> itemOrHeaders; | ||||
|     private List<User> users; | ||||
| @ -135,14 +146,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|     private ActionBar actionBar; | ||||
|     private AppStateViewModel appStateViewModel; | ||||
|     private Runnable prevTitleRunnable; | ||||
|     private int originalSoftInputMode; | ||||
|     private AnimatorSet animatorSet; | ||||
|     private boolean isEmojiPickerShown; | ||||
|     private boolean isKbShown; | ||||
|     private HeightProvider heightProvider; | ||||
|     private boolean isRecording; | ||||
|     private boolean wasKbShowing; | ||||
|     private int keyboardHeight = Utils.convertDpToPx(250); | ||||
|     private DirectItemReactionDialogFragment reactionDialogFragment; | ||||
|     private DirectItem itemToForward; | ||||
|     private MutableLiveData<Object> backStackSavedStateResultLiveData; | ||||
| @ -163,6 +168,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|     private MenuItem markAsSeenMenuItem; | ||||
|     private Media tempMedia; | ||||
|     private DirectItem addReactionItem; | ||||
|     private TranslateDeferringInsetsAnimationCallback inputHolderAnimationCallback; | ||||
|     private TranslateDeferringInsetsAnimationCallback chatsAnimationCallback; | ||||
|     private EmojiPickerInsetsAnimationCallback emojiPickerAnimationCallback; | ||||
|     private boolean hasKbOpenedOnce; | ||||
|     private boolean wasToggled; | ||||
| 
 | ||||
|     private final AppExecutors appExecutors = AppExecutors.getInstance(); | ||||
|     private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() { | ||||
| @ -304,7 +314,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|             emojiBottomSheetDialog.show(getChildFragmentManager(), EmojiBottomSheetDialog.TAG); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     private final DirectItemLongClickListener directItemLongClickListener = position -> { | ||||
|         // viewModel.setSelectedPosition(position); | ||||
|     }; | ||||
| @ -333,6 +342,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         backStackSavedStateResultLiveData.postValue(null); | ||||
|     }; | ||||
|     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 | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
| @ -371,13 +388,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|             return root; | ||||
|         } | ||||
|         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 | ||||
|         return root; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { | ||||
|         // WindowCompat.setDecorFitsSystemWindows(fragmentActivity.getWindow(), false); | ||||
|         if (!shouldRefresh) return; | ||||
|         init(); | ||||
|         binding.send.post(() -> initialSendX = binding.send.getX()); | ||||
| @ -490,10 +507,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         if (isRecording) { | ||||
|             binding.recordView.cancelRecording(binding.send); | ||||
|         } | ||||
|         if (isKbShown) { | ||||
|             wasKbShowing = true; | ||||
|             binding.emojiPicker.setAlpha(0); | ||||
|         } | ||||
|         emojiPickerVisible.postValue(false); | ||||
|         kbVisible.postValue(false); | ||||
|         binding.inputHolder.setTranslationY(0); | ||||
|         binding.chats.setTranslationY(0); | ||||
|         binding.emojiPicker.setTranslationY(0); | ||||
|         removeObservers(); | ||||
|         super.onPause(); | ||||
|     } | ||||
| @ -501,16 +519,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|     @Override | ||||
|     public void 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) { | ||||
|             binding.send.setX(initialSendX); | ||||
|         } | ||||
|         binding.send.stopScale(); | ||||
|         final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher(); | ||||
|         onBackPressedDispatcher.addCallback(onEmojiPickerBackPressedCallback); | ||||
|         setupBackStackResultObserver(); | ||||
|         setObservers(); | ||||
|         // attachPendingRequestsBadge(viewModel.getPendingRequestsCount().getValue()); | ||||
| @ -533,13 +547,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         if (prevTitleRunnable != null) { | ||||
|             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) { | ||||
|             final RecyclerView.ViewHolder holder = binding.chats.getChildViewHolder(binding.chats.getChildAt(i)); | ||||
|             if (holder == null) continue; | ||||
| @ -549,7 +556,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         } | ||||
|         isPendingRequestCountBadgeAttached = false; | ||||
|         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; | ||||
|         } | ||||
|     } | ||||
| @ -561,37 +572,8 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         setupList(); | ||||
|         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() { | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
| @ -862,7 +844,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|             pendingRequestCountBadgeDrawable = BadgeDrawable.create(context); | ||||
|         } | ||||
|         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; | ||||
|             pendingRequestCountBadgeDrawable.setNumber(0); | ||||
|             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) { | ||||
|                 final int length = s.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 -> { | ||||
| @ -1131,30 +1100,15 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|             Log.d(TAG, "setOnRecordLongClickListener"); | ||||
|             return true; | ||||
|         }); | ||||
|         binding.input.setShowSoftInputOnFocus(false); | ||||
|         binding.input.requestFocus(); | ||||
|         binding.input.setOnKeyEventListener((keyCode, keyEvent) -> { | ||||
|             if (keyCode != KeyEvent.KEYCODE_BACK) return false; | ||||
|             // We'll close the keyboard/emoji picker only when user releases the back button | ||||
|             // return true so that system doesn't handle the event | ||||
|             if (keyEvent.getAction() != KeyEvent.ACTION_UP) return true; | ||||
|             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(); | ||||
|         binding.input.setOnFocusChangeListener((v, hasFocus) -> { | ||||
|             if (!hasFocus) return; | ||||
|             final Boolean emojiPickerVisibleValue = emojiPickerVisible.getValue(); | ||||
|             if (emojiPickerVisibleValue == null || !emojiPickerVisibleValue) return; | ||||
|             inputHolderAnimationCallback.setShouldTranslate(false); | ||||
|             chatsAnimationCallback.setShouldTranslate(false); | ||||
|             emojiPickerAnimationCallback.setShouldTranslate(false); | ||||
|         }); | ||||
|         setupInsetsCallback(); | ||||
|         setupEmojiPicker(); | ||||
|         binding.gallery.setOnClickListener(v -> { | ||||
|             final MediaPickerBottomDialogFragment mediaPicker = MediaPickerBottomDialogFragment.newInstance(); | ||||
| @ -1163,10 +1117,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|                 if (!isAdded()) return; | ||||
|                 if (!entry.isVideo) { | ||||
|                     navigateToImageEditFragment(entry.path); | ||||
|                     return; | ||||
|                 } | ||||
|                 handleSentMessage(viewModel.sendUri(entry)); | ||||
|             }); | ||||
|             mediaPicker.show(getChildFragmentManager(), "MediaPicker"); | ||||
|             hideKeyboard(true); | ||||
|         }); | ||||
|         binding.gif.setOnClickListener(v -> { | ||||
|             final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance(); | ||||
| @ -1176,7 +1131,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|                 handleSentMessage(viewModel.sendAnimatedMedia(giphyGif)); | ||||
|             }); | ||||
|             gifPicker.show(getChildFragmentManager(), "GifPicker"); | ||||
|             hideKeyboard(true); | ||||
|         }); | ||||
|         binding.camera.setOnClickListener(v -> { | ||||
|             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() { | ||||
|         final Drawable icon = binding.send.getIcon(); | ||||
|         if (icon instanceof Animatable) { | ||||
| @ -1230,15 +1251,87 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|     private void setupEmojiPicker() { | ||||
|         root.post(() -> binding.emojiPicker.init( | ||||
|                 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)) | ||||
|         )); | ||||
|         setupKbHeightProvider(); | ||||
|         if (keyboardHeight == 0) { | ||||
|             keyboardHeight = Utils.convertDpToPx(250); | ||||
|         } | ||||
|         setEmojiPickerBounds(); | ||||
|         binding.emojiToggle.setOnClickListener(v -> toggleEmojiPicker()); | ||||
|         binding.emojiToggle.setOnClickListener(v -> { | ||||
|             Boolean isEmojiPickerVisible = emojiPickerVisible.getValue(); | ||||
|             if (isEmojiPickerVisible == null) isEmojiPickerVisible = false; | ||||
|             Boolean isKbVisible = kbVisible.getValue(); | ||||
|             if (isKbVisible == null) isKbVisible = false; | ||||
|             wasToggled = isEmojiPickerVisible || isKbVisible; | ||||
| 
 | ||||
|             if (isEmojiPickerVisible) { | ||||
|                 if (hasKbOpenedOnce && binding.emojiPicker.getTranslationY() != 0) { | ||||
|                     inputHolderAnimationCallback.setShouldTranslate(false); | ||||
|                     chatsAnimationCallback.setShouldTranslate(false); | ||||
|                     emojiPickerAnimationCallback.setShouldTranslate(false); | ||||
|                 } | ||||
|                 // 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() { | ||||
| @ -1246,67 +1339,21 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         if (context == null) return; | ||||
|         final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); | ||||
|         if (imm == null) return; | ||||
|         if (!isEmojiPickerShown) { | ||||
|             binding.emojiPicker.setAlpha(0); | ||||
|         if (!binding.input.isFocused()) { | ||||
|             binding.input.requestFocus(); | ||||
|         } | ||||
|         final boolean shown = imm.showSoftInput(binding.input, InputMethodManager.SHOW_IMPLICIT); | ||||
|         if (!shown) { | ||||
|             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(); | ||||
|         if (context == null) return; | ||||
|         final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); | ||||
|         if (imm == null) return; | ||||
|         if (shouldPan) { | ||||
|             binding.emojiPicker.setAlpha(0); | ||||
|         } | ||||
|         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() { | ||||
| @ -1375,40 +1422,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         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 | ||||
|     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()) { | ||||
|             animatorSet.cancel(); | ||||
|         } | ||||
|         final ImmutableList.Builder<Animator> builder = ImmutableList.builder(); | ||||
|         builder.add( | ||||
|                 ObjectAnimator.ofFloat(binding.chats, TRANSLATION_Y, -height), | ||||
|                 ObjectAnimator.ofFloat(binding.input, TRANSLATION_Y, -height), | ||||
|                 ObjectAnimator.ofFloat(binding.inputBg, 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) | ||||
|                 ObjectAnimator.ofFloat(binding.inputHolder, TRANSLATION_Y, -height), | ||||
|                 ObjectAnimator.ofFloat(binding.emojiPicker, TRANSLATION_Y, -height) | ||||
|         ); | ||||
|         // if (headerItemDecoration != null && headerItemDecoration.getCurrentHeader() != null) { | ||||
|         //     builder.add(ObjectAnimator.ofFloat(headerItemDecoration.getCurrentHeader(), TRANSLATION_Y, height)); | ||||
| @ -1418,10 +1443,21 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact | ||||
|         animatorSet.setDuration(200); | ||||
|         animatorSet.setInterpolator(CubicBezierInterpolator.EASE_IN); | ||||
|         animatorSet.addListener(new AnimatorListenerAdapter() { | ||||
|             @Override | ||||
|             public void onAnimationStart(final Animator animation) { | ||||
|                 super.onAnimationStart(animation); | ||||
|                 if (onAnimationStart != null) { | ||||
|                     onAnimationStart.apply(null); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onAnimationEnd(final Animator animation) { | ||||
|                 binding.emojiPicker.setAlpha(1); | ||||
|                 super.onAnimationEnd(animation); | ||||
|                 animatorSet = null; | ||||
|                 if (onAnimationEnd != null) { | ||||
|                     onAnimationEnd.apply(null); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         animatorSet.start(); | ||||
|  | ||||
| @ -337,6 +337,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         binding.getRoot().postDelayed(feedStoriesAdapter::notifyDataSetChanged, 1000); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         binding.feedRecyclerView.refresh(); | ||||
| @ -418,15 +424,16 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|     } | ||||
| 
 | ||||
|     private void fetchStories() { | ||||
|         if (storiesFetching) return; | ||||
|         // final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         storiesFetching = true; | ||||
|         updateSwipeRefreshState(); | ||||
|         storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { | ||||
|             @Override | ||||
|             public void onSuccess(final List<FeedStoryModel> result) { | ||||
|                 storiesFetching = false; | ||||
|                 feedStoriesViewModel.getList().postValue(result); | ||||
|                 feedStoriesAdapter.submitList(result); | ||||
|                 storiesFetching = false; | ||||
|                 if (storyListMenu != null) storyListMenu.setVisible(true); | ||||
|                 updateSwipeRefreshState(); | ||||
|             } | ||||
| @ -451,8 +458,10 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|     } | ||||
| 
 | ||||
|     public void scrollToTop() { | ||||
|         binding.feedRecyclerView.smoothScrollToPosition(0); | ||||
|         // binding.storiesContainer.setExpanded(true); | ||||
|         if (binding != null) { | ||||
|             binding.feedRecyclerView.smoothScrollToPosition(0); | ||||
|             // binding.storiesContainer.setExpanded(true); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private boolean isSafeToNavigate(final NavController navController) { | ||||
|  | ||||
| @ -407,7 +407,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         } | ||||
|         chainingMenuItem = menu.findItem(R.id.chaining); | ||||
|         if (chainingMenuItem != null) { | ||||
|             chainingMenuItem.setVisible(isNotMe); | ||||
|             chainingMenuItem.setVisible(isNotMe && profileModel.hasChaining()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -527,7 +527,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.GONE); | ||||
|         profileDetailsBinding.countsDivider.getRoot().setVisibility(View.GONE); | ||||
|         profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); | ||||
|         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(); | ||||
|             return; | ||||
|         } | ||||
|         if (!postsSetupDone) { | ||||
|             setupPosts(); | ||||
|         } else { | ||||
|             binding.postsRecyclerView.refresh(); | ||||
|         final long profileId = profileModel.getPk(); | ||||
|         if (!isReallyPrivate()) { | ||||
|             if (!postsSetupDone) { | ||||
|                 setupPosts(); | ||||
|             } | ||||
|             else { | ||||
|                 binding.postsRecyclerView.refresh(); | ||||
|             } | ||||
|             if (isLoggedIn) { | ||||
|                 fetchStoryAndHighlights(profileId); | ||||
|             } | ||||
|         } | ||||
|         profileDetailsBinding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); | ||||
|         profileDetailsBinding.isPrivate.setVisibility(profileModel.isPrivate() ? View.VISIBLE : View.GONE); | ||||
|         final long profileId = profileModel.getPk(); | ||||
|         if (isLoggedIn) { | ||||
|             fetchStoryAndHighlights(profileId); | ||||
|         } | ||||
| 
 | ||||
|         setupButtons(profileId); | ||||
|         final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); | ||||
|         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.setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|         profileDetailsBinding.countsBarrier.getRoot().setVisibility(View.VISIBLE); | ||||
|         profileDetailsBinding.countsDivider.getRoot().setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|         final long followersCount = profileModel.getFollowerCount(); | ||||
|         final long followingCount = profileModel.getFollowingCount(); | ||||
| @ -905,6 +909,8 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|             binding.privatePage1.setImageResource(R.drawable.lock); | ||||
|             binding.privatePage2.setText(R.string.priv_acc); | ||||
|             binding.privatePage.setVisibility(View.VISIBLE); | ||||
|             binding.privatePage1.setVisibility(View.VISIBLE); | ||||
|             binding.privatePage2.setVisibility(View.VISIBLE); | ||||
|             binding.postsRecyclerView.setVisibility(View.GONE); | ||||
|             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); | ||||
|             } | ||||
|             if (chainingMenuItem != null) { | ||||
|                 chainingMenuItem.setVisible(true); | ||||
|                 chainingMenuItem.setVisible(profileModel.hasChaining()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -4,8 +4,11 @@ import android.annotation.SuppressLint; | ||||
| import android.content.Context; | ||||
| import android.content.Intent; | ||||
| import android.content.res.Resources; | ||||
| import android.os.Bundle; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| @ -19,6 +22,7 @@ import androidx.preference.Preference; | ||||
| import androidx.preference.PreferenceCategory; | ||||
| import androidx.preference.PreferenceScreen; | ||||
| import androidx.preference.PreferenceViewHolder; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| @ -53,6 +57,20 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | ||||
|     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 | ||||
|     void setupPreferenceScreen(final PreferenceScreen screen) { | ||||
|         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|  | ||||
| @ -16,11 +16,15 @@ public final class FeedStoryModel implements Serializable { | ||||
|     private final boolean isLive, isBestie; | ||||
|     private final long timestamp; | ||||
|     private final int mediaCount; | ||||
|     private boolean isShown = true; | ||||
| 
 | ||||
|     public FeedStoryModel(final String storyMediaId, final User profileModel, final boolean fullyRead, | ||||
|                           final long timestamp, final StoryModel firstStoryModel, final int mediaCount, | ||||
|                           final boolean isLive, final boolean isBestie) { | ||||
|     public FeedStoryModel(final String storyMediaId, | ||||
|                           final User profileModel, | ||||
|                           final boolean fullyRead, | ||||
|                           final long timestamp, | ||||
|                           final StoryModel firstStoryModel, | ||||
|                           final int mediaCount, | ||||
|                           final boolean isLive, | ||||
|                           final boolean isBestie) { | ||||
|         this.storyMediaId = storyMediaId; | ||||
|         this.profileModel = profileModel; | ||||
|         this.fullyRead = fullyRead; | ||||
| @ -52,10 +56,6 @@ public final class FeedStoryModel implements Serializable { | ||||
|         return profileModel; | ||||
|     } | ||||
| 
 | ||||
|     //    public void setFirstStoryModel(final StoryModel firstStoryModel) { | ||||
|     //        this.firstStoryModel = firstStoryModel; | ||||
|     //    } | ||||
| 
 | ||||
|     public StoryModel getFirstStoryModel() { | ||||
|         return firstStoryModel; | ||||
|     } | ||||
| @ -75,12 +75,4 @@ public final class FeedStoryModel implements Serializable { | ||||
|     public boolean isBestie() { | ||||
|         return isBestie; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isShown() { | ||||
|         return isShown; | ||||
|     } | ||||
| 
 | ||||
|     public void setShown(final boolean shown) { | ||||
|         isShown = shown; | ||||
|     } | ||||
| } | ||||
| @ -17,6 +17,7 @@ public class User implements Serializable { | ||||
|     private final boolean isUnpublished; | ||||
|     private final boolean isFavorite; | ||||
|     private final boolean isDirectappInstalled; | ||||
|     private final boolean hasChaining; | ||||
|     private final String reelAutoArchive; | ||||
|     private final String allowedCommenterType; | ||||
|     private final long mediaCount; | ||||
| @ -28,11 +29,10 @@ public class User implements Serializable { | ||||
|     private final long usertagsCount; | ||||
|     private final String publicEmail; | ||||
|     private final HdProfilePicUrlInfo hdProfilePicUrlInfo; | ||||
|     private final String profileContext; | ||||
|     private final List<UserProfileContextLink> profileContextLinksWithUserIds; | ||||
|     private final String socialContext; | ||||
|     // if a DM member is a Facebook user, this is present | ||||
|     private final String interopMessagingUserFbid; | ||||
|     private final String profileContext; // "also followed by" your friends | ||||
|     private final List<UserProfileContextLink> profileContextLinksWithUserIds; // ^ | ||||
|     private final String socialContext; // AYML | ||||
|     private final String interopMessagingUserFbid; // in DMs only: Facebook user ID | ||||
| 
 | ||||
|     public User(final long pk, | ||||
|                 final String username, | ||||
| @ -46,6 +46,7 @@ public class User implements Serializable { | ||||
|                 final boolean isUnpublished, | ||||
|                 final boolean isFavorite, | ||||
|                 final boolean isDirectappInstalled, | ||||
|                 final boolean hasChaining, | ||||
|                 final String reelAutoArchive, | ||||
|                 final String allowedCommenterType, | ||||
|                 final long mediaCount, | ||||
| @ -73,6 +74,7 @@ public class User implements Serializable { | ||||
|         this.isUnpublished = isUnpublished; | ||||
|         this.isFavorite = isFavorite; | ||||
|         this.isDirectappInstalled = isDirectappInstalled; | ||||
|         this.hasChaining = hasChaining; | ||||
|         this.reelAutoArchive = reelAutoArchive; | ||||
|         this.allowedCommenterType = allowedCommenterType; | ||||
|         this.mediaCount = mediaCount; | ||||
| @ -90,6 +92,53 @@ public class User implements Serializable { | ||||
|         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() { | ||||
|         return pk; | ||||
|     } | ||||
| @ -149,6 +198,10 @@ public class User implements Serializable { | ||||
|         return isDirectappInstalled; | ||||
|     } | ||||
| 
 | ||||
|     public boolean hasChaining() { | ||||
|         return hasChaining; | ||||
|     } | ||||
| 
 | ||||
|     public String getReelAutoArchive() { | ||||
|         return reelAutoArchive; | ||||
|     } | ||||
| @ -237,7 +290,7 @@ public class User implements Serializable { | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         return Objects.hash(pk, username, fullName, isPrivate, profilePicUrl, profilePicId, friendshipStatus, isVerified, hasAnonymousProfilePicture, | ||||
|                             isUnpublished, isFavorite, isDirectappInstalled, reelAutoArchive, allowedCommenterType, mediaCount, followerCount, | ||||
|                             followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail); | ||||
|                             isUnpublished, isFavorite, isDirectappInstalled, hasChaining, reelAutoArchive, allowedCommenterType, mediaCount, | ||||
|                             followerCount, followingCount, followingTagCount, biography, externalUrl, usertagsCount, publicEmail); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -191,9 +191,7 @@ public class SearchItem { | ||||
|                 recentSearch.getName(), | ||||
|                 false, | ||||
|                 recentSearch.getPicUrl(), | ||||
|                 null, null, false, false, false, false, false, | ||||
|                 null, null, 0, 0, 0, 0, null, null, | ||||
|                 0, null, null, null, null, null, null | ||||
|                 false | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -205,9 +203,7 @@ public class SearchItem { | ||||
|                 favorite.getDisplayName(), | ||||
|                 false, | ||||
|                 favorite.getPicUrl(), | ||||
|                 null, null, false, false, false, false, false, | ||||
|                 null, null, 0, 0, 0, 0, null, null, | ||||
|                 0, null, null, null, null, null, null | ||||
|                 false | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -63,7 +63,7 @@ public class NavigationExtensions { | ||||
|         selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId()); | ||||
|         final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId); | ||||
|         isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag); | ||||
|         bottomNavigationView.setOnNavigationItemSelectedListener(item -> { | ||||
|         bottomNavigationView.setOnItemSelectedListener(item -> { | ||||
|             if (fragmentManager.isStateSaved()) { | ||||
|                 return false; | ||||
|             } | ||||
| @ -169,7 +169,7 @@ public class NavigationExtensions { | ||||
|     private static void setupItemReselected(final BottomNavigationView bottomNavigationView, | ||||
|                                             final SparseArray<String> graphIdToTagMap, | ||||
|                                             final FragmentManager fragmentManager) { | ||||
|         bottomNavigationView.setOnNavigationItemReselectedListener(item -> { | ||||
|         bottomNavigationView.setOnItemReselectedListener(item -> { | ||||
|             final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); | ||||
|             final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag); | ||||
|             if (fragmentByTag == null) { | ||||
|  | ||||
| @ -767,11 +767,7 @@ public final class ResponseBodyUtils { | ||||
|                     owner.optString("full_name"), | ||||
|                     false, | ||||
|                     owner.optString("profile_pic_url"), | ||||
|                     null, | ||||
|                     friendshipStatus, | ||||
|                     owner.optBoolean("is_verified"), | ||||
|                     false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, | ||||
|                     null, null, null, null); | ||||
|                     owner.optBoolean("is_verified")); | ||||
|         } | ||||
|         final String id = feedItem.getString(Constants.EXTRAS_ID); | ||||
|         VideoVersion videoVersion = null; | ||||
|  | ||||
| @ -38,6 +38,8 @@ import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.lifecycle.LiveData; | ||||
| import androidx.lifecycle.MediatorLiveData; | ||||
| import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; | ||||
| 
 | ||||
| import com.google.android.exoplayer2.database.ExoDatabaseProvider; | ||||
| @ -562,4 +564,39 @@ public final class Utils { | ||||
|         display.getRealSize(size); | ||||
|         return size; | ||||
|     } | ||||
| 
 | ||||
|     public static <F, S> LiveData<Pair<F, S>> zipLiveData(@NonNull final LiveData<F> firstLiveData, | ||||
|                                                           @NonNull final LiveData<S> secondLiveData) { | ||||
|         final ZippedLiveData<F, S> zippedLiveData = new ZippedLiveData<>(); | ||||
|         zippedLiveData.addFirstSource(firstLiveData); | ||||
|         zippedLiveData.addSecondSource(secondLiveData); | ||||
|         return zippedLiveData; | ||||
|     } | ||||
| 
 | ||||
|     public static class ZippedLiveData<F, S> extends MediatorLiveData<Pair<F, S>> { | ||||
|         private F lastF; | ||||
|         private S lastS; | ||||
| 
 | ||||
|         private void update() { | ||||
|             F localLastF = lastF; | ||||
|             S localLastS = lastS; | ||||
|             if (localLastF != null && localLastS != null) { | ||||
|                 setValue(new Pair<>(localLastF, localLastS)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public void addFirstSource(@NonNull final LiveData<F> firstLiveData) { | ||||
|             addSource(firstLiveData, f -> { | ||||
|                 lastF = f; | ||||
|                 update(); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         public void addSecondSource(@NonNull final LiveData<S> secondLiveData) { | ||||
|             addSource(secondLiveData, s -> { | ||||
|                 lastS = s; | ||||
|                 update(); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,18 +1,28 @@ | ||||
| package awais.instagrabber.utils; | ||||
| 
 | ||||
| import android.annotation.SuppressLint; | ||||
| import android.content.Context; | ||||
| import android.graphics.drawable.Drawable; | ||||
| import android.graphics.drawable.GradientDrawable; | ||||
| import android.graphics.drawable.ShapeDrawable; | ||||
| import android.graphics.drawable.shapes.RoundRectShape; | ||||
| import android.os.Build; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.FrameLayout; | ||||
| import android.widget.TextView; | ||||
| 
 | ||||
| import androidx.annotation.ColorInt; | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.content.res.ResourcesCompat; | ||||
| 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 { | ||||
| 
 | ||||
| @ -69,4 +79,43 @@ public final class ViewUtils { | ||||
|     public static float getTextViewValueWidth(final TextView textView, final String 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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -238,11 +238,7 @@ public class CommentsViewerViewModel extends ViewModel { | ||||
|                 null, | ||||
|                 false, | ||||
|                 owner.getString("profile_pic_url"), | ||||
|                 null, | ||||
|                 new FriendshipStatus(false, false, false, false, false, false, false, false, false, false), | ||||
|                 owner.optBoolean("is_verified"), | ||||
|                 false, false, false, false, null, null, 0, 0, 0, 0, null, null, 0, null, null, null, null, | ||||
|                 null, null); | ||||
|                 owner.optBoolean("is_verified")); | ||||
|         final JSONObject likedBy = commentJsonObject.optJSONObject("edge_liked_by"); | ||||
|         final String commentId = commentJsonObject.getString("id"); | ||||
|         final JSONObject childCommentsJsonObject = commentJsonObject.optJSONObject("edge_threaded_comments"); | ||||
|  | ||||
| @ -34,7 +34,6 @@ import retrofit2.Response; | ||||
| 
 | ||||
| public class GraphQLService extends BaseService { | ||||
|     private static final String TAG = "GraphQLService"; | ||||
|     // private static final boolean loadFromMock = false; | ||||
| 
 | ||||
|     private final GraphQLRepository repository; | ||||
| 
 | ||||
| @ -230,39 +229,7 @@ public class GraphQLService extends BaseService { | ||||
|                                 userObject.optString("full_name"), | ||||
|                                 userObject.optBoolean("is_private"), | ||||
|                                 userObject.getString("profile_pic_url"), | ||||
|                                 null, | ||||
|                                 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 | ||||
|                                 userObject.optBoolean("is_verified") | ||||
|                         )); | ||||
|                         // userModels.add(new ProfileModel(userObject.optBoolean("is_private"), | ||||
|                         //                                 false, | ||||
| @ -357,6 +324,7 @@ public class GraphQLService extends BaseService { | ||||
|                             false, | ||||
|                             false, | ||||
|                             false, | ||||
|                             false, | ||||
|                             null, | ||||
|                             null, | ||||
|                             timelineMedia.getLong("count"), | ||||
|  | ||||
| @ -143,39 +143,7 @@ public class StoriesService extends BaseService { | ||||
|                                                userJson.optString("full_name"), | ||||
|                                                userJson.optBoolean("is_private"), | ||||
|                                                userJson.getString("profile_pic_url"), | ||||
|                                                null, | ||||
|                                                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 | ||||
|                                                userJson.optBoolean("is_verified") | ||||
|                     ); | ||||
|                     final long timestamp = node.getLong("latest_reel_media"); | ||||
|                     final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp; | ||||
| @ -210,39 +178,7 @@ public class StoriesService extends BaseService { | ||||
|                                            userJson.optString("full_name"), | ||||
|                                            userJson.optBoolean("is_private"), | ||||
|                                            userJson.getString("profile_pic_url"), | ||||
|                                            null, | ||||
|                                            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 | ||||
|                                            userJson.optBoolean("is_verified") | ||||
|                 ); | ||||
|                 feedStoryModels.add(new FeedStoryModel( | ||||
|                         node.getString("id"), | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| <?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:tools="http://schemas.android.com/tools" | ||||
|     android:id="@+id/main_container" | ||||
| @ -62,13 +62,14 @@ | ||||
|         android:id="@+id/main_nav_host" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:clipToPadding="false" | ||||
|         app:layout_behavior="@string/appbar_scrolling_view_behavior" /> | ||||
| 
 | ||||
|     <!--app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"--> | ||||
|     <com.google.android.material.bottomnavigation.BottomNavigationView | ||||
|         android:id="@+id/bottomNavView" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="bottom" | ||||
|         app:labelVisibilityMode="auto" | ||||
|         app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" /> | ||||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
|         app:labelVisibilityMode="auto" /> | ||||
| </awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout> | ||||
| @ -1,312 +1,315 @@ | ||||
| <?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:tools="http://schemas.android.com/tools" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent" | ||||
|     android:clipToPadding="false"> | ||||
|     android:clipToPadding="false" | ||||
|     android:orientation="vertical"> | ||||
| 
 | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|         android:id="@+id/chats" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_weight="1" | ||||
|         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" /> | ||||
| 
 | ||||
|     <androidx.constraintlayout.widget.Barrier | ||||
|         android:id="@+id/chats_barrier" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         app:barrierDirection="bottom" /> | ||||
|     <androidx.constraintlayout.widget.ConstraintLayout | ||||
|         android:id="@+id/input_holder" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content"> | ||||
| 
 | ||||
|     <View | ||||
|         android:id="@+id/reply_bg" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:background="@drawable/bg_input" | ||||
|         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.constraintlayout.widget.Barrier | ||||
|             android:id="@+id/chats_barrier" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="0dp" | ||||
|             app:barrierDirection="bottom" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatTextView | ||||
|         android:id="@+id/reply_info" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:ellipsize="end" | ||||
|         android:paddingStart="16dp" | ||||
|         android:paddingTop="8dp" | ||||
|         android:paddingEnd="16dp" | ||||
|         android:paddingBottom="4dp" | ||||
|         android:singleLine="true" | ||||
|         android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | ||||
|         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" /> | ||||
|         <View | ||||
|             android:id="@+id/reply_bg" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:background="@drawable/bg_input" | ||||
|             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.emoji.widget.EmojiAppCompatTextView | ||||
|         android:id="@+id/reply_preview_text" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:ellipsize="end" | ||||
|         android:paddingStart="16dp" | ||||
|         android:paddingTop="4dp" | ||||
|         android:paddingEnd="16dp" | ||||
|         android:paddingBottom="8dp" | ||||
|         android:singleLine="true" | ||||
|         android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | ||||
|         android: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.AppCompatTextView | ||||
|             android:id="@+id/reply_info" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:ellipsize="end" | ||||
|             android:paddingStart="16dp" | ||||
|             android:paddingTop="8dp" | ||||
|             android:paddingEnd="16dp" | ||||
|             android:paddingBottom="4dp" | ||||
|             android:singleLine="true" | ||||
|             android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | ||||
|             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" /> | ||||
| 
 | ||||
|     <com.facebook.drawee.view.SimpleDraweeView | ||||
|         android:id="@+id/reply_preview_image" | ||||
|         android:layout_width="@dimen/dm_inbox_avatar_size_small" | ||||
|         android:layout_height="@dimen/dm_inbox_avatar_size_small" | ||||
|         android:layout_marginEnd="8dp" | ||||
|         android:visibility="gone" | ||||
|         app:actualImageScaleType="centerCrop" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/reply_preview_text" | ||||
|         app:layout_constraintEnd_toStartOf="@id/reply_cancel" | ||||
|         app:layout_constraintStart_toEndOf="@id/reply_preview_text" | ||||
|         app:layout_constraintTop_toTopOf="@id/reply_info" | ||||
|         tools:background="@mipmap/ic_launcher" | ||||
|         tools:visibility="gone" /> | ||||
|         <androidx.emoji.widget.EmojiAppCompatTextView | ||||
|             android:id="@+id/reply_preview_text" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:ellipsize="end" | ||||
|             android:paddingStart="16dp" | ||||
|             android:paddingTop="4dp" | ||||
|             android:paddingEnd="16dp" | ||||
|             android:paddingBottom="8dp" | ||||
|             android:singleLine="true" | ||||
|             android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" | ||||
|             android: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 | ||||
|         android:id="@+id/reply_cancel" | ||||
|         android:layout_width="24dp" | ||||
|         android:layout_height="24dp" | ||||
|         android:layout_marginEnd="12dp" | ||||
|         android:visibility="gone" | ||||
|         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" /> | ||||
|         <com.facebook.drawee.view.SimpleDraweeView | ||||
|             android:id="@+id/reply_preview_image" | ||||
|             android:layout_width="@dimen/dm_inbox_avatar_size_small" | ||||
|             android:layout_height="@dimen/dm_inbox_avatar_size_small" | ||||
|             android:layout_marginEnd="8dp" | ||||
|             android:visibility="gone" | ||||
|             app:actualImageScaleType="centerCrop" | ||||
|             app:layout_constraintBottom_toBottomOf="@id/reply_preview_text" | ||||
|             app:layout_constraintEnd_toStartOf="@id/reply_cancel" | ||||
|             app:layout_constraintStart_toEndOf="@id/reply_preview_text" | ||||
|             app:layout_constraintTop_toTopOf="@id/reply_info" | ||||
|             tools:background="@mipmap/ic_launcher" | ||||
|             tools:visibility="gone" /> | ||||
| 
 | ||||
|     <!--<androidx.constraintlayout.widget.Group--> | ||||
|     <!--    android:id="@+id/reply_group"--> | ||||
|     <!--    android:layout_width="0dp"--> | ||||
|     <!--    android:layout_height="0dp"--> | ||||
|     <!--    android:visibility="gone"--> | ||||
|     <!--    app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"--> | ||||
|     <!--    tools:visibility="visible" />--> | ||||
|         <androidx.appcompat.widget.AppCompatImageView | ||||
|             android:id="@+id/reply_cancel" | ||||
|             android:layout_width="24dp" | ||||
|             android:layout_height="24dp" | ||||
|             android:layout_marginEnd="12dp" | ||||
|             android:visibility="gone" | ||||
|             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 | ||||
|         android:id="@+id/input_bg" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_marginStart="4dp" | ||||
|         android:layout_marginEnd="4dp" | ||||
|         android:background="@drawable/bg_input" | ||||
|         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" /> | ||||
|         <!--<androidx.constraintlayout.widget.Group--> | ||||
|         <!--    android:id="@+id/reply_group"--> | ||||
|         <!--    android:layout_width="0dp"--> | ||||
|         <!--    android:layout_height="0dp"--> | ||||
|         <!--    android:visibility="gone"--> | ||||
|         <!--    app:constraint_referenced_ids="reply_bg,reply_cancel,reply_info,reply_item_type,reply_preview"--> | ||||
|         <!--    tools:visibility="visible" />--> | ||||
| 
 | ||||
|     <com.google.android.material.button.MaterialButton | ||||
|         android:id="@+id/emoji_toggle" | ||||
|         style="@style/Widget.MaterialComponents.Button.Icon.NoInsets" | ||||
|         android:layout_width="24dp" | ||||
|         android:layout_height="24dp" | ||||
|         android:layout_marginStart="8dp" | ||||
|         android:layout_marginEnd="2dp" | ||||
|         android:background="@android:color/transparent" | ||||
|         android:scrollbars="none" | ||||
|         android:visibility="gone" | ||||
|         app:icon="@drawable/ic_face_24" | ||||
|         app:iconGravity="textStart" | ||||
|         app:iconSize="24dp" | ||||
|         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" /> | ||||
|         <View | ||||
|             android:id="@+id/input_bg" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:layout_marginStart="4dp" | ||||
|             android:layout_marginEnd="4dp" | ||||
|             android:background="@drawable/bg_input" | ||||
|             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" /> | ||||
| 
 | ||||
|     <awais.instagrabber.customviews.KeyNotifyingEmojiEditText | ||||
|         android:id="@+id/input" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_marginStart="4dp" | ||||
|         android:background="@android:color/transparent" | ||||
|         android:hint="@string/message" | ||||
|         android:paddingTop="12dp" | ||||
|         android:paddingBottom="12dp" | ||||
|         android:textColor="?dmInputTextColor" | ||||
|         android:textColorHint="@color/grey_500" | ||||
|         android:visibility="gone" | ||||
|         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" /> | ||||
|         <com.google.android.material.button.MaterialButton | ||||
|             android:id="@+id/emoji_toggle" | ||||
|             style="@style/Widget.MaterialComponents.Button.Icon.NoInsets" | ||||
|             android:layout_width="24dp" | ||||
|             android:layout_height="24dp" | ||||
|             android:layout_marginStart="8dp" | ||||
|             android:layout_marginEnd="2dp" | ||||
|             android:background="@android:color/transparent" | ||||
|             android:scrollbars="none" | ||||
|             android:visibility="gone" | ||||
|             app:icon="@drawable/ic_face_24" | ||||
|             app:iconGravity="textStart" | ||||
|             app:iconSize="24dp" | ||||
|             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" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatImageButton | ||||
|         android:id="@+id/gif" | ||||
|         android:layout_width="32dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:background="@android:color/transparent" | ||||
|         android:scaleType="fitCenter" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|         app:layout_constraintEnd_toStartOf="@id/camera" | ||||
|         app:layout_constraintStart_toEndOf="@id/input" | ||||
|         app:layout_constraintTop_toTopOf="@id/input" | ||||
|         app:srcCompat="@drawable/ic_round_gif_24" | ||||
|         tools:visibility="visible" /> | ||||
|         <awais.instagrabber.customviews.KeyNotifyingEmojiEditText | ||||
|             android:id="@+id/input" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_marginStart="4dp" | ||||
|             android:background="@android:color/transparent" | ||||
|             android:hint="@string/message" | ||||
|             android:paddingTop="12dp" | ||||
|             android:paddingBottom="12dp" | ||||
|             android:textColor="?dmInputTextColor" | ||||
|             android:textColorHint="@color/grey_500" | ||||
|             android:visibility="gone" | ||||
|             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 | ||||
|         android:id="@+id/camera" | ||||
|         android:layout_width="32dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_marginStart="4dp" | ||||
|         android:background="@android:color/transparent" | ||||
|         android:paddingStart="4dp" | ||||
|         android:paddingEnd="4dp" | ||||
|         android:scaleType="fitCenter" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|         app:layout_constraintEnd_toStartOf="@id/gallery" | ||||
|         app:layout_constraintStart_toEndOf="@id/gif" | ||||
|         app:layout_constraintTop_toTopOf="@id/input" | ||||
|         app:srcCompat="@drawable/ic_camera_24" | ||||
|         tools:visibility="visible" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton | ||||
|             android:id="@+id/gif" | ||||
|             android:layout_width="32dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:background="@android:color/transparent" | ||||
|             android:scaleType="fitCenter" | ||||
|             android:visibility="gone" | ||||
|             app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|             app:layout_constraintEnd_toStartOf="@id/camera" | ||||
|             app:layout_constraintStart_toEndOf="@id/input" | ||||
|             app:layout_constraintTop_toTopOf="@id/input" | ||||
|             app:srcCompat="@drawable/ic_round_gif_24" | ||||
|             tools:visibility="visible" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatImageButton | ||||
|         android:id="@+id/gallery" | ||||
|         android:layout_width="32dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_marginStart="4dp" | ||||
|         android:layout_marginEnd="16dp" | ||||
|         android:background="@android:color/transparent" | ||||
|         android:paddingStart="4dp" | ||||
|         android:paddingEnd="4dp" | ||||
|         android:scaleType="fitCenter" | ||||
|         android:src="@drawable/ic_image_24" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|         app:layout_constraintEnd_toStartOf="@id/send" | ||||
|         app:layout_constraintStart_toEndOf="@id/camera" | ||||
|         app:layout_constraintTop_toTopOf="@id/input" | ||||
|         tools:visibility="visible" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton | ||||
|             android:id="@+id/camera" | ||||
|             android:layout_width="32dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:layout_marginStart="4dp" | ||||
|             android:background="@android:color/transparent" | ||||
|             android:paddingStart="4dp" | ||||
|             android:paddingEnd="4dp" | ||||
|             android:scaleType="fitCenter" | ||||
|             android:visibility="gone" | ||||
|             app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|             app:layout_constraintEnd_toStartOf="@id/gallery" | ||||
|             app:layout_constraintStart_toEndOf="@id/gif" | ||||
|             app:layout_constraintTop_toTopOf="@id/input" | ||||
|             app:srcCompat="@drawable/ic_camera_24" | ||||
|             tools:visibility="visible" /> | ||||
| 
 | ||||
|     <awais.instagrabber.customviews.RecordView | ||||
|         android:id="@+id/record_view" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="0dp" | ||||
|         android:visibility="gone" | ||||
|         app:counter_time_color="@color/white" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|         app:layout_constraintEnd_toEndOf="@id/input_bg" | ||||
|         app:layout_constraintStart_toStartOf="@id/input" | ||||
|         app:layout_constraintTop_toBottomOf="@id/chats_barrier" | ||||
|         app:slide_to_cancel_arrow="@drawable/recv_ic_arrow" | ||||
|         app:slide_to_cancel_arrow_color="@color/white" | ||||
|         app:slide_to_cancel_bounds="0dp" | ||||
|         app:slide_to_cancel_margin_right="16dp" | ||||
|         app:slide_to_cancel_text="Slide To Cancel" | ||||
|         app:slide_to_cancel_text_color="@color/white" | ||||
|         tools:visibility="visible" /> | ||||
|         <androidx.appcompat.widget.AppCompatImageButton | ||||
|             android:id="@+id/gallery" | ||||
|             android:layout_width="32dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:layout_marginStart="4dp" | ||||
|             android:layout_marginEnd="16dp" | ||||
|             android:background="@android:color/transparent" | ||||
|             android:paddingStart="4dp" | ||||
|             android:paddingEnd="4dp" | ||||
|             android:scaleType="fitCenter" | ||||
|             android:src="@drawable/ic_image_24" | ||||
|             android:visibility="gone" | ||||
|             app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|             app:layout_constraintEnd_toStartOf="@id/send" | ||||
|             app:layout_constraintStart_toEndOf="@id/camera" | ||||
|             app:layout_constraintTop_toTopOf="@id/input" | ||||
|             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" /> | ||||
|         <awais.instagrabber.customviews.RecordView | ||||
|             android:id="@+id/record_view" | ||||
|             android:layout_width="0dp" | ||||
|             android:layout_height="0dp" | ||||
|             android:visibility="gone" | ||||
|             app:counter_time_color="@color/white" | ||||
|             app:layout_constraintBottom_toBottomOf="@id/input_bg" | ||||
|             app:layout_constraintEnd_toEndOf="@id/input_bg" | ||||
|             app:layout_constraintStart_toStartOf="@id/input" | ||||
|             app:layout_constraintTop_toBottomOf="@id/chats_barrier" | ||||
|             app:slide_to_cancel_arrow="@drawable/recv_ic_arrow" | ||||
|             app:slide_to_cancel_arrow_color="@color/white" | ||||
|             app:slide_to_cancel_bounds="0dp" | ||||
|             app:slide_to_cancel_margin_right="16dp" | ||||
|             app:slide_to_cancel_text="Slide To Cancel" | ||||
|             app:slide_to_cancel_text_color="@color/white" | ||||
|             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 | ||||
|         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_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" | ||||
|         android:layout_height="250dp" | ||||
|         android:layout_marginBottom="-250dp" | ||||
|         android:alpha="0" | ||||
|         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> | ||||
|         app:layout_constraintStart_toStartOf="parent" /> | ||||
| </awais.instagrabber.customviews.InsetsAnimationLinearLayout> | ||||
| @ -11,7 +11,6 @@ | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:clipToPadding="false" | ||||
|         android:paddingBottom="?attr/actionBarSize" | ||||
|         app:layout_behavior="@string/appbar_scrolling_view_behavior"> | ||||
| 
 | ||||
|         <androidx.recyclerview.widget.RecyclerView | ||||
| @ -19,6 +18,7 @@ | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             android:clipToPadding="false" | ||||
|             android:paddingBottom="?actionBarSize" | ||||
|             app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" | ||||
|             app:spanCount="2" | ||||
|             tools:itemCount="10" | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:clipToPadding="false" | ||||
|         android:paddingBottom="?attr/actionBarSize" | ||||
|         app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/header"> | ||||
|  | ||||
| @ -19,7 +19,6 @@ | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:clipToPadding="false" | ||||
|         android:paddingBottom="?attr/actionBarSize" | ||||
|         app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||||
|         app:layout_constraintBottom_toBottomOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/header"> | ||||
|  | ||||
| @ -38,7 +38,6 @@ | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:layout_gravity="top" | ||||
|         android:layout_marginTop="@dimen/private_page_margins" | ||||
|         android:gravity="center" | ||||
|         android:orientation="vertical" | ||||
|         android:visibility="gone" | ||||
| @ -48,6 +47,8 @@ | ||||
|             android:id="@+id/privatePage1" | ||||
|             android:layout_width="@dimen/private_page_size" | ||||
|             android:layout_height="@dimen/private_page_size" | ||||
|             android:visibility="gone" | ||||
|             tools:visibility="visible" | ||||
|             app:srcCompat="@drawable/lock" /> | ||||
| 
 | ||||
|         <androidx.appcompat.widget.AppCompatTextView | ||||
| @ -55,6 +56,8 @@ | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:gravity="center" | ||||
|             android:visibility="gone" | ||||
|             tools:visibility="visible" | ||||
|             android:text="@string/priv_acc" | ||||
|             android:textAppearance="@style/TextAppearance.AppCompat.Large" /> | ||||
|     </LinearLayout> | ||||
|  | ||||
| @ -43,7 +43,9 @@ | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_below="@+id/title" | ||||
|             android:ellipsize="end" | ||||
|             android:gravity="center_vertical" | ||||
|             android:maxLines="1" | ||||
|             android:textAppearance="@style/TextAppearance.AppCompat.Subhead" | ||||
|             android:textSize="15sp" | ||||
|             android:visibility="visible" | ||||
|  | ||||
| @ -15,10 +15,11 @@ | ||||
|         android:transitionName="profile_pic" | ||||
|         android:visibility="invisible" | ||||
|         app:actualImageScaleType="centerCrop" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/btnTagged" | ||||
|         app:layout_constraintBottom_toTopOf="@id/top_barrier" | ||||
|         app:layout_constraintEnd_toStartOf="@id/btnFollow" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toTopOf="parent" | ||||
|         app:layout_constraintVertical_bias="0" | ||||
|         tools:foreground="@mipmap/ic_launcher" | ||||
|         tools:visibility="visible" /> | ||||
| 
 | ||||
| @ -112,7 +113,7 @@ | ||||
|         app:chipBackgroundColor="@null" | ||||
|         app:chipIcon="@drawable/ic_outline_person_pin_24" | ||||
|         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_constraintTop_toBottomOf="@id/fav_chip" | ||||
|         app:rippleColor="@color/deep_orange_400" | ||||
| @ -128,12 +129,18 @@ | ||||
|         app:chipBackgroundColor="@null" | ||||
|         app:chipIcon="@drawable/ic_round_send_24" | ||||
|         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_constraintTop_toBottomOf="@id/fav_chip" | ||||
|         app:rippleColor="@color/green" | ||||
|         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 | ||||
|         android:id="@+id/mainFullName" | ||||
|         android:layout_width="wrap_content" | ||||
| @ -146,40 +153,37 @@ | ||||
|         android:singleLine="true" | ||||
|         android:textAppearance="@style/TextAppearance.AppCompat.Body1" | ||||
|         android:textStyle="bold" | ||||
|         app:layout_constraintBottom_toTopOf="@id/mainBiography" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/btnTagged" | ||||
|         app:layout_constraintTop_toBottomOf="@id/top_barrier" | ||||
|         tools:text="Austin Huang" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatImageView | ||||
|         android:id="@+id/isVerified" | ||||
|         android:layout_width="30dp" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_gravity="center" | ||||
|         android:adjustViewBounds="true" | ||||
|         android:paddingTop="4dp" | ||||
|         android:paddingBottom="4dp" | ||||
|         android:scaleType="fitCenter" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/mainFullName" | ||||
|         app:layout_constraintStart_toEndOf="@id/mainFullName" | ||||
|         app:layout_constraintTop_toBottomOf="@id/btnTagged" | ||||
|         app:layout_constraintTop_toTopOf="@id/mainFullName" | ||||
|         app:srcCompat="@drawable/verified" | ||||
|         tools:visibility="visible" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatImageView | ||||
|         android:id="@+id/isPrivate" | ||||
|         android:layout_width="25dp" | ||||
|         android:layout_height="match_parent" | ||||
|         android:layout_height="0dp" | ||||
|         android:layout_gravity="center" | ||||
|         android:adjustViewBounds="true" | ||||
|         android:paddingTop="4dp" | ||||
|         android:paddingBottom="4dp" | ||||
|         android:scaleType="fitCenter" | ||||
|         android:tint="@color/red_500" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toBottomOf="@id/mainFullName" | ||||
|         app:layout_constraintStart_toEndOf="@id/isVerified" | ||||
|         app:layout_constraintTop_toBottomOf="@id/btnTagged" | ||||
|         app:layout_constraintTop_toTopOf="@id/mainFullName" | ||||
|         app:srcCompat="@drawable/lock" | ||||
|         tools:visibility="visible" /> | ||||
| 
 | ||||
| @ -233,6 +237,7 @@ | ||||
|         android:paddingBottom="4dp" | ||||
|         android:textAppearance="@style/TextAppearance.AppCompat.Body1" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toTopOf="@id/profileContext" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/mainBiography" | ||||
| @ -254,7 +259,7 @@ | ||||
|         android:textSize="12sp" | ||||
|         android:textStyle="italic" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toTopOf="@id/counts_barrier" | ||||
|         app:layout_constraintBottom_toTopOf="@id/counts_divider" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/mainUrl" | ||||
| @ -262,11 +267,12 @@ | ||||
|         tools:visibility="visible" /> | ||||
| 
 | ||||
|     <include | ||||
|         android:id="@+id/counts_barrier" | ||||
|         android:id="@+id/counts_divider" | ||||
|         layout="@layout/item_pref_divider" | ||||
|         android:layout_width="0dp" | ||||
|         android:layout_height="1dp" | ||||
|         android:visibility="gone" | ||||
|         app:layout_constraintBottom_toTopOf="@id/mainPostCount" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/profileContext" | ||||
| @ -285,7 +291,7 @@ | ||||
|         app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" | ||||
|         app:layout_constraintEnd_toStartOf="@id/mainFollowers" | ||||
|         app:layout_constraintStart_toStartOf="parent" | ||||
|         app:layout_constraintTop_toBottomOf="@id/counts_barrier" | ||||
|         app:layout_constraintTop_toBottomOf="@id/counts_divider" | ||||
|         tools:text="35\nPosts" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatTextView | ||||
| @ -302,7 +308,7 @@ | ||||
|         app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" | ||||
|         app:layout_constraintEnd_toStartOf="@id/mainFollowing" | ||||
|         app:layout_constraintStart_toEndOf="@id/mainPostCount" | ||||
|         app:layout_constraintTop_toBottomOf="@id/counts_barrier" | ||||
|         app:layout_constraintTop_toBottomOf="@id/counts_divider" | ||||
|         tools:text="68\nFollowers" /> | ||||
| 
 | ||||
|     <androidx.appcompat.widget.AppCompatTextView | ||||
| @ -319,7 +325,7 @@ | ||||
|         app:layout_constraintBottom_toBottomOf="@id/highlights_barrier" | ||||
|         app:layout_constraintEnd_toEndOf="parent" | ||||
|         app:layout_constraintStart_toEndOf="@id/mainFollowers" | ||||
|         app:layout_constraintTop_toBottomOf="@id/counts_barrier" | ||||
|         app:layout_constraintTop_toBottomOf="@id/counts_divider" | ||||
|         tools:text="64\nFollowing" /> | ||||
| 
 | ||||
|     <androidx.constraintlayout.widget.Barrier | ||||
|  | ||||
| @ -19,9 +19,9 @@ | ||||
|     <color name="btn_lightpink_text_color">@color/text_color_light</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_text_color">#FF000000</color> | ||||
|     <color name="btn_lightorange_text_color">@color/black</color> | ||||
| 
 | ||||
|     <color name="dm_profile_button_color">#efefef</color> | ||||
| 
 | ||||
|  | ||||
| @ -9,6 +9,13 @@ | ||||
|             motion:layout_constraintEnd_toEndOf="parent" | ||||
|             motion:layout_constraintStart_toStartOf="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 android:id="@+id/end"> | ||||
|         <Constraint | ||||
|  | ||||
| @ -5,7 +5,7 @@ buildscript { | ||||
|     } | ||||
| 
 | ||||
|     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" | ||||
|         classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" | ||||
|     } | ||||
|  | ||||
							
								
								
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| distributionBase=GRADLE_USER_HOME | ||||
| distributionPath=wrapper/dists | ||||
| distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7 | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip | ||||
| distributionSha256Sum=13bf8d3cf8eeeb5770d19741a59bde9bd966dd78d17f1bbad787a05ef19d1c2d | ||||
| distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip | ||||
| zipStoreBase=GRADLE_USER_HOME | ||||
| zipStorePath=wrapper/dists | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user