mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-31 03:25:34 +00:00 
			
		
		
		
	Merge branch 'master' into dm-notifications-enhancements
This commit is contained in:
		
						commit
						d42d4dd3ea
					
				| @ -61,6 +61,15 @@ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "CrazyMarvin", | ||||
|       "name": "CrazyMarvin", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/15004217?v=4", | ||||
|       "profile": "https://github.com/CrazyMarvin", | ||||
|       "contributions": [ | ||||
|         "financial" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "KevinNThomas", | ||||
|       "name": "Kevin Thomas", | ||||
| @ -82,16 +91,6 @@ | ||||
|         "question" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "e-edgren", | ||||
|       "name": "Airikr", | ||||
|       "avatar_url": "https://avatars0.githubusercontent.com/u/53869451", | ||||
|       "profile": "https://airikr.me/", | ||||
|       "contributions": [ | ||||
|         "ideas", | ||||
|         "question" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "alin-1", | ||||
|       "name": "ALIN", | ||||
| @ -102,6 +101,26 @@ | ||||
|         "ideas" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "RickyM7", | ||||
|       "name": "Ricardo", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/24703825?v=4", | ||||
|       "profile": "https://github.com/RickyM7", | ||||
|       "contributions": [ | ||||
|         "bug", | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "e-edgren", | ||||
|       "name": "Airikr", | ||||
|       "avatar_url": "https://avatars0.githubusercontent.com/u/53869451", | ||||
|       "profile": "https://airikr.me/", | ||||
|       "contributions": [ | ||||
|         "ideas", | ||||
|         "question" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "Akrai", | ||||
|       "name": "Akrai", | ||||
| @ -112,6 +131,15 @@ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "cizordj", | ||||
|       "name": "Cézar Augusto", | ||||
|       "avatar_url": "https://avatars2.githubusercontent.com/u/32869222?v=4", | ||||
|       "profile": "https://github.com/cizordj", | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "farzadx", | ||||
|       "name": "farzadx", | ||||
| @ -149,19 +177,28 @@ | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "kernoeb", | ||||
|       "name": "kernoeb", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/24623168", | ||||
|       "profile": "https://becauseofprog.fr/", | ||||
|       "login": "initdebugs", | ||||
|       "name": "Initdebugs", | ||||
|       "avatar_url": "https://avatars0.githubusercontent.com/u/75781464?v=4", | ||||
|       "profile": "https://github.com/initdebugs", | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "Lego8486", | ||||
|       "name": "Ten_Lego", | ||||
|       "avatar_url": "https://avatars1.githubusercontent.com/u/47414485", | ||||
|       "profile": "https://github.com/Lego8486", | ||||
|       "login": "CrafterSvK", | ||||
|       "name": "Jakub Janek", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/8365659?v=4", | ||||
|       "profile": "https://janek.xyz/", | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "kernoeb", | ||||
|       "name": "kernoeb", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/24623168", | ||||
|       "profile": "https://becauseofprog.fr/", | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
| @ -229,16 +266,6 @@ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "RickyM7", | ||||
|       "name": "Ricardo", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/24703825?v=4", | ||||
|       "profile": "https://github.com/RickyM7", | ||||
|       "contributions": [ | ||||
|         "bug", | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "rikishi0071", | ||||
|       "name": "rikishi0071", | ||||
| @ -257,6 +284,15 @@ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "Lego8486", | ||||
|       "name": "Ten_Lego", | ||||
|       "avatar_url": "https://avatars1.githubusercontent.com/u/47414485", | ||||
|       "profile": "https://github.com/Lego8486", | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "wagnim", | ||||
|       "name": "wagnim", | ||||
| @ -265,6 +301,15 @@ | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
|     }, | ||||
|     { | ||||
|       "login": "ysakamoto", | ||||
|       "name": "ysakamoto", | ||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/1331642?v=4", | ||||
|       "profile": "https://github.com/ysakamoto", | ||||
|       "contributions": [ | ||||
|         "translation" | ||||
|       ] | ||||
|     } | ||||
|   ], | ||||
|   "contributorsPerLine": 6, | ||||
|  | ||||
							
								
								
									
										46
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								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 --> | ||||
| 
 | ||||
| We're previously known as InstaGrabber. | ||||
| @ -32,11 +32,15 @@ Version status: . | ||||
| 
 | ||||
| ## Contact us | ||||
| 
 | ||||
| * Use [GitHub issues](https://github.com/austinhuang0131/instagrabber/issues) when possible. | ||||
| * Email: [Barinsta@AustinHuang.me](mailto:barinsta@austinhuang.me?body=Please%20note%20that%20your%20email%20address%20and%20the%20entire%20content%20will%20be%20published%20onto%20GitHub%20issues.%20If%20you%20do%20not%20wish%20to%20do%20that%2C%20use%20other%20contact%20methods%20instead.) (Synced to GitHub issues) | ||||
| * Reddit: [r/Barinsta](https://reddit.com/r/barinsta) | ||||
| * Reddit: [](https://reddit.com/r/barinsta) | ||||
| * Chat (Bridged to each other): [](https://matrix.to/#/#barinsta:matrix.org) [](https://t.me/grabber_app) [](https://discord.gg/YtEDzN2) [](https://gitter.im/instagrabber/general) | ||||
| 
 | ||||
| ## Contributors | ||||
| @ -53,36 +57,43 @@ Prominent contributors are listed here in the [all-contributors](https://allcont | ||||
|     <td align="center"><a href="https://github.com/andersonvom"><img src="https://avatars3.githubusercontent.com/u/69922?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anderson Mesquita</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=andersonvom" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aandersonvom" title="Bug reports">🐛</a></td> | ||||
|     <td align="center"><a href="http://rerolledgeek.blogspot.com/"><img src="https://avatars3.githubusercontent.com/u/5278488?s=100" width="100px;" alt=""/><br /><sub><b>AWAiS</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=AwaisKing" title="Code">💻</a></td> | ||||
|     <td align="center"><a href="https://stefannajdovski.com/"><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="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/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> | ||||
|   </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://airikr.me/"><img src="https://avatars0.githubusercontent.com/u/53869451?s=100" width="100px;" alt=""/><br /><sub><b>Airikr</b></sub></a><br /><a href="#ideas-e-edgren" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-e-edgren" title="Answering Questions">💬</a></td> | ||||
|     <td align="center"><a href="https://alin.atwebpages.com/"><img src="https://avatars2.githubusercontent.com/u/13281020?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ALIN</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aalin-1" title="Bug reports">🐛</a> <a href="#ideas-alin-1" title="Ideas, Planning, & Feedback">🤔</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://airikr.me/"><img src="https://avatars0.githubusercontent.com/u/53869451?s=100" width="100px;" alt=""/><br /><sub><b>Airikr</b></sub></a><br /><a href="#ideas-e-edgren" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-e-edgren" title="Answering Questions">💬</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> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/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> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <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://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/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/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/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> | ||||
|   </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://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> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://github.com/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/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> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||
|     <td align="center"><a href="https://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> | ||||
|   </tr> | ||||
|   <tr> | ||||
|     <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/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> | ||||
|   </tr> | ||||
| </table> | ||||
| 
 | ||||
| @ -92,12 +103,11 @@ Prominent contributors are listed here in the [all-contributors](https://allcont | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| This app is originally made by [@AwaisKing](https://github.com/AwaisKing) on [GitLab](https://gitlab.com/AwaisKing/instagrabber). | ||||
| This app's predecessor, InstaGrabber, is originally made by [@AwaisKing](https://github.com/AwaisKing) on [GitLab](https://gitlab.com/AwaisKing/instagrabber). | ||||
| 
 | ||||
|     Barinsta (ex-InstaGrabber) | ||||
|     Copyright (C) 2019  AWAiS        <chapter50000@hotmail.com> | ||||
|     Copyright (C) 2020  Austin Huang <im@austinhuang.me> | ||||
|                         Ammar Githam <ammargitham786@gmail.com> | ||||
|     Barinsta | ||||
|     Copyright (C) 2020-2021  Austin Huang <im@austinhuang.me> | ||||
|                              Ammar Githam <ammargitham786@gmail.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 | ||||
|  | ||||
| @ -10,8 +10,8 @@ android { | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 29 | ||||
| 
 | ||||
|         versionCode 54 | ||||
|         versionName '19.0.2' | ||||
|         versionCode 56 | ||||
|         versionName '19.0.4' | ||||
| 
 | ||||
|         multiDexEnabled true | ||||
| 
 | ||||
| @ -98,6 +98,9 @@ dependencies { | ||||
|     implementation "androidx.emoji:emoji:$emoji_compat_version" | ||||
|     implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version" | ||||
| 
 | ||||
|     // implementation 'com.github.hendrawd:StorageUtil:1.1.0' | ||||
|     implementation 'me.austinhuang:AutoLinkTextViewV2:-SNAPSHOT' | ||||
| 
 | ||||
|     implementation 'org.jsoup:jsoup:1.13.1' | ||||
|     implementation 'com.facebook.fresco:fresco:2.3.0' | ||||
|     implementation 'com.facebook.fresco:animated-webp:2.3.0' | ||||
| @ -109,7 +112,6 @@ dependencies { | ||||
| 
 | ||||
|     implementation 'org.apache.commons:commons-imaging:1.0-alpha2' | ||||
|     implementation 'com.ibm.icu:icu4j:68.1' | ||||
|     implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT' | ||||
|     implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2' | ||||
|     implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4' | ||||
| 
 | ||||
|  | ||||
| @ -83,8 +83,8 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie | ||||
|         } | ||||
|     }; | ||||
|     private final CommentCallback commentCallback; | ||||
|     private CommentModel selected; | ||||
|     private int selectedIndex; | ||||
|     private CommentModel selected, toChangeLike; | ||||
|     private int selectedIndex, likedIndex; | ||||
| 
 | ||||
|     public CommentsAdapter(final CommentCallback commentCallback) { | ||||
|         super(DIFF_CALLBACK); | ||||
| @ -106,10 +106,12 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { | ||||
|         final CommentModel commentModel = getItem(position); | ||||
|         CommentModel commentModel = getItem(position); | ||||
|         if (commentModel == null) return; | ||||
|         final int type = getItemViewType(position); | ||||
|         final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId()); | ||||
|         final boolean toLike = this.toChangeLike != null && this.toChangeLike.getId().equals(commentModel.getId()); | ||||
|         if (toLike) commentModel = this.toChangeLike; | ||||
|         if (type == TYPE_PARENT) { | ||||
|             final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder; | ||||
|             viewHolder.bind(commentModel, selected, commentCallback); | ||||
| @ -174,6 +176,14 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie | ||||
|         notifyItemChanged(selectedIndex); | ||||
|     } | ||||
| 
 | ||||
|     public void setLiked(final CommentModel commentModel, final boolean liked) { | ||||
|         likedIndex = getCurrentList().indexOf(commentModel); | ||||
|         CommentModel newCommentModel = commentModel; | ||||
|         newCommentModel.setLiked(liked); | ||||
|         this.toChangeLike = newCommentModel; | ||||
|         notifyItemChanged(likedIndex); | ||||
|     } | ||||
| 
 | ||||
|     public CommentModel getSelected() { | ||||
|         return selected; | ||||
|     } | ||||
|  | ||||
| @ -46,7 +46,7 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { | ||||
|             return oldItem.getPostId().equals(newItem.getPostId()); | ||||
|             return oldItem.getPostId().equals(newItem.getPostId()) && oldItem.getPostCaption().equals(newItem.getPostCaption()); | ||||
|         } | ||||
|     }; | ||||
|     private final AdapterSelectionCallback adapterSelectionCallback = new AdapterSelectionCallback() { | ||||
|  | ||||
| @ -4,12 +4,19 @@ import android.view.LayoutInflater; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.recyclerview.widget.DiffUtil; | ||||
| import androidx.recyclerview.widget.ListAdapter; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder; | ||||
| import awais.instagrabber.databinding.ItemHighlightBinding; | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> { | ||||
|     private final OnFeedStoryClickListener listener; | ||||
| @ -22,7 +29,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) { | ||||
|             return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); | ||||
|             return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
| @ -47,5 +54,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt | ||||
| 
 | ||||
|     public interface OnFeedStoryClickListener { | ||||
|         void onFeedStoryClick(FeedStoryModel model, int position); | ||||
| 
 | ||||
|         void onFeedStoryLongClick(FeedStoryModel model, int position); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										59
									
								
								app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								app/src/main/java/awais/instagrabber/adapters/FeedStoriesListAdapter.java
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,59 @@ | ||||
| package awais.instagrabber.adapters; | ||||
| 
 | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.recyclerview.widget.DiffUtil; | ||||
| import androidx.recyclerview.widget.ListAdapter; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.adapters.viewholder.StoryListViewHolder; | ||||
| import awais.instagrabber.databinding.ItemNotificationBinding; | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| public final class FeedStoriesListAdapter extends ListAdapter<FeedStoryModel, StoryListViewHolder> { | ||||
|     private final OnFeedStoryClickListener listener; | ||||
| 
 | ||||
|     private static final DiffUtil.ItemCallback<FeedStoryModel> diffCallback = new DiffUtil.ItemCallback<FeedStoryModel>() { | ||||
|         @Override | ||||
|         public boolean areItemsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) { | ||||
|             return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) { | ||||
|             return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     public FeedStoriesListAdapter(final OnFeedStoryClickListener listener) { | ||||
|         super(diffCallback); | ||||
|         this.listener = listener; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public StoryListViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { | ||||
|         final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); | ||||
|         final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false); | ||||
|         return new StoryListViewHolder(binding); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { | ||||
|         final FeedStoryModel model = getItem(position); | ||||
|         holder.bind(model, position, listener); | ||||
|     } | ||||
| 
 | ||||
|     public interface OnFeedStoryClickListener { | ||||
|         void onFeedStoryClick(final FeedStoryModel model, final int position); | ||||
| 
 | ||||
|         void onProfileClick(final String username); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,53 @@ | ||||
| package awais.instagrabber.adapters; | ||||
| 
 | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.DiffUtil; | ||||
| import androidx.recyclerview.widget.ListAdapter; | ||||
| 
 | ||||
| import awais.instagrabber.adapters.viewholder.StoryListViewHolder; | ||||
| import awais.instagrabber.databinding.ItemNotificationBinding; | ||||
| import awais.instagrabber.models.HighlightModel; | ||||
| 
 | ||||
| public final class HighlightStoriesListAdapter extends ListAdapter<HighlightModel, StoryListViewHolder> { | ||||
|     private final OnHighlightStoryClickListener listener; | ||||
| 
 | ||||
|     private static final DiffUtil.ItemCallback<HighlightModel> diffCallback = new DiffUtil.ItemCallback<HighlightModel>() { | ||||
|         @Override | ||||
|         public boolean areItemsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) { | ||||
|             return oldItem.getId().equals(newItem.getId()); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean areContentsTheSame(@NonNull final HighlightModel oldItem, @NonNull final HighlightModel newItem) { | ||||
|             return oldItem.getId().equals(newItem.getId()); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     public HighlightStoriesListAdapter(final OnHighlightStoryClickListener listener) { | ||||
|         super(diffCallback); | ||||
|         this.listener = listener; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public StoryListViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { | ||||
|         final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); | ||||
|         final ItemNotificationBinding binding = ItemNotificationBinding.inflate(layoutInflater, parent, false); | ||||
|         return new StoryListViewHolder(binding); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull final StoryListViewHolder holder, final int position) { | ||||
|         final HighlightModel model = getItem(position); | ||||
|         holder.bind(model, position, listener); | ||||
|     } | ||||
| 
 | ||||
|     public interface OnHighlightStoryClickListener { | ||||
|         void onHighlightClick(final HighlightModel model, final int position); | ||||
| 
 | ||||
|         void onProfileClick(final String username); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										45
									
								
								app/src/main/java/awais/instagrabber/adapters/LikesAdapter.java
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										45
									
								
								app/src/main/java/awais/instagrabber/adapters/LikesAdapter.java
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,45 @@ | ||||
| package awais.instagrabber.adapters; | ||||
| 
 | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.adapters.viewholder.FollowsViewHolder; | ||||
| import awais.instagrabber.databinding.ItemFollowBinding; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| 
 | ||||
| public final class LikesAdapter extends RecyclerView.Adapter<FollowsViewHolder> { | ||||
|     private final List<ProfileModel> profileModels; | ||||
|     private final View.OnClickListener onClickListener; | ||||
| 
 | ||||
|     public LikesAdapter(final List<ProfileModel> profileModels, | ||||
|                         final View.OnClickListener onClickListener) { | ||||
|         this.profileModels = profileModels; | ||||
|         this.onClickListener = onClickListener; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public FollowsViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { | ||||
|         final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); | ||||
|         final ItemFollowBinding binding = ItemFollowBinding.inflate(layoutInflater, parent, false); | ||||
|         return new FollowsViewHolder(binding); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onBindViewHolder(@NonNull final FollowsViewHolder holder, final int position) { | ||||
|         final ProfileModel model = profileModels.get(position); | ||||
|         holder.bind(model, null, onClickListener); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getItemCount() { | ||||
|         return profileModels.size(); | ||||
|     } | ||||
| } | ||||
| @ -76,16 +76,23 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N | ||||
|     private List<NotificationModel> sort(final List<NotificationModel> list) { | ||||
|         final List<NotificationModel> listCopy = new ArrayList<>(list); | ||||
|         Collections.sort(listCopy, (o1, o2) -> { | ||||
|             if (o1.getType() == o2.getType()) return 0; | ||||
|             // keep requests at top | ||||
|             if (o1.getType() == NotificationType.REQUEST) return -1; | ||||
|             if (o2.getType() == NotificationType.REQUEST) return 1; | ||||
|             return 0; | ||||
|             if (o1.getType() == o2.getType() | ||||
|                     && o1.getType() == NotificationType.REQUEST | ||||
|                     && o2.getType() == NotificationType.REQUEST) return 0; | ||||
|             else if (o1.getType() == NotificationType.REQUEST) return -1; | ||||
|             else if (o2.getType() == NotificationType.REQUEST) return 1; | ||||
|             // timestamp | ||||
|             return o1.getTimestamp() > o2.getTimestamp() ? -1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1); | ||||
|         }); | ||||
|         return listCopy; | ||||
|     } | ||||
| 
 | ||||
|     public interface OnNotificationClickListener { | ||||
|         void onNotificationClick(final NotificationModel model); | ||||
| 
 | ||||
|         void onProfileClick(final String username); | ||||
| 
 | ||||
|         void onPreviewClick(final NotificationModel model); | ||||
|     } | ||||
| } | ||||
| @ -1,75 +0,0 @@ | ||||
| // package awais.instagrabber.adapters; | ||||
| // | ||||
| // import android.view.LayoutInflater; | ||||
| // import android.view.View; | ||||
| // import android.view.ViewGroup; | ||||
| // | ||||
| // import androidx.annotation.NonNull; | ||||
| // import androidx.recyclerview.widget.DiffUtil; | ||||
| // import androidx.recyclerview.widget.ListAdapter; | ||||
| // | ||||
| // import awais.instagrabber.adapters.viewholder.PostViewerViewHolder; | ||||
| // import awais.instagrabber.databinding.ItemFullPostViewBinding; | ||||
| // import awais.instagrabber.interfaces.MentionClickListener; | ||||
| // import awais.instagrabber.models.ViewerPostModelWrapper; | ||||
| // | ||||
| // public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> { | ||||
| //     private final OnPostViewChildViewClickListener clickListener; | ||||
| //     private final OnPostCaptionLongClickListener longClickListener; | ||||
| //     private final MentionClickListener mentionClickListener; | ||||
| // | ||||
| //     private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() { | ||||
| //         @Override | ||||
| //         public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem, | ||||
| //                                        @NonNull final ViewerPostModelWrapper newItem) { | ||||
| //             return oldItem.getPosition() == newItem.getPosition(); | ||||
| //         } | ||||
| // | ||||
| //         @Override | ||||
| //         public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem, | ||||
| //                                           @NonNull final ViewerPostModelWrapper newItem) { | ||||
| //             return oldItem.getViewerPostModels().equals(newItem.getViewerPostModels()); | ||||
| //         } | ||||
| //     }; | ||||
| // | ||||
| //     public PostViewAdapter(final OnPostViewChildViewClickListener clickListener, | ||||
| //                            final OnPostCaptionLongClickListener longClickListener, | ||||
| //                            final MentionClickListener mentionClickListener) { | ||||
| //         super(diffCallback); | ||||
| //         this.clickListener = clickListener; | ||||
| //         this.longClickListener = longClickListener; | ||||
| //         this.mentionClickListener = mentionClickListener; | ||||
| //     } | ||||
| // | ||||
| //     @Override | ||||
| //     public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) { | ||||
| //         holder.stopPlayingVideo(); | ||||
| //     } | ||||
| // | ||||
| //     @NonNull | ||||
| //     @Override | ||||
| //     public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, | ||||
| //                                                    final int viewType) { | ||||
| //         final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); | ||||
| //         final ItemFullPostViewBinding binding = ItemFullPostViewBinding | ||||
| //                 .inflate(layoutInflater, parent, false); | ||||
| //         return new PostViewerViewHolder(binding); | ||||
| //     } | ||||
| // | ||||
| //     @Override | ||||
| //     public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) { | ||||
| //         final ViewerPostModelWrapper item = getItem(position); | ||||
| //         holder.bind(item, position, clickListener, longClickListener, mentionClickListener); | ||||
| //     } | ||||
| // | ||||
| //     public interface OnPostViewChildViewClickListener { | ||||
| //         void onClick(View v, | ||||
| //                      ViewerPostModelWrapper viewerPostModelWrapper, | ||||
| //                      int postPosition, | ||||
| //                      int childPosition); | ||||
| //     } | ||||
| // | ||||
| //     public interface OnPostCaptionLongClickListener { | ||||
| //         void onLongClick(String text); | ||||
| //     } | ||||
| // } | ||||
| @ -24,10 +24,14 @@ public final class FeedStoryViewHolder extends RecyclerView.ViewHolder { | ||||
|             if (listener == null) return; | ||||
|             listener.onFeedStoryClick(model, position); | ||||
|         }); | ||||
|         binding.getRoot().setOnLongClickListener(v -> { | ||||
|             if (listener != null) listener.onFeedStoryLongClick(model, position); | ||||
|             return true; | ||||
|         }); | ||||
|         final ProfileModel profileModel = model.getProfileModel(); | ||||
|         binding.title.setText(profileModel.getUsername()); | ||||
|         binding.title.setAlpha(model.getFullyRead() ? 0.5F : 1.0F); | ||||
|         binding.title.setAlpha(model.isFullyRead() ? 0.5F : 1.0F); | ||||
|         binding.icon.setImageURI(profileModel.getSdProfilePic()); | ||||
|         binding.icon.setAlpha(model.getFullyRead() ? 0.5F : 1.0F); | ||||
|         binding.icon.setAlpha(model.isFullyRead() ? 0.5F : 1.0F); | ||||
|     } | ||||
| } | ||||
| @ -1,5 +1,6 @@ | ||||
| package awais.instagrabber.adapters.viewholder; | ||||
| 
 | ||||
| import android.util.Log; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
|  | ||||
| @ -24,10 +24,6 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder { | ||||
|     public void bind(final NotificationModel model, | ||||
|                      final OnNotificationClickListener notificationClickListener) { | ||||
|         if (model == null) return; | ||||
|         itemView.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onNotificationClick(model); | ||||
|         }); | ||||
|         int text = -1; | ||||
|         CharSequence subtext = null; | ||||
|         switch (model.getType()) { | ||||
| @ -52,20 +48,54 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder { | ||||
|                 text = R.string.request_notif; | ||||
|                 subtext = model.getText(); | ||||
|                 break; | ||||
|             case COMMENT_LIKE: | ||||
|             case TAGGED_COMMENT: | ||||
|             case RESPONDED_STORY: | ||||
|                 subtext = model.getText(); | ||||
|                 break; | ||||
|             case AYML: | ||||
|                 subtext = model.getPostId(); | ||||
|                 break; | ||||
|         } | ||||
|         binding.tvUsername.setText(model.getUsername()); | ||||
|         binding.tvComment.setText(text); | ||||
|         binding.tvSubComment.setText(subtext, subtext instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL); | ||||
|         // binding.tvSubComment.setMentionClickListener(mentionClickListener); | ||||
|         if (model.getType() != NotificationType.REQUEST) { | ||||
|         binding.tvSubComment.setText(model.getType() == NotificationType.AYML ? model.getText() : subtext); | ||||
|         if (text == -1 && subtext != null) { | ||||
|             binding.tvComment.setText(subtext); | ||||
|             binding.tvComment.setVisibility(TextUtils.isEmpty(subtext) ? View.GONE : View.VISIBLE); | ||||
|             binding.tvSubComment.setVisibility(model.getType() == NotificationType.AYML ? View.VISIBLE : View.GONE); | ||||
|         } | ||||
|         else if (text != -1) { | ||||
|             binding.tvComment.setText(text); | ||||
|             binding.tvSubComment.setVisibility(subtext == null ? View.GONE : View.VISIBLE); | ||||
|         } | ||||
| 
 | ||||
|         if (model.getType() != NotificationType.REQUEST && model.getType() != NotificationType.AYML) { | ||||
|             binding.tvDate.setText(model.getDateTime()); | ||||
|         } | ||||
| 
 | ||||
|         binding.tvUsername.setText(model.getUsername()); | ||||
|         binding.ivProfilePic.setImageURI(model.getProfilePic()); | ||||
|         if (TextUtils.isEmpty(model.getPreviewPic())) { | ||||
|         binding.ivProfilePic.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onProfileClick(model.getUsername()); | ||||
|         }); | ||||
| 
 | ||||
|         if (model.getType() == NotificationType.AYML) { | ||||
|             binding.ivPreviewPic.setVisibility(View.GONE); | ||||
|         } | ||||
|         else if (TextUtils.isEmpty(model.getPreviewPic())) { | ||||
|             binding.ivPreviewPic.setVisibility(View.INVISIBLE); | ||||
|         } else { | ||||
|             binding.ivPreviewPic.setVisibility(View.VISIBLE); | ||||
|             binding.ivPreviewPic.setImageURI(model.getPreviewPic()); | ||||
|             binding.ivPreviewPic.setOnClickListener(v -> { | ||||
|                 if (notificationClickListener == null) return; | ||||
|                 notificationClickListener.onPreviewClick(model); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         itemView.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onNotificationClick(model); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -1,240 +0,0 @@ | ||||
| // package awais.instagrabber.adapters.viewholder; | ||||
| // | ||||
| // import android.content.res.ColorStateList; | ||||
| // import android.content.res.Resources; | ||||
| // import android.util.Log; | ||||
| // import android.view.View; | ||||
| // import android.widget.TextView; | ||||
| // | ||||
| // import androidx.annotation.NonNull; | ||||
| // import androidx.core.content.ContextCompat; | ||||
| // import androidx.core.view.ViewCompat; | ||||
| // import androidx.recyclerview.widget.RecyclerView; | ||||
| // import androidx.viewpager2.widget.ViewPager2; | ||||
| // | ||||
| // import com.google.android.exoplayer2.Player; | ||||
| // import com.google.android.exoplayer2.SimpleExoPlayer; | ||||
| // import com.google.android.exoplayer2.ui.PlayerView; | ||||
| // | ||||
| // import java.util.ArrayList; | ||||
| // import java.util.List; | ||||
| // | ||||
| // import awais.instagrabber.R; | ||||
| // import awais.instagrabber.adapters.PostViewAdapter.OnPostCaptionLongClickListener; | ||||
| // import awais.instagrabber.adapters.PostViewAdapter.OnPostViewChildViewClickListener; | ||||
| // import awais.instagrabber.adapters.PostViewerChildAdapter; | ||||
| // import awais.instagrabber.databinding.ItemFullPostViewBinding; | ||||
| // import awais.instagrabber.interfaces.MentionClickListener; | ||||
| // import awais.instagrabber.models.PostChild; | ||||
| // import awais.instagrabber.models.ProfileModel; | ||||
| // import awais.instagrabber.models.ViewerPostModel; | ||||
| // import awais.instagrabber.models.ViewerPostModelWrapper; | ||||
| // import awais.instagrabber.models.enums.MediaItemType; | ||||
| // import awais.instagrabber.utils.TextUtils; | ||||
| // import awais.instagrabber.utils.Utils; | ||||
| // | ||||
| // public class PostViewerViewHolder extends RecyclerView.ViewHolder { | ||||
| //     private static final String TAG = "PostViewerViewHolder"; | ||||
| // | ||||
| //     private final ItemFullPostViewBinding binding; | ||||
| //     private int currentChildPosition; | ||||
| // | ||||
| //     public PostViewerViewHolder(@NonNull final ItemFullPostViewBinding binding) { | ||||
| //         super(binding.getRoot()); | ||||
| //         this.binding = binding; | ||||
| //         binding.topPanel.viewStoryPost.setVisibility(View.GONE); | ||||
| //     } | ||||
| // | ||||
| //     public void bind(final ViewerPostModelWrapper wrapper, | ||||
| //                      final int position, | ||||
| //                      final OnPostViewChildViewClickListener clickListener, | ||||
| //                      final OnPostCaptionLongClickListener longClickListener, | ||||
| //                      final MentionClickListener mentionClickListener) { | ||||
| //         if (wrapper == null) return; | ||||
| //         final List<PostChild> items = wrapper.getViewerPostModels(); | ||||
| //         if (items == null || items.size() == 0) return; | ||||
| //         if (items.get(0) == null) return; | ||||
| //         final PostViewerChildAdapter adapter = new PostViewerChildAdapter(); | ||||
| //         binding.mediaViewPager.setAdapter(adapter); | ||||
| //         final PostChild firstPost = items.get(0); | ||||
| //         setPostInfo(firstPost, mentionClickListener); | ||||
| //         setMediaItems(items, adapter); | ||||
| //         setupListeners(wrapper, | ||||
| //                        position, | ||||
| //                        clickListener, | ||||
| //                        longClickListener, | ||||
| //                        mentionClickListener, | ||||
| //                        firstPost.getLocation()); | ||||
| //     } | ||||
| // | ||||
| //     private void setPostInfo(final PostChild firstPost, | ||||
| //                              final MentionClickListener mentionClickListener) { | ||||
| //         final ProfileModel profileModel = firstPost.getProfileModel(); | ||||
| //         if (profileModel == null) return; | ||||
| //         binding.topPanel.title.setText(profileModel.getUsername()); | ||||
| //         final String locationName = firstPost.getLocationName(); | ||||
| //         if (!TextUtils.isEmpty(locationName)) { | ||||
| //             binding.topPanel.location.setVisibility(View.VISIBLE); | ||||
| //             binding.topPanel.location.setText(locationName); | ||||
| //         } else binding.topPanel.location.setVisibility(View.GONE); | ||||
| //         binding.topPanel.ivProfilePic.setImageURI(profileModel.getSdProfilePic()); | ||||
| //         binding.bottomPanel.commentsCount.setText(String.valueOf(firstPost.getCommentsCount())); | ||||
| //         final CharSequence postCaption = firstPost.getPostCaption(); | ||||
| //         if (TextUtils.hasMentions(postCaption)) { | ||||
| //             binding.bottomPanel.viewerCaption.setMentionClickListener(mentionClickListener); | ||||
| //             binding.bottomPanel.viewerCaption | ||||
| //                     .setText(TextUtils.getMentionText(postCaption), TextView.BufferType.SPANNABLE); | ||||
| //         } else { | ||||
| //             binding.bottomPanel.viewerCaption.setMentionClickListener(null); | ||||
| //             binding.bottomPanel.viewerCaption.setText(postCaption); | ||||
| //         } | ||||
| //         binding.bottomPanel.tvPostDate.setText(firstPost.getPostDate()); | ||||
| //         setupLikes(firstPost); | ||||
| //         setupSave(firstPost); | ||||
| //     } | ||||
| // | ||||
| //     private void setupLikes(final ViewerPostModel firstPost) { | ||||
| //         final boolean liked = firstPost.getLike(); | ||||
| //         final long likeCount = firstPost.getLikes(); | ||||
| //         final Resources resources = itemView.getContext().getResources(); | ||||
| //         if (liked) { | ||||
| //             final String unlikeString = resources.getString(R.string.unlike, String.valueOf(likeCount)); | ||||
| //             binding.btnLike.setText(unlikeString); | ||||
| //             ViewCompat.setBackgroundTintList(binding.btnLike, | ||||
| //                                              ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_pink_background))); | ||||
| //         } else { | ||||
| //             final String likeString = resources.getString(R.string.like, String.valueOf(likeCount)); | ||||
| //             binding.btnLike.setText(likeString); | ||||
| //             ViewCompat.setBackgroundTintList(binding.btnLike, | ||||
| //                                              ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightpink_background))); | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     private void setupSave(final ViewerPostModel firstPost) { | ||||
| //         final boolean saved = firstPost.isSaved(); | ||||
| //         if (saved) { | ||||
| //             binding.btnBookmark.setText(R.string.unbookmark); | ||||
| //             ViewCompat.setBackgroundTintList(binding.btnBookmark, | ||||
| //                                              ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_orange_background))); | ||||
| //         } else { | ||||
| //             binding.btnBookmark.setText(R.string.bookmark); | ||||
| //             ViewCompat.setBackgroundTintList( | ||||
| //                     binding.btnBookmark, | ||||
| //                     ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.btn_lightorange_background))); | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     private void setupListeners(final ViewerPostModelWrapper wrapper, | ||||
| //                                 final int position, | ||||
| //                                 final OnPostViewChildViewClickListener clickListener, | ||||
| //                                 final OnPostCaptionLongClickListener longClickListener, | ||||
| //                                 final MentionClickListener mentionClickListener, | ||||
| //                                 final String location) { | ||||
| //         final View.OnClickListener onClickListener = v -> clickListener | ||||
| //                 .onClick(v, wrapper, position, currentChildPosition); | ||||
| //         binding.bottomPanel.btnComments.setOnClickListener(onClickListener); | ||||
| //         binding.topPanel.title.setOnClickListener(onClickListener); | ||||
| //         binding.topPanel.ivProfilePic.setOnClickListener(onClickListener); | ||||
| //         binding.bottomPanel.btnDownload.setOnClickListener(onClickListener); | ||||
| //         binding.bottomPanel.viewerCaption.setOnClickListener(onClickListener); | ||||
| //         binding.btnLike.setOnClickListener(onClickListener); | ||||
| //         binding.btnBookmark.setOnClickListener(onClickListener); | ||||
| //         binding.bottomPanel.viewerCaption.setOnLongClickListener(v -> { | ||||
| //             longClickListener.onLongClick(binding.bottomPanel.viewerCaption.getText().toString()); | ||||
| //             return true; | ||||
| //         }); | ||||
| //         if (!TextUtils.isEmpty(location)) { | ||||
| //             binding.topPanel.location.setOnClickListener(v -> mentionClickListener | ||||
| //                     .onClick(binding.topPanel.location, location, false, true)); | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     private void setMediaItems(final List<ViewerPostModel> items, | ||||
| //                                final PostViewerChildAdapter adapter) { | ||||
| //         final List<ViewerPostModel> filteredList = new ArrayList<>(); | ||||
| //         for (final ViewerPostModel model : items) { | ||||
| //             final MediaItemType itemType = model.getItemType(); | ||||
| //             if (itemType == MediaItemType.MEDIA_TYPE_VIDEO || itemType == MediaItemType.MEDIA_TYPE_IMAGE) { | ||||
| //                 filteredList.add(model); | ||||
| //             } | ||||
| //         } | ||||
| //         binding.mediaCounter.setVisibility(filteredList.size() > 1 ? View.VISIBLE : View.GONE); | ||||
| //         final String counter = "1/" + filteredList.size(); | ||||
| //         binding.mediaCounter.setText(counter); | ||||
| //         adapter.submitList(filteredList); | ||||
| //         binding.mediaViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { | ||||
| //             @Override | ||||
| //             public void onPageSelected(final int position) { | ||||
| //                 if (filteredList.size() <= 0 || position >= filteredList.size()) return; | ||||
| //                 currentChildPosition = position; | ||||
| //                 final String counter = (position + 1) + "/" + filteredList.size(); | ||||
| //                 binding.mediaCounter.setText(counter); | ||||
| //                 final ViewerPostModel viewerPostModel = filteredList.get(position); | ||||
| //                 if (viewerPostModel.getItemType() == MediaItemType.MEDIA_TYPE_VIDEO) { | ||||
| //                     setVideoDetails(viewerPostModel); | ||||
| //                     setVolumeListener(position); | ||||
| //                     return; | ||||
| //                 } | ||||
| //                 setImageDetails(); | ||||
| //             } | ||||
| //         }); | ||||
| //     } | ||||
| // | ||||
| //     private void setVolumeListener(final int position) { | ||||
| //         binding.bottomPanel.btnMute.setOnClickListener(v -> { | ||||
| //             try { | ||||
| //                 final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager | ||||
| //                         .getChildAt(0)).findViewHolderForAdapterPosition(position); | ||||
| //                 if (viewHolder != null) { | ||||
| //                     final View itemView = viewHolder.itemView; | ||||
| //                     if (itemView instanceof PlayerView) { | ||||
| //                         final SimpleExoPlayer player = (SimpleExoPlayer) ((PlayerView) itemView) | ||||
| //                                 .getPlayer(); | ||||
| //                         if (player == null) return; | ||||
| //                         final float vol = player.getVolume() == 0f ? 1f : 0f; | ||||
| //                         player.setVolume(vol); | ||||
| //                         binding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 | ||||
| //                                                                                : R.drawable.ic_volume_off_24); | ||||
| //                         Utils.sessionVolumeFull = vol == 1f; | ||||
| //                     } | ||||
| //                 } | ||||
| //             } catch (Exception e) { | ||||
| //                 Log.e(TAG, "Error", e); | ||||
| //             } | ||||
| //         }); | ||||
| //     } | ||||
| // | ||||
| //     private void setImageDetails() { | ||||
| //         binding.bottomPanel.btnMute.setVisibility(View.GONE); | ||||
| //         binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE); | ||||
| //     } | ||||
| // | ||||
| //     private void setVideoDetails(final ViewerPostModel viewerPostModel) { | ||||
| //         binding.bottomPanel.btnMute.setVisibility(View.VISIBLE); | ||||
| //         final long videoViews = viewerPostModel.getVideoViews(); | ||||
| //         if (videoViews < 0) { | ||||
| //             binding.bottomPanel.videoViewsContainer.setVisibility(View.GONE); | ||||
| //             return; | ||||
| //         } | ||||
| //         binding.bottomPanel.tvVideoViews.setText(String.valueOf(videoViews)); | ||||
| //         binding.bottomPanel.videoViewsContainer.setVisibility(View.VISIBLE); | ||||
| //     } | ||||
| // | ||||
| //     public void stopPlayingVideo() { | ||||
| //         try { | ||||
| //             final RecyclerView.ViewHolder viewHolder = ((RecyclerView) binding.mediaViewPager | ||||
| //                     .getChildAt(0)).findViewHolderForAdapterPosition(currentChildPosition); | ||||
| //             if (viewHolder != null) { | ||||
| //                 final View itemView = viewHolder.itemView; | ||||
| //                 if (itemView instanceof PlayerView) { | ||||
| //                     final Player player = ((PlayerView) itemView).getPlayer(); | ||||
| //                     if (player != null) { | ||||
| //                         player.setPlayWhenReady(false); | ||||
| //                     } | ||||
| //                 } | ||||
| //             } | ||||
| //         } catch (Exception e) { | ||||
| //             Log.e(TAG, "Error", e); | ||||
| //         } | ||||
| //     } | ||||
| // } | ||||
| @ -0,0 +1,85 @@ | ||||
| package awais.instagrabber.adapters.viewholder; | ||||
| 
 | ||||
| import android.text.TextUtils; | ||||
| import android.view.View; | ||||
| 
 | ||||
| import androidx.recyclerview.widget.RecyclerView; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener; | ||||
| import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener; | ||||
| import awais.instagrabber.databinding.ItemNotificationBinding; | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
| import awais.instagrabber.models.HighlightModel; | ||||
| 
 | ||||
| public final class StoryListViewHolder extends RecyclerView.ViewHolder { | ||||
|     private final ItemNotificationBinding binding; | ||||
| 
 | ||||
|     public StoryListViewHolder(final ItemNotificationBinding binding) { | ||||
|         super(binding.getRoot()); | ||||
|         this.binding = binding; | ||||
|     } | ||||
| 
 | ||||
|     public void bind(final FeedStoryModel model, | ||||
|                      final int position, | ||||
|                      final OnFeedStoryClickListener notificationClickListener) { | ||||
|         if (model == null) return; | ||||
| 
 | ||||
|         final int storiesCount = model.getMediaCount(); | ||||
|         binding.tvComment.setVisibility(View.VISIBLE); | ||||
|         binding.tvComment.setText(itemView.getResources().getQuantityString(R.plurals.stories_count, storiesCount, storiesCount)); | ||||
| 
 | ||||
|         binding.tvSubComment.setVisibility(View.GONE); | ||||
| 
 | ||||
|         binding.tvDate.setText(model.getDateTime()); | ||||
| 
 | ||||
|         binding.tvUsername.setText(model.getProfileModel().getUsername()); | ||||
|         binding.ivProfilePic.setImageURI(model.getProfileModel().getSdProfilePic()); | ||||
|         binding.ivProfilePic.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onProfileClick(model.getProfileModel().getUsername()); | ||||
|         }); | ||||
| 
 | ||||
|         if (model.getFirstStoryModel() != null) { | ||||
|             binding.ivPreviewPic.setVisibility(View.VISIBLE); | ||||
|             binding.ivPreviewPic.setImageURI(model.getFirstStoryModel().getThumbnail()); | ||||
|         } | ||||
|         else binding.ivPreviewPic.setVisibility(View.INVISIBLE); | ||||
| 
 | ||||
|         float alpha = model.isFullyRead() ? 0.5F : 1.0F; | ||||
|         binding.ivProfilePic.setAlpha(alpha); | ||||
|         binding.ivPreviewPic.setAlpha(alpha); | ||||
|         binding.tvUsername.setAlpha(alpha); | ||||
|         binding.tvComment.setAlpha(alpha); | ||||
|         binding.tvDate.setAlpha(alpha); | ||||
| 
 | ||||
|         itemView.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onFeedStoryClick(model, position); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void bind(final HighlightModel model, | ||||
|                      final int position, | ||||
|                      final OnHighlightStoryClickListener notificationClickListener) { | ||||
|         if (model == null) return; | ||||
| 
 | ||||
|         final int storiesCount = model.getMediaCount(); | ||||
|         binding.tvComment.setVisibility(View.VISIBLE); | ||||
|         binding.tvComment.setText(itemView.getResources().getQuantityString(R.plurals.stories_count, storiesCount, storiesCount)); | ||||
| 
 | ||||
|         binding.tvSubComment.setVisibility(View.GONE); | ||||
| 
 | ||||
|         binding.tvUsername.setText(model.getDateTime()); | ||||
| 
 | ||||
|         binding.ivProfilePic.setVisibility(View.GONE); | ||||
| 
 | ||||
|         binding.ivPreviewPic.setVisibility(View.VISIBLE); | ||||
|         binding.ivPreviewPic.setImageURI(model.getThumbnailUrl()); | ||||
| 
 | ||||
|         itemView.setOnClickListener(v -> { | ||||
|             if (notificationClickListener == null) return; | ||||
|             notificationClickListener.onHighlightClick(model, position); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -89,8 +89,7 @@ public final class ChildCommentViewHolder extends RecyclerView.ViewHolder { | ||||
| 
 | ||||
|     public final void setLiked(final boolean liked) { | ||||
|         if (liked) { | ||||
|             // container.setBackgroundColor(0x40FF69B4); | ||||
|             return; | ||||
|             itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -89,8 +89,7 @@ public final class ParentCommentViewHolder extends RecyclerView.ViewHolder { | ||||
| 
 | ||||
|     public final void setLiked(final boolean liked) { | ||||
|         if (liked) { | ||||
|             // container.setBackgroundColor(0x40FF69B4); | ||||
|             return; | ||||
|             itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -28,11 +28,12 @@ import static awais.instagrabber.utils.Utils.logCollector; | ||||
| public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> { | ||||
|     private static final String TAG = "CommentsFetcher"; | ||||
| 
 | ||||
|     private final String shortCode; | ||||
|     private final String shortCode, endCursor; | ||||
|     private final FetchListener<List<CommentModel>> fetchListener; | ||||
| 
 | ||||
|     public CommentsFetcher(final String shortCode, final FetchListener<List<CommentModel>> fetchListener) { | ||||
|     public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener<List<CommentModel>> fetchListener) { | ||||
|         this.shortCode = shortCode; | ||||
|         this.endCursor = endCursor; | ||||
|         this.fetchListener = fetchListener; | ||||
|     } | ||||
| 
 | ||||
| @ -48,15 +49,17 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|         https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""} | ||||
|          */ | ||||
|         final List<CommentModel> commentModels = getParentComments(); | ||||
|         for (final CommentModel commentModel : commentModels) { | ||||
|             final List<CommentModel> childCommentModels = commentModel.getChildCommentModels(); | ||||
|             if (childCommentModels != null) { | ||||
|                 final int childCommentsLen = childCommentModels.size(); | ||||
|                 final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1); | ||||
|                 if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) { | ||||
|                     final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId()); | ||||
|                     commentModel.setChildCommentModels(remoteChildComments); | ||||
|                     lastChild.setPageCursor(false, null); | ||||
|         if (commentModels != null) { | ||||
|             for (final CommentModel commentModel : commentModels) { | ||||
|                 final List<CommentModel> childCommentModels = commentModel.getChildCommentModels(); | ||||
|                 if (childCommentModels != null) { | ||||
|                     final int childCommentsLen = childCommentModels.size(); | ||||
|                     final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1); | ||||
|                     if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) { | ||||
|                         final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId()); | ||||
|                         commentModel.setChildCommentModels(remoteChildComments); | ||||
|                         lastChild.setPageCursor(false, null); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -76,11 +79,10 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|     @NonNull | ||||
|     private synchronized List<CommentModel> getChildComments(final String commentId) { | ||||
|         final List<CommentModel> commentModels = new ArrayList<>(); | ||||
|         String endCursor = ""; | ||||
|         while (endCursor != null) { | ||||
|         String childEndCursor = ""; | ||||
|         while (childEndCursor != null) { | ||||
|             final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" + | ||||
|                     "{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; | ||||
| 
 | ||||
|                     "{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}"; | ||||
|             try { | ||||
|                 final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||
|                 conn.setUseCaches(false); | ||||
| @ -93,8 +95,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                                                  .getJSONObject("edge_threaded_comments"); | ||||
| 
 | ||||
|                     final JSONObject pageInfo = data.getJSONObject("page_info"); | ||||
|                     endCursor = pageInfo.getString("end_cursor"); | ||||
|                     if (TextUtils.isEmpty(endCursor)) endCursor = null; | ||||
|                     childEndCursor = pageInfo.getString("end_cursor"); | ||||
|                     if (TextUtils.isEmpty(childEndCursor)) childEndCursor = null; | ||||
| 
 | ||||
|                     final JSONArray childComments = data.optJSONArray("edges"); | ||||
|                     if (childComments != null) { | ||||
| @ -120,6 +122,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                                    false, | ||||
|                                                                                    false, | ||||
|                                                                                    false, | ||||
|                                                                                    false, | ||||
|                                                                                    false); | ||||
| 
 | ||||
|                                 final JSONObject likedBy = childComment.optJSONObject("edge_liked_by"); | ||||
| @ -142,6 +145,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                  "getChildComments", | ||||
|                                                  new Pair<>("commentModels.size", commentModels.size())); | ||||
|                 if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||
|                 if (fetchListener != null) fetchListener.onFailure(e); | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| @ -152,17 +156,14 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|     @NonNull | ||||
|     private synchronized List<CommentModel> getParentComments() { | ||||
|         final List<CommentModel> commentModels = new ArrayList<>(); | ||||
|         String endCursor = ""; | ||||
|         while (endCursor != null) { | ||||
|             final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" + | ||||
|                     "{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; | ||||
| 
 | ||||
|             try { | ||||
|                     "{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}"; | ||||
|         try { | ||||
|                 final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||
|                 conn.setUseCaches(false); | ||||
|                 conn.connect(); | ||||
| 
 | ||||
|                 if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break; | ||||
|                 if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null; | ||||
|                 else { | ||||
|                     final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") | ||||
|                                                                                                            .getJSONObject("shortcode_media") | ||||
| @ -170,8 +171,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                                                                    "edge_media_to_parent_comment"); | ||||
| 
 | ||||
|                     final JSONObject pageInfo = parentComments.getJSONObject("page_info"); | ||||
|                     endCursor = pageInfo.optString("end_cursor"); | ||||
|                     if (TextUtils.isEmpty(endCursor)) endCursor = null; | ||||
|                     final String foundEndCursor = pageInfo.optString("end_cursor"); | ||||
|                     final boolean hasNextPage = pageInfo.optBoolean("has_next_page", !TextUtils.isEmpty(foundEndCursor)); | ||||
| 
 | ||||
|                     // final boolean containsToken = endCursor.contains("bifilter_token"); | ||||
|                     // if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) { | ||||
| @ -209,6 +210,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                            false, | ||||
|                                                                            false, | ||||
|                                                                            false, | ||||
|                                                                            false, | ||||
|                                                                            false); | ||||
| 
 | ||||
|                         final JSONObject likedBy = comment.optJSONObject("edge_liked_by"); | ||||
| @ -219,6 +221,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                            likedBy != null ? likedBy.optLong("count", 0) : 0, | ||||
|                                                                            comment.getBoolean("viewer_has_liked"), | ||||
|                                                                            profileModel); | ||||
|                         if (i == 0 && !foundEndCursor.contains("tao_cursor")) | ||||
|                             commentModel.setPageCursor(hasNextPage, TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor); | ||||
|                         JSONObject tempJsonObject; | ||||
|                         final JSONArray childCommentsArray; | ||||
|                         final int childCommentsLen; | ||||
| @ -227,13 +231,13 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                 && (childCommentsLen = childCommentsArray.length()) > 0) { | ||||
| 
 | ||||
|                             final String childEndCursor; | ||||
|                             final boolean hasNextPage; | ||||
|                             final boolean childHasNextPage; | ||||
|                             if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) { | ||||
|                                 childEndCursor = tempJsonObject.optString("end_cursor"); | ||||
|                                 hasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor)); | ||||
|                                 childHasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor)); | ||||
|                             } else { | ||||
|                                 childEndCursor = null; | ||||
|                                 hasNextPage = false; | ||||
|                                 childHasNextPage = false; | ||||
|                             } | ||||
| 
 | ||||
|                             final List<CommentModel> childCommentModels = new ArrayList<>(); | ||||
| @ -257,6 +261,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                                         false, | ||||
|                                                                                         false, | ||||
|                                                                                         false, | ||||
|                                                                                         false, | ||||
|                                                                                         false); | ||||
| 
 | ||||
|                                 tempJsonObject = childComment.optJSONObject("edge_liked_by"); | ||||
| @ -267,7 +272,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                                                                         childComment.getBoolean("viewer_has_liked"), | ||||
|                                                                         childProfileModel)); | ||||
|                             } | ||||
|                             childCommentModels.get(childCommentsLen - 1).setPageCursor(hasNextPage, childEndCursor); | ||||
|                             childCommentModels.get(childCommentsLen - 1).setPageCursor(childHasNextPage, childEndCursor); | ||||
|                             commentModel.setChildCommentModels(childCommentModels); | ||||
|                         } | ||||
|                         commentModels.add(commentModel); | ||||
| @ -280,9 +285,9 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | ||||
|                     logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments", | ||||
|                                                  new Pair<>("commentModelsList.size", commentModels.size())); | ||||
|                 if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); | ||||
|                 break; | ||||
|                 if (fetchListener != null) fetchListener.onFailure(e); | ||||
|                 return null; | ||||
|             } | ||||
|         } | ||||
|         return commentModels; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,34 +1,61 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| //import android.os.Handler; | ||||
| //import android.util.Log; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.customviews.helpers.PostFetcher; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.webservices.FeedService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class FeedPostFetchService implements PostFetcher.PostFetchService { | ||||
|     private static final String TAG = "FeedPostFetchService"; | ||||
|     private final FeedService feedService; | ||||
|     private String nextCursor; | ||||
| //    private final Handler handler; | ||||
|     private boolean hasNextPage; | ||||
| //    private static final int DELAY_MILLIS = 500; | ||||
| 
 | ||||
|     public FeedPostFetchService() { | ||||
|         feedService = FeedService.getInstance(); | ||||
| //        handler = new Handler(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||
|         feedService.fetch(25, nextCursor, new ServiceCallback<PostsFetchResponse>() { | ||||
|         final List<FeedModel> feedModels = new ArrayList<>(); | ||||
|         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); | ||||
|         feedModels.clear(); | ||||
|         feedService.fetch(csrfToken, nextCursor, new ServiceCallback<PostsFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final PostsFetchResponse result) { | ||||
|                 if (result == null) return; | ||||
|                 if (result == null && feedModels.size() > 0) { | ||||
|                     fetchListener.onResult(feedModels); | ||||
|                     return; | ||||
|                 } | ||||
|                 else if (result == null) return; | ||||
|                 nextCursor = result.getNextCursor(); | ||||
|                 hasNextPage = result.hasNextPage(); | ||||
|                 feedModels.addAll(result.getFeedModels()); | ||||
|                 if (fetchListener != null) { | ||||
|                     fetchListener.onResult(result.getFeedModels()); | ||||
|                     if (feedModels.size() < 15 && hasNextPage) { | ||||
| //                        handler.postDelayed(() -> { | ||||
|                             feedService.fetch(csrfToken, nextCursor, this); | ||||
| //                        }, DELAY_MILLIS); | ||||
|                     } | ||||
|                     else { | ||||
|                         fetchListener.onResult(feedModels); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -1,93 +0,0 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.NetworkUtils; | ||||
| import awaisomereport.LogCollector.LogFile; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.logCollector; | ||||
| 
 | ||||
| public final class FeedStoriesFetcher extends AsyncTask<Void, Void, FeedStoryModel[]> { | ||||
|     private final FetchListener<FeedStoryModel[]> fetchListener; | ||||
| 
 | ||||
|     public FeedStoriesFetcher(final FetchListener<FeedStoryModel[]> fetchListener) { | ||||
|         this.fetchListener = fetchListener; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected FeedStoryModel[] doInBackground(final Void... voids) { | ||||
|         FeedStoryModel[] result = null; | ||||
|         String url = "https://www.instagram.com/graphql/query/?query_hash=b7b84d884400bc5aa7cfe12ae843a091&variables=" + | ||||
|                 "{\"only_stories\":true,\"stories_prefetch\":false,\"stories_video_dash_manifest\":false}"; | ||||
| 
 | ||||
|         try { | ||||
|             HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||
|             conn.setInstanceFollowRedirects(false); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.connect(); | ||||
| 
 | ||||
|             if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { | ||||
|                 final JSONArray feedStoriesReel = new JSONObject(NetworkUtils.readFromConnection(conn)) | ||||
|                         .getJSONObject("data") | ||||
|                         .getJSONObject(Constants.EXTRAS_USER) | ||||
|                         .getJSONObject("feed_reels_tray") | ||||
|                         .getJSONObject("edge_reels_tray_to_reel") | ||||
|                         .getJSONArray("edges"); | ||||
| 
 | ||||
|                 conn.disconnect(); | ||||
| 
 | ||||
|                 final int storiesLen = feedStoriesReel.length(); | ||||
|                 final FeedStoryModel[] feedStoryModels = new FeedStoryModel[storiesLen]; | ||||
|                 final String[] feedStoryIDs = new String[storiesLen]; | ||||
| 
 | ||||
|                 for (int i = 0; i < storiesLen; ++i) { | ||||
|                     final JSONObject node = feedStoriesReel.getJSONObject(i).getJSONObject("node"); | ||||
| 
 | ||||
|                     final JSONObject user = node.getJSONObject(node.has("user") ? "user" : "owner"); | ||||
|                     final ProfileModel profileModel = new ProfileModel(false, false, false, | ||||
|                             user.getString("id"), | ||||
|                             user.getString("username"), | ||||
|                             null, null, null, | ||||
|                             user.getString("profile_pic_url"), | ||||
|                             null, 0, 0, 0, false, false, false, false); | ||||
| 
 | ||||
|                     final String id = node.getString("id"); | ||||
|                     final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == node.getLong("latest_reel_media"); | ||||
|                     feedStoryIDs[i] = id; | ||||
|                     feedStoryModels[i] = new FeedStoryModel(id, profileModel, fullyRead); | ||||
|                 } | ||||
|                 result = feedStoryModels; | ||||
|             } | ||||
| 
 | ||||
|             conn.disconnect(); | ||||
|         } catch (final Exception e) { | ||||
|             if (logCollector != null) | ||||
|                 logCollector.appendException(e, LogFile.ASYNC_FEED_STORY_FETCHER, "doInBackground"); | ||||
|             if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPreExecute() { | ||||
|         if (fetchListener != null) fetchListener.doBefore(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final FeedStoryModel[] result) { | ||||
|         if (fetchListener != null) fetchListener.onResult(result); | ||||
|     } | ||||
| } | ||||
| @ -16,12 +16,21 @@ import awais.instagrabber.utils.TextUtils; | ||||
| public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsyncTask.NotificationCounts> { | ||||
|     private static final String TAG = "GetActivityAsyncTask"; | ||||
| 
 | ||||
|     private OnTaskCompleteListener onTaskCompleteListener; | ||||
|     private final OnTaskCompleteListener onTaskCompleteListener; | ||||
| 
 | ||||
|     public GetActivityAsyncTask(final OnTaskCompleteListener onTaskCompleteListener) { | ||||
|         this.onTaskCompleteListener = onTaskCompleteListener; | ||||
|     } | ||||
| 
 | ||||
|     /* | ||||
|     This needs to be redone to fetch i inbox instead | ||||
|     Within inbox, data is (body JSON => counts) | ||||
|     Then we have these counts: | ||||
|     new_posts, activity_feed_dot_badge, relationships, campaign_notification | ||||
|     usertags, likes, comment_likes, shopping_notification, comments | ||||
|     photos_of_you (not sure about difference to usertags), requests | ||||
|      */ | ||||
| 
 | ||||
|     protected NotificationCounts doInBackground(final String... cookiesArray) { | ||||
|         if (cookiesArray == null) return null; | ||||
|         final String cookie = cookiesArray[0]; | ||||
| @ -70,11 +79,11 @@ public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsy | ||||
|     } | ||||
| 
 | ||||
|     public static class NotificationCounts { | ||||
|         private int relationshipsCount; | ||||
|         private int userTagsCount; | ||||
|         private int commentsCount; | ||||
|         private int commentLikesCount; | ||||
|         private int likesCount; | ||||
|         private final int relationshipsCount; | ||||
|         private final int userTagsCount; | ||||
|         private final int commentsCount; | ||||
|         private final int commentLikesCount; | ||||
|         private final int likesCount; | ||||
| 
 | ||||
|         public NotificationCounts(final int relationshipsCount, | ||||
|                                   final int userTagsCount, | ||||
|  | ||||
| @ -89,6 +89,7 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> { | ||||
|             if (logCollector != null) | ||||
|                 logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); | ||||
|             if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||
|             if (fetchListener != null) fetchListener.onFailure(e); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|  | ||||
| @ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.HashtagModel; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.webservices.GraphQLService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awais.instagrabber.webservices.TagsService; | ||||
| import awais.instagrabber.webservices.TagsService.TagPostsFetchResponse; | ||||
| 
 | ||||
| public class HashtagPostFetchService implements PostFetcher.PostFetchService { | ||||
|     private final TagsService tagsService; | ||||
|     private final GraphQLService graphQLService; | ||||
|     private final HashtagModel hashtagModel; | ||||
|     private String nextMaxId; | ||||
|     private boolean moreAvailable; | ||||
| @ -20,19 +22,20 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService { | ||||
|     public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) { | ||||
|         this.hashtagModel = hashtagModel; | ||||
|         this.isLoggedIn = isLoggedIn; | ||||
|         tagsService = TagsService.getInstance(); | ||||
|         tagsService = isLoggedIn ? TagsService.getInstance() : null; | ||||
|         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||
|         final ServiceCallback cb = new ServiceCallback<TagPostsFetchResponse>() { | ||||
|         final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final TagPostsFetchResponse result) { | ||||
|             public void onSuccess(final PostsFetchResponse result) { | ||||
|                 if (result == null) return; | ||||
|                 nextMaxId = result.getNextMaxId(); | ||||
|                 moreAvailable = result.isMoreAvailable(); | ||||
|                 nextMaxId = result.getNextCursor(); | ||||
|                 moreAvailable = result.hasNextPage(); | ||||
|                 if (fetchListener != null) { | ||||
|                     fetchListener.onResult(result.getItems()); | ||||
|                     fetchListener.onResult(result.getFeedModels()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| @ -45,7 +48,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService { | ||||
|             } | ||||
|         }; | ||||
|         if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb); | ||||
|         else tagsService.fetchGraphQLPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb); | ||||
|         else graphQLService.fetchHashtagPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | ||||
| @ -1,73 +0,0 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.HighlightModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.NetworkUtils; | ||||
| 
 | ||||
| public final class HighlightsFetcher extends AsyncTask<Void, Void, List<HighlightModel>> { | ||||
|     private final String id; | ||||
|     private final FetchListener<List<HighlightModel>> fetchListener; | ||||
| 
 | ||||
|     public HighlightsFetcher(final String id, final FetchListener<List<HighlightModel>> fetchListener) { | ||||
|         this.id = id; | ||||
|         this.fetchListener = fetchListener; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected List<HighlightModel> doInBackground(final Void... voids) { | ||||
|         List<HighlightModel> result = null; | ||||
|         String url = "https://i.instagram.com/api/v1/highlights/" + id + "/highlights_tray/"; | ||||
| 
 | ||||
|         try { | ||||
|             HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||
|             conn.setInstanceFollowRedirects(false); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT); | ||||
|             conn.connect(); | ||||
| 
 | ||||
|             if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { | ||||
|                 final JSONArray highlightsReel = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONArray("tray"); | ||||
| 
 | ||||
|                 final int length = highlightsReel.length(); | ||||
|                 final List<HighlightModel> highlightModels = new ArrayList<>(); | ||||
|                 // final String[] highlightIds = new String[length]; | ||||
|                 for (int i = 0; i < length; ++i) { | ||||
|                     final JSONObject highlightNode = highlightsReel.getJSONObject(i); | ||||
|                     highlightModels.add(new HighlightModel( | ||||
|                             highlightNode.getString("title"), | ||||
|                             highlightNode.getString(Constants.EXTRAS_ID), | ||||
|                             highlightNode.getJSONObject("cover_media") | ||||
|                                          .getJSONObject("cropped_image_version") | ||||
|                                          .getString("url") | ||||
|                     )); | ||||
|                 } | ||||
|                 conn.disconnect(); | ||||
|                 result = highlightModels; | ||||
|             } | ||||
| 
 | ||||
|             conn.disconnect(); | ||||
|         } catch (Exception e) { | ||||
|             if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final List<HighlightModel> result) { | ||||
|         if (fetchListener != null) fetchListener.onResult(result); | ||||
|     } | ||||
| } | ||||
| @ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.LocationModel; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.webservices.GraphQLService; | ||||
| import awais.instagrabber.webservices.LocationService; | ||||
| import awais.instagrabber.webservices.LocationService.LocationPostsFetchResponse; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| public class LocationPostFetchService implements PostFetcher.PostFetchService { | ||||
|     private final LocationService locationService; | ||||
|     private final GraphQLService graphQLService; | ||||
|     private final LocationModel locationModel; | ||||
|     private String nextMaxId; | ||||
|     private boolean moreAvailable; | ||||
| @ -20,19 +22,20 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService { | ||||
|     public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) { | ||||
|         this.locationModel = locationModel; | ||||
|         this.isLoggedIn = isLoggedIn; | ||||
|         locationService = LocationService.getInstance(); | ||||
|         locationService = isLoggedIn ? LocationService.getInstance() : null; | ||||
|         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||
|         final ServiceCallback cb = new ServiceCallback<LocationPostsFetchResponse>() { | ||||
|         final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final LocationPostsFetchResponse result) { | ||||
|             public void onSuccess(final PostsFetchResponse result) { | ||||
|                 if (result == null) return; | ||||
|                 nextMaxId = result.getNextMaxId(); | ||||
|                 moreAvailable = result.isMoreAvailable(); | ||||
|                 nextMaxId = result.getNextCursor(); | ||||
|                 moreAvailable = result.hasNextPage(); | ||||
|                 if (fetchListener != null) { | ||||
|                     fetchListener.onResult(result.getItems()); | ||||
|                     fetchListener.onResult(result.getFeedModels()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| @ -45,7 +48,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService { | ||||
|             } | ||||
|         }; | ||||
|         if (isLoggedIn) locationService.fetchPosts(locationModel.getId(), nextMaxId, cb); | ||||
|         else locationService.fetchGraphQLPosts(locationModel.getId(), nextMaxId, cb); | ||||
|         else graphQLService.fetchLocationPosts(locationModel.getId(), nextMaxId, cb); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|  | ||||
| @ -3,21 +3,14 @@ package awais.instagrabber.asyncs; | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.NotificationModel; | ||||
| import awais.instagrabber.models.enums.NotificationType; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.LocaleUtils; | ||||
| import awais.instagrabber.utils.NetworkUtils; | ||||
| import awais.instagrabber.webservices.NewsService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awaisomereport.LogCollector; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.logCollector; | ||||
| @ -26,89 +19,48 @@ public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notif | ||||
|     private static final String TAG = "NotificationsFetcher"; | ||||
| 
 | ||||
|     private final FetchListener<List<NotificationModel>> fetchListener; | ||||
|     private final NewsService newsService; | ||||
|     private final boolean markAsSeen; | ||||
|     private boolean fetchedWeb = false; | ||||
| 
 | ||||
|     public NotificationsFetcher(final FetchListener<List<NotificationModel>> fetchListener) { | ||||
|     public NotificationsFetcher(final boolean markAsSeen, | ||||
|                                 final FetchListener<List<NotificationModel>> fetchListener) { | ||||
|         this.markAsSeen = markAsSeen; | ||||
|         this.fetchListener = fetchListener; | ||||
|         newsService = NewsService.getInstance(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected List<NotificationModel> doInBackground(final Void... voids) { | ||||
|         List<NotificationModel> result = new ArrayList<>(); | ||||
|         final String url = "https://www.instagram.com/accounts/activity/?__a=1"; | ||||
|         try { | ||||
|             final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||
|             conn.setInstanceFollowRedirects(false); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8"); | ||||
|             conn.connect(); | ||||
|         List<NotificationModel> notificationModels = new ArrayList<>(); | ||||
| 
 | ||||
|             if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { | ||||
|                 final JSONObject page = new JSONObject(NetworkUtils.readFromConnection(conn)) | ||||
|                         .getJSONObject("graphql") | ||||
|                         .getJSONObject("user"); | ||||
|                 final JSONObject ewaf = page.getJSONObject("activity_feed") | ||||
|                                             .optJSONObject("edge_web_activity_feed"); | ||||
|                 final JSONObject efr = page.optJSONObject("edge_follow_requests"); | ||||
|                 JSONObject data; | ||||
|                 JSONArray media; | ||||
|                 if (ewaf != null | ||||
|                         && (media = ewaf.optJSONArray("edges")) != null | ||||
|                         && media.length() > 0 | ||||
|                         && media.optJSONObject(0).optJSONObject("node") != null) { | ||||
|                     for (int i = 0; i < media.length(); ++i) { | ||||
|                         data = media.optJSONObject(i).optJSONObject("node"); | ||||
|                         if (data == null) continue; | ||||
|                         final String type = data.getString("__typename"); | ||||
|                         final NotificationType notificationType = NotificationType.valueOfType(type); | ||||
|                         if (notificationType == null) continue; | ||||
|                         final JSONObject user = data.getJSONObject("user"); | ||||
|                         result.add(new NotificationModel( | ||||
|                                 data.getString(Constants.EXTRAS_ID), | ||||
|                                 data.optString("text"), // comments or mentions | ||||
|                                 data.getLong("timestamp"), | ||||
|                                 user.getString("id"), | ||||
|                                 user.getString("username"), | ||||
|                                 user.getString("profile_pic_url"), | ||||
|                                 !data.isNull("media") ? data.getJSONObject("media").getString("shortcode") : null, | ||||
|                                 !data.isNull("media") ? data.getJSONObject("media").getString("thumbnail_src") : null, notificationType)); | ||||
|                     } | ||||
|         newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<NotificationModel>>() { | ||||
|             @Override | ||||
|             public void onSuccess(final List<NotificationModel> result) { | ||||
|                 if (result == null) return; | ||||
|                 notificationModels.addAll(result); | ||||
|                 if (fetchedWeb) { | ||||
|                     fetchListener.onResult(notificationModels); | ||||
|                 } | ||||
| 
 | ||||
|                 if (efr != null | ||||
|                         && (media = efr.optJSONArray("edges")) != null | ||||
|                         && media.length() > 0 | ||||
|                         && media.optJSONObject(0).optJSONObject("node") != null) { | ||||
|                     for (int i = 0; i < media.length(); ++i) { | ||||
|                         data = media.optJSONObject(i).optJSONObject("node"); | ||||
|                         if (data == null) continue; | ||||
|                         result.add(new NotificationModel( | ||||
|                                 data.getString(Constants.EXTRAS_ID), | ||||
|                                 data.optString("full_name"), | ||||
|                                 0L, | ||||
|                                 data.getString(Constants.EXTRAS_ID), | ||||
|                                 data.getString("username"), | ||||
|                                 data.getString("profile_pic_url"), | ||||
|                                 null, | ||||
|                                 null, NotificationType.REQUEST)); | ||||
|                     } | ||||
|                 else { | ||||
|                     fetchedWeb = true; | ||||
|                     newsService.fetchWebInbox(markAsSeen, this); | ||||
|                 } | ||||
|             } | ||||
|             conn.disconnect(); | ||||
|         } catch (final Exception e) { | ||||
|             if (logCollector != null) | ||||
|                 logCollector.appendException(e, LogCollector.LogFile.ASYNC_NOTIFICATION_FETCHER, "doInBackground"); | ||||
|             if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||
|         } | ||||
|         return result; | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(final Throwable t) { | ||||
|                 // Log.e(TAG, "onFailure: ", t); | ||||
|                 if (fetchListener != null) { | ||||
|                     fetchListener.onFailure(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         return notificationModels; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPreExecute() { | ||||
|         if (fetchListener != null) fetchListener.doBefore(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final List<NotificationModel> result) { | ||||
|         if (fetchListener != null) fetchListener.onResult(result); | ||||
|     } | ||||
| } | ||||
| @ -65,6 +65,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> { | ||||
|                             owner.optInt("edge_followed_by"), | ||||
|                             -1, | ||||
|                             owner.optBoolean("followed_by_viewer"), | ||||
|                             false, | ||||
|                             owner.optBoolean("restricted_by_viewer"), | ||||
|                             owner.optBoolean("blocked_by_viewer"), | ||||
|                             owner.optBoolean("requested_by_viewer") | ||||
|  | ||||
| @ -72,6 +72,7 @@ public final class ProfileFetcher extends AsyncTask<Void, Void, ProfileModel> { | ||||
|                         user.getJSONObject("edge_followed_by").getLong("count"), | ||||
|                         user.getJSONObject("edge_follow").getLong("count"), | ||||
|                         user.optBoolean("followed_by_viewer"), | ||||
|                         user.optBoolean("follows_viewer"), | ||||
|                         user.optBoolean("restricted_by_viewer"), | ||||
|                         user.optBoolean("blocked_by_viewer"), | ||||
|                         user.optBoolean("requested_by_viewer")); | ||||
|  | ||||
| @ -40,51 +40,34 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> { | ||||
|     protected String doInBackground(final Void... voids) { | ||||
|         String out = null; | ||||
|         if (isHashtag) out = picUrl; | ||||
|         else try { | ||||
|             final HttpURLConnection conn = | ||||
|                     (HttpURLConnection) new URL("https://i.instagram.com/api/v1/users/"+userId+"/info/").openConnection(); | ||||
|             conn.setUseCaches(false); | ||||
|             conn.setRequestMethod("GET"); | ||||
|             conn.setRequestProperty("User-Agent", Constants.USER_AGENT); | ||||
|         else if (Utils.settingsHelper.getBoolean(Constants.INSTADP)) try { | ||||
|             final HttpURLConnection backup = | ||||
|                     (HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection(); | ||||
|             backup.setUseCaches(false); | ||||
|             backup.setRequestMethod("GET"); | ||||
|             backup.setRequestProperty("User-Agent", Constants.A_USER_AGENT); | ||||
| 
 | ||||
|             final String result = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(conn) : null; | ||||
|             conn.disconnect(); | ||||
|             final String instadp = backup.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(backup) : null; | ||||
|             backup.disconnect(); | ||||
| 
 | ||||
|             if (!TextUtils.isEmpty(result)) { | ||||
|                 JSONObject data = new JSONObject(result).getJSONObject("user"); | ||||
|                 if (data.has("hd_profile_pic_url_info")) | ||||
|                     out = data.getJSONObject("hd_profile_pic_url_info").optString("url"); | ||||
|             } | ||||
|             if (!TextUtils.isEmpty(instadp)) { | ||||
|                 final Document doc = Jsoup.parse(instadp); | ||||
|                 boolean fallback = false; | ||||
| 
 | ||||
|             if (TextUtils.isEmpty(out) && Utils.settingsHelper.getBoolean(Constants.INSTADP)) { | ||||
|                 final HttpURLConnection backup = | ||||
|                         (HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection(); | ||||
|                 backup.setUseCaches(false); | ||||
|                 backup.setRequestMethod("GET"); | ||||
|                 backup.setRequestProperty("User-Agent", Constants.A_USER_AGENT); | ||||
|                 final int imgIndex = instadp.indexOf("preloadImg('"), lastIndex; | ||||
| 
 | ||||
|                 final String instadp = backup.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(backup) : null; | ||||
|                 backup.disconnect(); | ||||
| 
 | ||||
|                 if (!TextUtils.isEmpty(instadp)) { | ||||
|                     final Document doc = Jsoup.parse(instadp); | ||||
|                     boolean fallback = false; | ||||
| 
 | ||||
|                     final int imgIndex = instadp.indexOf("preloadImg('"), lastIndex; | ||||
| 
 | ||||
|                     Element element = doc.selectFirst(".instadp"); | ||||
|                     if (element != null && (element = element.selectFirst(".picture")) != null) | ||||
|                         out = element.attr("src"); | ||||
|                     else if ((element = doc.selectFirst(".download-btn")) != null) | ||||
|                         out = element.attr("href"); | ||||
|                     else if (imgIndex != -1 && (lastIndex = instadp.indexOf("')", imgIndex)) != -1) | ||||
|                         out = instadp.substring(imgIndex + 12, lastIndex); | ||||
|                     else { | ||||
|                         final Elements imgs = doc.getElementsByTag("img"); | ||||
|                         for (final Element img : imgs) { | ||||
|                             final String imgStr = img.toString(); | ||||
|                             if (imgStr.contains("cdninstagram.com")) out = img.attr("src"); | ||||
|                         } | ||||
|                 Element element = doc.selectFirst(".instadp"); | ||||
|                 if (element != null && (element = element.selectFirst(".picture")) != null) | ||||
|                     out = element.attr("src"); | ||||
|                 else if ((element = doc.selectFirst(".download-btn")) != null) | ||||
|                     out = element.attr("href"); | ||||
|                 else if (imgIndex != -1 && (lastIndex = instadp.indexOf("')", imgIndex)) != -1) | ||||
|                     out = instadp.substring(imgIndex + 12, lastIndex); | ||||
|                 else { | ||||
|                     final Elements imgs = doc.getElementsByTag("img"); | ||||
|                     for (final Element img : imgs) { | ||||
|                         final String imgStr = img.toString(); | ||||
|                         if (imgStr.contains("cdninstagram.com")) out = img.attr("src"); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @ -7,29 +7,34 @@ import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.webservices.GraphQLService; | ||||
| import awais.instagrabber.webservices.ProfileService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| public class ProfilePostFetchService implements PostFetcher.PostFetchService { | ||||
|     private static final String TAG = "ProfilePostFetchService"; | ||||
|     private final ProfileService profileService; | ||||
|     private final GraphQLService graphQLService; | ||||
|     private final ProfileModel profileModel; | ||||
|     private String nextCursor; | ||||
|     private boolean hasNextPage; | ||||
|     private final boolean isLoggedIn; | ||||
|     private String nextMaxId; | ||||
|     private boolean moreAvailable; | ||||
| 
 | ||||
|     public ProfilePostFetchService(final ProfileModel profileModel) { | ||||
|     public ProfilePostFetchService(final ProfileModel profileModel, final boolean isLoggedIn) { | ||||
|         this.profileModel = profileModel; | ||||
|         profileService = ProfileService.getInstance(); | ||||
|         this.isLoggedIn = isLoggedIn; | ||||
|         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||
|         profileService = isLoggedIn ? ProfileService.getInstance() : null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||
|         profileService.fetchPosts(profileModel, 30, nextCursor, new ServiceCallback<PostsFetchResponse>() { | ||||
|         final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final PostsFetchResponse result) { | ||||
|                 if (result == null) return; | ||||
|                 nextCursor = result.getNextCursor(); | ||||
|                 hasNextPage = result.hasNextPage(); | ||||
|                 nextMaxId = result.getNextCursor(); | ||||
|                 moreAvailable = result.hasNextPage(); | ||||
|                 if (fetchListener != null) { | ||||
|                     fetchListener.onResult(result.getFeedModels()); | ||||
|                 } | ||||
| @ -42,16 +47,18 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService { | ||||
|                     fetchListener.onFailure(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|         }; | ||||
|         if (isLoggedIn) profileService.fetchPosts(profileModel.getId(), nextMaxId, cb); | ||||
|         else graphQLService.fetchProfilePosts(profileModel.getId(), 30, nextMaxId, cb); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void reset() { | ||||
|         nextCursor = null; | ||||
|         nextMaxId = null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean hasNextPage() { | ||||
|         return hasNextPage; | ||||
|         return moreAvailable; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,89 +0,0 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.io.DataOutputStream; | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import awais.instagrabber.models.StoryModel; | ||||
| import awais.instagrabber.models.stickers.QuizModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class QuizAction extends AsyncTask<Integer, Void, Integer> { | ||||
|     private static final String TAG = "QuizAction"; | ||||
| 
 | ||||
|     private final StoryModel storyModel; | ||||
|     private final QuizModel quizModel; | ||||
|     private final String cookie; | ||||
|     private final OnTaskCompleteListener onTaskCompleteListener; | ||||
| 
 | ||||
|     public QuizAction(final StoryModel storyModel, | ||||
|                       final QuizModel quizModel, | ||||
|                       final String cookie, | ||||
|                       final OnTaskCompleteListener onTaskCompleteListener) { | ||||
|         this.storyModel = storyModel; | ||||
|         this.quizModel = quizModel; | ||||
|         this.cookie = cookie; | ||||
|         this.onTaskCompleteListener = onTaskCompleteListener; | ||||
|     } | ||||
| 
 | ||||
|     protected Integer doInBackground(Integer... rawChoice) { | ||||
|         int choice = rawChoice[0]; | ||||
|         final String url = "https://i.instagram.com/api/v1/media/" + storyModel.getStoryMediaId().split("_")[0] + "/" + quizModel.getId() + "/story_quiz_answer/"; | ||||
|         HttpURLConnection urlConnection = null; | ||||
|         try { | ||||
|             JSONObject ogBody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString() | ||||
|                     + "\",\"mutation_token\":\"" + UUID.randomUUID().toString() | ||||
|                     + "\",\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0] | ||||
|                     + "\",\"_uid\":\"" + CookieUtils.getUserIdFromCookie(cookie) | ||||
|                     + "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID) | ||||
|                     + "\"}"); | ||||
|             ogBody.put("answer", String.valueOf(choice)); | ||||
|             String urlParameters = Utils.sign(ogBody.toString()); | ||||
|             urlConnection = (HttpURLConnection) new URL(url).openConnection(); | ||||
|             urlConnection.setRequestMethod("POST"); | ||||
|             urlConnection.setUseCaches(false); | ||||
|             urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT); | ||||
|             urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); | ||||
|             if (urlParameters != null) { | ||||
|                 urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length)); | ||||
|             } | ||||
|             urlConnection.setDoOutput(true); | ||||
|             DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); | ||||
|             wr.writeBytes(urlParameters); | ||||
|             wr.flush(); | ||||
|             wr.close(); | ||||
|             Log.d(TAG, "quiz: " + url + " " + cookie + " " + urlParameters); | ||||
|             urlConnection.connect(); | ||||
|             if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { | ||||
|                 return choice; | ||||
|             } | ||||
|         } catch (Throwable ex) { | ||||
|             Log.e(TAG, "quiz: " + ex); | ||||
|         } finally { | ||||
|             if (urlConnection != null) { | ||||
|                 urlConnection.disconnect(); | ||||
|             } | ||||
|         } | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final Integer choice) { | ||||
|         if (onTaskCompleteListener == null || choice == null) return; | ||||
|         onTaskCompleteListener.onTaskComplete(choice); | ||||
|     } | ||||
| 
 | ||||
|     public interface OnTaskCompleteListener { | ||||
|         void onTaskComplete(final int choice); | ||||
|     } | ||||
| } | ||||
| @ -1,88 +0,0 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.io.DataOutputStream; | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import awais.instagrabber.models.StoryModel; | ||||
| import awais.instagrabber.models.stickers.QuestionModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class RespondAction extends AsyncTask<String, Void, Boolean> { | ||||
| 
 | ||||
|     private final StoryModel storyModel; | ||||
|     private final QuestionModel questionModel; | ||||
|     private final String cookie; | ||||
|     private final OnTaskCompleteListener onTaskCompleteListener; | ||||
| 
 | ||||
|     public RespondAction(final StoryModel storyModel, | ||||
|                          final QuestionModel questionModel, | ||||
|                          final String cookie, | ||||
|                          final OnTaskCompleteListener onTaskCompleteListener) { | ||||
|         this.storyModel = storyModel; | ||||
|         this.questionModel = questionModel; | ||||
|         this.cookie = cookie; | ||||
|         this.onTaskCompleteListener = onTaskCompleteListener; | ||||
|     } | ||||
| 
 | ||||
|     protected Boolean doInBackground(String... rawChoice) { | ||||
|         final String url = "https://i.instagram.com/api/v1/media/" | ||||
|                 + storyModel.getStoryMediaId().split("_")[0] + "/" + questionModel.getId() + "/story_question_response/"; | ||||
|         HttpURLConnection urlConnection = null; | ||||
|         try { | ||||
|             final JSONObject ogbody = new JSONObject("{\"client_context\":\"" + UUID.randomUUID().toString() | ||||
|                     + "\",\"mutation_token\":\"" + UUID.randomUUID().toString() | ||||
|                     + "\",\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0] | ||||
|                     + "\",\"_uid\":\"" + CookieUtils.getUserIdFromCookie(cookie) | ||||
|                     + "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID) | ||||
|                     + "\"}"); | ||||
|             String choice = rawChoice[0].replaceAll("\"", ("\\\"")); | ||||
|             ogbody.put("response", choice); | ||||
|             String urlParameters = Utils.sign(ogbody.toString()); | ||||
|             urlConnection = (HttpURLConnection) new URL(url).openConnection(); | ||||
|             urlConnection.setRequestMethod("POST"); | ||||
|             urlConnection.setUseCaches(false); | ||||
|             urlConnection.setRequestProperty("User-Agent", Constants.I_USER_AGENT); | ||||
|             urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); | ||||
|             urlConnection.setRequestProperty("Content-Length", Integer.toString(urlParameters.getBytes().length)); | ||||
|             urlConnection.setDoOutput(true); | ||||
|             DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); | ||||
|             wr.writeBytes(urlParameters); | ||||
|             wr.flush(); | ||||
|             wr.close(); | ||||
|             urlConnection.connect(); | ||||
|             if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { | ||||
|                 return true; | ||||
|             } | ||||
| 
 | ||||
|         } catch (Throwable ex) { | ||||
|             Log.e("austin_debug", "respond: " + ex); | ||||
|         } finally { | ||||
|             if (urlConnection != null) { | ||||
|                 urlConnection.disconnect(); | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final Boolean ok) { | ||||
|         if (onTaskCompleteListener == null) return; | ||||
|         onTaskCompleteListener.onTaskComplete(ok); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public interface OnTaskCompleteListener { | ||||
|         void onTaskComplete(final boolean result); | ||||
|     } | ||||
| } | ||||
| @ -6,34 +6,39 @@ import awais.instagrabber.customviews.helpers.PostFetcher; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.enums.PostItemType; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.webservices.GraphQLService; | ||||
| import awais.instagrabber.webservices.ProfileService; | ||||
| import awais.instagrabber.webservices.ProfileService.SavedPostsFetchResponse; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| public class SavedPostFetchService implements PostFetcher.PostFetchService { | ||||
|     private final ProfileService profileService; | ||||
|     private final GraphQLService graphQLService; | ||||
|     private final String profileId; | ||||
|     private final PostItemType type; | ||||
|     private final boolean isLoggedIn; | ||||
| 
 | ||||
|     private String nextMaxId; | ||||
|     private boolean moreAvailable; | ||||
| 
 | ||||
|     public SavedPostFetchService(final String profileId, final PostItemType type) { | ||||
|     public SavedPostFetchService(final String profileId, final PostItemType type, final boolean isLoggedIn) { | ||||
|         this.profileId = profileId; | ||||
|         this.type = type; | ||||
|         profileService = ProfileService.getInstance(); | ||||
|         this.isLoggedIn = isLoggedIn; | ||||
|         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||
|         profileService = isLoggedIn ? ProfileService.getInstance() : null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||
|         final ServiceCallback<SavedPostsFetchResponse> callback = new ServiceCallback<SavedPostsFetchResponse>() { | ||||
|         final ServiceCallback<PostsFetchResponse> callback = new ServiceCallback<PostsFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final SavedPostsFetchResponse result) { | ||||
|             public void onSuccess(final PostsFetchResponse result) { | ||||
|                 if (result == null) return; | ||||
|                 nextMaxId = result.getNextMaxId(); | ||||
|                 moreAvailable = result.isMoreAvailable(); | ||||
|                 nextMaxId = result.getNextCursor(); | ||||
|                 moreAvailable = result.hasNextPage(); | ||||
|                 if (fetchListener != null) { | ||||
|                     fetchListener.onResult(result.getItems()); | ||||
|                     fetchListener.onResult(result.getFeedModels()); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
| @ -50,7 +55,8 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService { | ||||
|                 profileService.fetchLiked(nextMaxId, callback); | ||||
|                 break; | ||||
|             case TAGGED: | ||||
|                 profileService.fetchTagged(profileId, nextMaxId, callback); | ||||
|                 if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback); | ||||
|                 else graphQLService.fetchTaggedPosts(profileId, 30, nextMaxId, callback); | ||||
|                 break; | ||||
|             case SAVED: | ||||
|             default: | ||||
|  | ||||
| @ -1,69 +0,0 @@ | ||||
| package awais.instagrabber.asyncs; | ||||
| 
 | ||||
| import android.os.AsyncTask; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import java.io.DataOutputStream; | ||||
| import java.net.HttpURLConnection; | ||||
| import java.net.URL; | ||||
| 
 | ||||
| import awais.instagrabber.models.StoryModel; | ||||
| import awais.instagrabber.models.stickers.PollModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| 
 | ||||
| public class VoteAction extends AsyncTask<Integer, Void, Integer> { | ||||
| 
 | ||||
|     private static final String TAG = "VoteAction"; | ||||
| 
 | ||||
|     private final StoryModel storyModel; | ||||
|     private final PollModel pollModel; | ||||
|     private final String cookie; | ||||
|     private final OnTaskCompleteListener onTaskCompleteListener; | ||||
| 
 | ||||
|     public VoteAction(final StoryModel storyModel, | ||||
|                       final PollModel pollModel, | ||||
|                       final String cookie, | ||||
|                       final OnTaskCompleteListener onTaskCompleteListener) { | ||||
|         this.storyModel = storyModel; | ||||
|         this.pollModel = pollModel; | ||||
|         this.cookie = cookie; | ||||
|         this.onTaskCompleteListener = onTaskCompleteListener; | ||||
|     } | ||||
| 
 | ||||
|     protected Integer doInBackground(Integer... rawChoice) { | ||||
|         int choice = rawChoice[0]; | ||||
|         final String url = "https://www.instagram.com/media/" + storyModel.getStoryMediaId().split("_")[0] + "/" + pollModel.getId() + "/story_poll_vote/"; | ||||
|         try { | ||||
|             final HttpURLConnection urlConnection = (HttpURLConnection) new URL(url).openConnection(); | ||||
|             urlConnection.setRequestMethod("POST"); | ||||
|             urlConnection.setUseCaches(false); | ||||
|             urlConnection.setRequestProperty("User-Agent", Constants.USER_AGENT); | ||||
|             urlConnection.setRequestProperty("x-csrftoken", cookie.split("csrftoken=")[1].split(";")[0]); | ||||
|             urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); | ||||
|             urlConnection.setRequestProperty("Content-Length", "6"); | ||||
|             urlConnection.setDoOutput(true); | ||||
|             DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream()); | ||||
|             wr.writeBytes("vote=" + choice); | ||||
|             wr.flush(); | ||||
|             wr.close(); | ||||
|             urlConnection.connect(); | ||||
|             if (urlConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { | ||||
|                 return choice; | ||||
|             } | ||||
|             urlConnection.disconnect(); | ||||
|         } catch (Exception ex) { | ||||
|             Log.e(TAG, "Error", ex); | ||||
|         } | ||||
|         return -1; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPostExecute(final Integer result) { | ||||
|         if (result == null || onTaskCompleteListener == null) return; | ||||
|         onTaskCompleteListener.onTaskComplete(result); | ||||
|     } | ||||
| 
 | ||||
|     public interface OnTaskCompleteListener { | ||||
|         void onTaskComplete(final int choice); | ||||
|     } | ||||
| } | ||||
| @ -122,4 +122,131 @@ | ||||
| //     public interface OnBroadcastCompleteListener { | ||||
| //         void onTaskComplete(DirectThreadBroadcastResponse response); | ||||
| //     } | ||||
| // | ||||
| //     public enum ItemType { | ||||
| //         TEXT("text"), | ||||
| //         REACTION("reaction"), | ||||
| //         REELSHARE("reel_share"), | ||||
| //         IMAGE("configure_photo"); | ||||
| // | ||||
| //         private final String value; | ||||
| // | ||||
| //         ItemType(final String value) { | ||||
| //             this.value = value; | ||||
| //         } | ||||
| // | ||||
| //         public String getValue() { | ||||
| //             return value; | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     public static abstract class BroadcastOptions { | ||||
| //         private final ItemType itemType; | ||||
| // | ||||
| //         public BroadcastOptions(final ItemType itemType) { | ||||
| //             this.itemType = itemType; | ||||
| //         } | ||||
| // | ||||
| //         public ItemType getItemType() { | ||||
| //             return itemType; | ||||
| //         } | ||||
| // | ||||
| //         abstract Map<String, String> getFormMap(); | ||||
| //     } | ||||
| // | ||||
| //     public static class TextBroadcastOptions extends BroadcastOptions { | ||||
| //         private final String text; | ||||
| // | ||||
| //         public TextBroadcastOptions(String text) throws UnsupportedEncodingException { | ||||
| //             super(ItemType.TEXT); | ||||
| //             this.text = URLEncoder.encode(text, "UTF-8") | ||||
| //                     .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'").replaceAll("%22", "\\\"") | ||||
| //                     .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~").replaceAll("%0A", "\n"); | ||||
| //         } | ||||
| // | ||||
| //         @Override | ||||
| //         Map<String, String> getFormMap() { | ||||
| //             return Collections.singletonMap("text", text); | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     public static class ReactionBroadcastOptions extends BroadcastOptions { | ||||
| //         private final String itemId; | ||||
| //         private final boolean delete; | ||||
| // | ||||
| //         public ReactionBroadcastOptions(String itemId, boolean delete) { | ||||
| //             super(ItemType.REACTION); | ||||
| //             this.itemId = itemId; | ||||
| //             this.delete = delete; | ||||
| //         } | ||||
| // | ||||
| //         @Override | ||||
| //         Map<String, String> getFormMap() { | ||||
| //             final Map<String, String> form = new HashMap<>(); | ||||
| //             form.put("item_id", itemId); | ||||
| //             form.put("reaction_status", delete ? "deleted" : "created"); | ||||
| //             form.put("reaction_type", "like"); | ||||
| //             return form; | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     public static class StoryReplyBroadcastOptions extends BroadcastOptions { | ||||
| //         private final String text, mediaId, reelId; | ||||
| // | ||||
| //         public StoryReplyBroadcastOptions(String text, String mediaId, String reelId) throws UnsupportedEncodingException { | ||||
| //             super(ItemType.REELSHARE); | ||||
| //             this.text = URLEncoder.encode(text, "UTF-8") | ||||
| //                     .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'") | ||||
| //                     .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~").replaceAll("%0A", "\n"); | ||||
| //             this.mediaId = mediaId; | ||||
| //             this.reelId = reelId; // or user id, usually same | ||||
| //         } | ||||
| // | ||||
| //         @Override | ||||
| //         Map<String, String> getFormMap() { | ||||
| //             final Map<String, String> form = new HashMap<>(); | ||||
| //             form.put("text", text); | ||||
| //             form.put("media_id", mediaId); | ||||
| //             form.put("reel_id", reelId); | ||||
| //             form.put("entry", "reel"); | ||||
| //             return form; | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     public static class ImageBroadcastOptions extends BroadcastOptions { | ||||
| //         final boolean allowFullAspectRatio; | ||||
| //         final String uploadId; | ||||
| // | ||||
| //         public ImageBroadcastOptions(final boolean allowFullAspectRatio, final String uploadId) { | ||||
| //             super(ItemType.IMAGE); | ||||
| //             this.allowFullAspectRatio = allowFullAspectRatio; | ||||
| //             this.uploadId = uploadId; | ||||
| //         } | ||||
| // | ||||
| //         @Override | ||||
| //         Map<String, String> getFormMap() { | ||||
| //             final Map<String, String> form = new HashMap<>(); | ||||
| //             form.put("allow_full_aspect_ratio", String.valueOf(allowFullAspectRatio)); | ||||
| //             form.put("upload_id", uploadId); | ||||
| //             return form; | ||||
| //         } | ||||
| //     } | ||||
| // | ||||
| //     public static class DirectThreadBroadcastResponse { | ||||
| //         private final int responseCode; | ||||
| //         private final JSONObject response; | ||||
| // | ||||
| //         public DirectThreadBroadcastResponse(int responseCode, JSONObject response) { | ||||
| //             this.responseCode = responseCode; | ||||
| //             this.response = response; | ||||
| //         } | ||||
| // | ||||
| //         public int getResponseCode() { | ||||
| //             return responseCode; | ||||
| //         } | ||||
| // | ||||
| //         public JSONObject getResponse() { | ||||
| //             return response; | ||||
| //         } | ||||
| //     } | ||||
| // } | ||||
|  | ||||
| @ -183,11 +183,13 @@ public class PostsRecyclerView extends RecyclerView { | ||||
| 
 | ||||
|     private void initSelf() { | ||||
|         feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class); | ||||
|         feedViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> { | ||||
|             if (!shouldScrollToTop) return; | ||||
|             smoothScrollToPosition(0); | ||||
|             shouldScrollToTop = false; | ||||
|         })); | ||||
|         feedViewModel.getList().observe(lifeCycleOwner, list -> { | ||||
|             if (list.size() > 0) feedAdapter.submitList(list, () -> { | ||||
|                 if (!shouldScrollToTop) return; | ||||
|                 smoothScrollToPosition(0); | ||||
|                 shouldScrollToTop = false; | ||||
|             }); | ||||
|         }); | ||||
|         postFetcher = new PostFetcher(postFetchService, fetchListener); | ||||
|         if (layoutPreferences.getHasGap()) { | ||||
|             addItemDecoration(gridSpacingItemDecoration); | ||||
|  | ||||
| @ -161,7 +161,7 @@ public abstract class AppDatabase extends RoomDatabase { | ||||
|                         final FavoriteType type = favoriteTypeQueryPair.first; | ||||
|                         final String query = favoriteTypeQueryPair.second; | ||||
|                         oldModels.add(new Favorite( | ||||
|                                 -1, | ||||
|                                 0, | ||||
|                                 query, | ||||
|                                 type, | ||||
|                                 queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) | ||||
|  | ||||
| @ -21,7 +21,7 @@ public interface FavoriteDao { | ||||
|     @Query("SELECT * FROM favorites WHERE query_text = :query and type = :type") | ||||
|     Favorite findFavoriteByQueryAndType(String query, FavoriteType type); | ||||
| 
 | ||||
|     @Insert(onConflict = OnConflictStrategy.REPLACE) | ||||
|     @Insert | ||||
|     List<Long> insertFavorites(Favorite... favorites); | ||||
| 
 | ||||
|     @Update | ||||
|  | ||||
| @ -9,6 +9,7 @@ import android.graphics.drawable.ColorDrawable; | ||||
| import android.os.AsyncTask; | ||||
| import android.os.Bundle; | ||||
| import android.os.Environment; | ||||
| import android.util.Log; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| @ -19,6 +20,7 @@ import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.core.content.ContextCompat; | ||||
| import androidx.fragment.app.DialogFragment; | ||||
| import androidx.fragment.app.FragmentActivity; | ||||
| 
 | ||||
| import com.facebook.drawee.backends.pipeline.Fresco; | ||||
| import com.facebook.drawee.controller.BaseControllerListener; | ||||
| @ -30,9 +32,19 @@ import java.io.File; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.asyncs.ProfilePictureFetcher; | ||||
| import awais.instagrabber.databinding.DialogProfilepicBinding; | ||||
| import awais.instagrabber.db.entities.Account; | ||||
| import awais.instagrabber.db.repositories.RepositoryCallback; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.StoryModel; | ||||
| import awais.instagrabber.repositories.responses.UserInfo; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.webservices.ProfileService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class ProfilePicDialogFragment extends DialogFragment { | ||||
|     private static final String TAG = "ProfilePicDlgFragment"; | ||||
| @ -41,9 +53,15 @@ public class ProfilePicDialogFragment extends DialogFragment { | ||||
|     private final String name; | ||||
|     private final String fallbackUrl; | ||||
| 
 | ||||
|     private boolean isLoggedIn; | ||||
|     private DialogProfilepicBinding binding; | ||||
|     private String url; | ||||
| 
 | ||||
|     private final FetchListener<String> fetchListener = profileUrl -> { | ||||
|         url = profileUrl; | ||||
|         setupPhoto(); | ||||
|     }; | ||||
| 
 | ||||
|     public ProfilePicDialogFragment(final String id, final String name, final String fallbackUrl) { | ||||
|         this.id = id; | ||||
|         this.name = name; | ||||
| @ -55,6 +73,8 @@ public class ProfilePicDialogFragment extends DialogFragment { | ||||
|                              final ViewGroup container, | ||||
|                              final Bundle savedInstanceState) { | ||||
|         binding = DialogProfilepicBinding.inflate(inflater, container, false); | ||||
|         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; | ||||
|         return binding.getRoot(); | ||||
|     } | ||||
| 
 | ||||
| @ -83,7 +103,7 @@ public class ProfilePicDialogFragment extends DialogFragment { | ||||
|     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { | ||||
|         super.onViewCreated(view, savedInstanceState); | ||||
|         init(); | ||||
|         fetchPhoto(); | ||||
|         fetchAvatar(); | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
| @ -106,37 +126,55 @@ public class ProfilePicDialogFragment extends DialogFragment { | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void fetchPhoto() { | ||||
|         final FetchListener<String> fetchListener = profileUrl -> { | ||||
|             url = profileUrl; | ||||
|             if (TextUtils.isEmpty(url)) { | ||||
|                 url = fallbackUrl; | ||||
|             } | ||||
|             final DraweeController controller = Fresco | ||||
|                     .newDraweeControllerBuilder() | ||||
|                     .setUri(url) | ||||
|                     .setOldController(binding.imageViewer.getController()) | ||||
|                     .setControllerListener(new BaseControllerListener<ImageInfo>() { | ||||
|                         @Override | ||||
|                         public void onFailure(final String id, final Throwable throwable) { | ||||
|                             super.onFailure(id, throwable); | ||||
|                             binding.download.setVisibility(View.GONE); | ||||
|                             binding.progressView.setVisibility(View.GONE); | ||||
|                         } | ||||
|     private void fetchAvatar() { | ||||
|         if (isLoggedIn) { | ||||
|             final ProfileService profileService = ProfileService.getInstance(); | ||||
|             profileService.getUserInfo(id, new ServiceCallback<UserInfo>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final UserInfo result) { | ||||
|                     if (result != null) { | ||||
|                         fetchListener.onResult(result.getHDProfilePicUrl()); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onFinalImageSet(final String id, | ||||
|                                                     final ImageInfo imageInfo, | ||||
|                                                     final Animatable animatable) { | ||||
|                             super.onFinalImageSet(id, imageInfo, animatable); | ||||
|                             binding.download.setVisibility(View.VISIBLE); | ||||
|                             binding.progressView.setVisibility(View.GONE); | ||||
|                         } | ||||
|                     }) | ||||
|                     .build(); | ||||
|             binding.imageViewer.setController(controller); | ||||
|         }; | ||||
|         new ProfilePictureFetcher(name, id, fetchListener, url, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|                 @Override | ||||
|                 public void onFailure(final Throwable t) { | ||||
|                     final Context context = getContext(); | ||||
|                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                     getDialog().dismiss(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         else new ProfilePictureFetcher(name, id, fetchListener, fallbackUrl, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|     } | ||||
| 
 | ||||
|     private void setupPhoto() { | ||||
|         if (TextUtils.isEmpty(url)) { | ||||
|             url = fallbackUrl; | ||||
|         } | ||||
|         final DraweeController controller = Fresco | ||||
|                 .newDraweeControllerBuilder() | ||||
|                 .setUri(url) | ||||
|                 .setOldController(binding.imageViewer.getController()) | ||||
|                 .setControllerListener(new BaseControllerListener<ImageInfo>() { | ||||
|                     @Override | ||||
|                     public void onFailure(final String id, final Throwable throwable) { | ||||
|                         super.onFailure(id, throwable); | ||||
|                         binding.download.setVisibility(View.GONE); | ||||
|                         binding.progressView.setVisibility(View.GONE); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onFinalImageSet(final String id, | ||||
|                                                 final ImageInfo imageInfo, | ||||
|                                                 final Animatable animatable) { | ||||
|                         super.onFinalImageSet(id, imageInfo, animatable); | ||||
|                         binding.download.setVisibility(View.VISIBLE); | ||||
|                         binding.progressView.setVisibility(View.GONE); | ||||
|                     } | ||||
|                 }) | ||||
|                 .build(); | ||||
|         binding.imageViewer.setController(controller); | ||||
|     } | ||||
| 
 | ||||
|     private void downloadProfilePicture() { | ||||
|  | ||||
| @ -25,6 +25,7 @@ import androidx.appcompat.widget.LinearLayoutCompat; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
| import androidx.fragment.app.FragmentTransaction; | ||||
| import androidx.lifecycle.ViewModelProvider; | ||||
| import androidx.navigation.NavController; | ||||
| import androidx.navigation.NavDirections; | ||||
| import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| @ -32,11 +33,18 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| 
 | ||||
| import com.google.android.material.bottomsheet.BottomSheetDialogFragment; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.LinkedList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.CommentsAdapter; | ||||
| import awais.instagrabber.asyncs.CommentsFetcher; | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||
| import awais.instagrabber.databinding.FragmentCommentsBinding; | ||||
| import awais.instagrabber.dialogs.ProfilePicDialogFragment; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.CommentModel; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| @ -56,17 +64,48 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
| 
 | ||||
|     private CommentsAdapter commentsAdapter; | ||||
|     private FragmentCommentsBinding binding; | ||||
|     private String shortCode; | ||||
|     private String userId; | ||||
|     private LinearLayoutManager layoutManager; | ||||
|     private RecyclerLazyLoader lazyLoader; | ||||
|     private String shortCode, userId, endCursor = null; | ||||
|     private Resources resources; | ||||
|     private InputMethodManager imm; | ||||
|     private AppCompatActivity fragmentActivity; | ||||
|     private LinearLayoutCompat root; | ||||
|     private boolean shouldRefresh = true; | ||||
|     private boolean shouldRefresh = true, hasNextPage = false; | ||||
|     private MediaService mediaService; | ||||
|     private String postId; | ||||
|     private AsyncTask<Void, Void, List<CommentModel>> currentlyRunning; | ||||
|     private CommentsViewModel commentsViewModel; | ||||
| 
 | ||||
|     private final FetchListener<List<CommentModel>> fetchListener = new FetchListener<List<CommentModel>>() { | ||||
|         @Override | ||||
|         public void doBefore() { | ||||
|             binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onResult(final List<CommentModel> commentModels) { | ||||
|             if (commentModels != null && commentModels.size() > 0) { | ||||
|                 endCursor = commentModels.get(0).getEndCursor(); | ||||
|                 hasNextPage = commentModels.get(0).hasNextPage(); | ||||
|                 List<CommentModel> list = commentsViewModel.getList().getValue(); | ||||
|                 list = list != null ? new LinkedList<>(list) : new LinkedList<>(); | ||||
|                 // final int oldSize = list != null ? list.size() : 0; | ||||
|                 list.addAll(commentModels); | ||||
|                 commentsViewModel.getList().postValue(list); | ||||
|             } | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             stopCurrentExecutor(); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(Throwable t) { | ||||
|             Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             stopCurrentExecutor(); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() { | ||||
|         @Override | ||||
|         public void onClick(final CommentModel comment) { | ||||
| @ -181,11 +220,11 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         new CommentsFetcher(shortCode, commentModels -> { | ||||
|             commentsViewModel.getList().postValue(commentModels); | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|         endCursor = null; | ||||
|         lazyLoader.resetState(); | ||||
|         commentsViewModel.getList().postValue(Collections.emptyList()); | ||||
|         stopCurrentExecutor(); | ||||
|         currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
| @ -198,7 +237,8 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class); | ||||
|         binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
|         layoutManager = new LinearLayoutManager(getContext()); | ||||
|         binding.rvComments.setLayoutManager(layoutManager); | ||||
|         commentsAdapter = new CommentsAdapter(commentCallback); | ||||
|         binding.rvComments.setAdapter(commentsAdapter); | ||||
|         commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList); | ||||
| @ -226,6 +266,13 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|             }); | ||||
|             binding.commentField.setEndIconOnClickListener(newCommentListener); | ||||
|         } | ||||
|         lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { | ||||
|             if (hasNextPage && !TextUtils.isEmpty(endCursor)) | ||||
|                 currentlyRunning = new CommentsFetcher(shortCode, endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|             endCursor = null; | ||||
|         }); | ||||
|         binding.rvComments.addOnScrollListener(lazyLoader); | ||||
|         stopCurrentExecutor(); | ||||
|         onRefresh(); | ||||
|     } | ||||
| 
 | ||||
| @ -249,30 +296,29 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|                 && (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) { | ||||
|             commentDialogList = new String[]{ | ||||
|                     resources.getString(R.string.open_profile), | ||||
|                     resources.getString(R.string.view_pfp), | ||||
| //                    resources.getString(R.string.comment_viewer_copy_user), | ||||
|                     resources.getString(R.string.comment_viewer_copy_comment), | ||||
|                     resources.getString(R.string.comment_viewer_see_likers), | ||||
|                     resources.getString(R.string.comment_viewer_reply_comment), | ||||
|                     commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment) | ||||
|                                             : resources.getString(R.string.comment_viewer_like_comment), | ||||
|                     resources.getString(R.string.comment_viewer_translate_comment), | ||||
|                     resources.getString(R.string.comment_viewer_delete_comment) | ||||
|             }; | ||||
|         } else if (!TextUtils.isEmpty(cookie)) { | ||||
|             commentDialogList = new String[]{ | ||||
|                     resources.getString(R.string.open_profile), | ||||
|                     resources.getString(R.string.view_pfp), | ||||
| //                    resources.getString(R.string.comment_viewer_copy_user), | ||||
|                     resources.getString(R.string.comment_viewer_copy_comment), | ||||
|                     resources.getString(R.string.comment_viewer_see_likers), | ||||
|                     resources.getString(R.string.comment_viewer_reply_comment), | ||||
|                     commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment) | ||||
|                                             : resources.getString(R.string.comment_viewer_like_comment), | ||||
|                     resources.getString(R.string.comment_viewer_translate_comment) | ||||
|             }; | ||||
|         } else { | ||||
|             commentDialogList = new String[]{ | ||||
|                     resources.getString(R.string.open_profile), | ||||
|                     resources.getString(R.string.view_pfp), | ||||
| //                    resources.getString(R.string.comment_viewer_copy_user), | ||||
|                     resources.getString(R.string.comment_viewer_copy_comment) | ||||
|                     resources.getString(R.string.comment_viewer_copy_comment), | ||||
|                     resources.getString(R.string.comment_viewer_see_likers) | ||||
|             }; | ||||
|         } | ||||
|         final Context context = getContext(); | ||||
| @ -284,25 +330,20 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|                 case 0: // open profile | ||||
|                     openProfile("@" + profileModel.getUsername()); | ||||
|                     break; | ||||
|                 case 1: // view profile pic | ||||
|                     final FragmentManager fragmentManager = getParentFragmentManager(); | ||||
|                     final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(), | ||||
|                                                                                            profileModel.getUsername(), | ||||
|                                                                                            profileModel.getHdProfilePic()); | ||||
|                     final FragmentTransaction ft = fragmentManager.beginTransaction(); | ||||
|                     ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) | ||||
|                       .add(fragment, "profilePicDialog") | ||||
|                       .commit(); | ||||
|                     break; | ||||
| //              case 2: // copy username | ||||
| //                  Utils.copyText(context, profileModel.getUsername()); | ||||
| //                  break; | ||||
|                 case 2: // copy comment | ||||
|                 case 1: // copy comment | ||||
|                     Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText()); | ||||
|                     break; | ||||
|                 case 2: // see comment likers, this is surprisingly available to anons | ||||
|                     final NavController navController = getNavController(); | ||||
|                     if (navController != null) { | ||||
|                         final Bundle bundle = new Bundle(); | ||||
|                         bundle.putString("postId", commentModel.getId()); | ||||
|                         bundle.putBoolean("isComment", true); | ||||
|                         navController.navigate(R.id.action_global_likesViewerFragment, bundle); | ||||
|                     } | ||||
|                     else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                     break; | ||||
|                 case 3: // reply to comment | ||||
|                     // final View focus = binding.rvComments.findViewWithTag(commentModel); | ||||
|                     // focus.setBackgroundColor(0x80888888); | ||||
|                     commentsAdapter.setSelected(commentModel); | ||||
|                     String mention = "@" + profileModel.getUsername() + " "; | ||||
|                     binding.commentText.setText(mention); | ||||
| @ -326,7 +367,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                     return; | ||||
|                                 } | ||||
|                                 onRefresh(); | ||||
|                                 commentsAdapter.setLiked(commentModel, true); | ||||
|                             } | ||||
| 
 | ||||
|                             @Override | ||||
| @ -344,7 +385,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                 return; | ||||
|                             } | ||||
|                             onRefresh(); | ||||
|                             commentsAdapter.setLiked(commentModel, false); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
| @ -354,7 +395,29 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|                         } | ||||
|                     }); | ||||
|                     break; | ||||
|                 case 5: // delete comment | ||||
|                 case 5: // translate comment | ||||
|                     mediaService.translate(commentModel.getId(), "2", new ServiceCallback<String>() { | ||||
|                         @Override | ||||
|                         public void onSuccess(final String result) { | ||||
|                             if (TextUtils.isEmpty(result)) { | ||||
|                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                 return; | ||||
|                             } | ||||
|                             new AlertDialog.Builder(context) | ||||
|                                     .setTitle(username) | ||||
|                                     .setMessage(result) | ||||
|                                     .setPositiveButton(R.string.ok, null) | ||||
|                                     .show(); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onFailure(final Throwable t) { | ||||
|                             Log.e(TAG, "Error translating comment", t); | ||||
|                             Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                         } | ||||
|                     }); | ||||
|                     break; | ||||
|                 case 6: // delete comment | ||||
|                     final String userId = CookieUtils.getUserIdFromCookie(cookie); | ||||
|                     if (userId == null) return; | ||||
|                     mediaService.deleteComment( | ||||
| @ -389,4 +452,25 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | ||||
|         final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username); | ||||
|         NavHostFragment.findNavController(this).navigate(action); | ||||
|     } | ||||
| 
 | ||||
|     private void stopCurrentExecutor() { | ||||
|         if (currentlyRunning != null) { | ||||
|             try { | ||||
|                 currentlyRunning.cancel(true); | ||||
|             } catch (final Exception e) { | ||||
|                 if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Nullable | ||||
|     private NavController getNavController() { | ||||
|         NavController navController = null; | ||||
|         try { | ||||
|             navController = NavHostFragment.findNavController(this); | ||||
|         } catch (IllegalStateException e) { | ||||
|             Log.e(TAG, "navigateToProfile", e); | ||||
|         } | ||||
|         return navController; | ||||
|     } | ||||
| } | ||||
| @ -81,7 +81,6 @@ public class FavoritesFragment extends Fragment { | ||||
|             @Override | ||||
|             public void onSuccess(final List<Favorite> favorites) { | ||||
|                 favoritesViewModel.getList().postValue(favorites); | ||||
|                 fetchMissingInfo(favorites); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
| @ -156,109 +155,4 @@ public class FavoritesFragment extends Fragment { | ||||
|         binding.favoriteList.setAdapter(adapter); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private void fetchMissingInfo(final List<Favorite> allFavorites) { | ||||
|         final Runnable runnable = () -> { | ||||
|             final List<Favorite> updatedList = new ArrayList<>(allFavorites); | ||||
|             // cyclic barrier is to make the async calls synchronous | ||||
|             final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> { | ||||
|                 // Log.d(TAG, "fetchMissingInfo: barrier action"); | ||||
|                 favoritesViewModel.getList().postValue(new ArrayList<>(updatedList)); | ||||
|             }); | ||||
|             try { | ||||
|                 for (final Favorite model : allFavorites) { | ||||
|                     cyclicBarrier.reset(); | ||||
|                     // if the model has missing pic or display name (for user and location), fetch those details | ||||
|                     switch (model.getType()) { | ||||
|                         case LOCATION: | ||||
|                             if (TextUtils.isEmpty(model.getDisplayName()) | ||||
|                                     || TextUtils.isEmpty(model.getPicUrl())) { | ||||
|                                 new LocationFetcher(model.getQuery(), result -> { | ||||
|                                     if (result == null) return; | ||||
|                                     final int i = updatedList.indexOf(model); | ||||
|                                     updatedList.remove(i); | ||||
|                                     final Favorite updated = new Favorite( | ||||
|                                             model.getId(), | ||||
|                                             model.getQuery(), | ||||
|                                             model.getType(), | ||||
|                                             result.getName(), | ||||
|                                             result.getSdProfilePic(), | ||||
|                                             model.getDateAdded() | ||||
|                                     ); | ||||
|                                     favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback<Void>() { | ||||
|                                         @Override | ||||
|                                         public void onSuccess(final Void result) { | ||||
|                                             updatedList.add(i, updated); | ||||
|                                             try { | ||||
|                                                 cyclicBarrier.await(); | ||||
|                                             } catch (BrokenBarrierException | InterruptedException e) { | ||||
|                                                 Log.e(TAG, "fetchMissingInfo: ", e); | ||||
|                                             } | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onDataNotAvailable() { | ||||
|                                             try { | ||||
|                                                 cyclicBarrier.await(); | ||||
|                                             } catch (BrokenBarrierException | InterruptedException e) { | ||||
|                                                 Log.e(TAG, "fetchMissingInfo: ", e); | ||||
|                                             } | ||||
|                                         } | ||||
|                                     }); | ||||
|                                 }).execute(); | ||||
|                                 cyclicBarrier.await(); | ||||
|                             } | ||||
|                             break; | ||||
|                         case USER: | ||||
|                             if (TextUtils.isEmpty(model.getDisplayName()) | ||||
|                                     || TextUtils.isEmpty(model.getPicUrl())) { | ||||
|                                 new ProfileFetcher(model.getQuery(), result -> { | ||||
|                                     if (result == null) return; | ||||
|                                     final int i = updatedList.indexOf(model); | ||||
|                                     updatedList.remove(i); | ||||
|                                     final Favorite updated = new Favorite( | ||||
|                                             model.getId(), | ||||
|                                             model.getQuery(), | ||||
|                                             model.getType(), | ||||
|                                             result.getName(), | ||||
|                                             result.getSdProfilePic(), | ||||
|                                             model.getDateAdded() | ||||
|                                     ); | ||||
|                                     favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback<Void>() { | ||||
|                                         @Override | ||||
|                                         public void onSuccess(final Void result) { | ||||
|                                             try { | ||||
|                                                 cyclicBarrier.await(); | ||||
|                                             } catch (BrokenBarrierException | InterruptedException e) { | ||||
|                                                 Log.e(TAG, "fetchMissingInfo: ", e); | ||||
|                                             } | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onDataNotAvailable() { | ||||
|                                             try { | ||||
|                                                 cyclicBarrier.await(); | ||||
|                                             } catch (BrokenBarrierException | InterruptedException e) { | ||||
|                                                 Log.e(TAG, "fetchMissingInfo: ", e); | ||||
|                                             } | ||||
|                                         } | ||||
|                                     }); | ||||
|                                     updatedList.add(i, updated); | ||||
|                                 }).execute(); | ||||
|                                 cyclicBarrier.await(); | ||||
|                             } | ||||
|                             break; | ||||
|                         case HASHTAG: | ||||
|                         default: | ||||
|                             // hashtags don't require displayName or pic | ||||
|                             // updatedList.add(model); | ||||
|                     } | ||||
|                 } | ||||
|             } catch (Exception e) { | ||||
|                 Log.e(TAG, "fetchMissingInfo: ", e); | ||||
|             } | ||||
|             favoritesViewModel.getList().postValue(updatedList); | ||||
|         }; | ||||
|         new Thread(runnable).start(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -20,29 +20,22 @@ import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.SearchView; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| 
 | ||||
| import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.FollowAdapter; | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||
| import awais.instagrabber.databinding.FragmentFollowersViewerBinding; | ||||
| import awais.instagrabber.models.FollowModel; | ||||
| import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; | ||||
| import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.webservices.FriendshipService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awaisomereport.LogCollector; | ||||
| import thoughtbot.expandableadapter.ExpandableGroup; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.logCollector; | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final String TAG = "FollowViewerFragment"; | ||||
| 
 | ||||
| @ -51,9 +44,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | ||||
|     private final ArrayList<FollowModel> followersModels = new ArrayList<>(); | ||||
|     private final ArrayList<FollowModel> allFollowing = new ArrayList<>(); | ||||
| 
 | ||||
|     private boolean isFollowersList, isCompare = false, loading = false; | ||||
|     private String profileId, username, namePost, type; | ||||
|     private boolean moreAvailable = true, isFollowersList, isCompare = false, loading = false, shouldRefresh = true; | ||||
|     private String profileId, username, namePost, type, endCursor; | ||||
|     private Resources resources; | ||||
|     private LinearLayoutManager layoutManager; | ||||
|     private RecyclerLazyLoader lazyLoader; | ||||
|     private FollowModel model; | ||||
|     private FollowAdapter adapter; | ||||
|     private View.OnClickListener clickListener; | ||||
| @ -61,9 +56,61 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | ||||
|     private AsyncTask<Void, Void, FollowModel[]> currentlyExecuting; | ||||
|     private SwipeRefreshLayout root; | ||||
|     private FriendshipService friendshipService; | ||||
|     private boolean shouldRefresh = true; | ||||
|     private AppCompatActivity fragmentActivity; | ||||
| 
 | ||||
|     final ServiceCallback<FriendshipRepoListFetchResponse> followingFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() { | ||||
|         @Override | ||||
|         public void onSuccess(final FriendshipRepoListFetchResponse result) { | ||||
|             if (result != null) { | ||||
|                 followingModels.addAll(result.getItems()); | ||||
|                 if (!isFollowersList) followModels.addAll(result.getItems()); | ||||
|                 if (result.isMoreAvailable()) { | ||||
|                     endCursor = result.getNextMaxId(); | ||||
|                     friendshipService.getList(false, profileId, endCursor, this); | ||||
|                 } else if (followersModels.size() == 0) { | ||||
|                     if (!isFollowersList) moreAvailable = false; | ||||
|                     friendshipService.getList(true, profileId, null, followingFetchCb); | ||||
|                 } else { | ||||
|                     if (!isFollowersList) moreAvailable = false; | ||||
|                     showCompare(); | ||||
|                 } | ||||
|             } else binding.swipeRefreshLayout.setRefreshing(false); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(final Throwable t) { | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             Log.e(TAG, "Error fetching list (double, following)", t); | ||||
|         } | ||||
|     }; | ||||
|     final ServiceCallback<FriendshipRepoListFetchResponse> followersFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() { | ||||
|         @Override | ||||
|         public void onSuccess(final FriendshipRepoListFetchResponse result) { | ||||
|             if (result != null) { | ||||
|                 followersModels.addAll(result.getItems()); | ||||
|                 if (isFollowersList) followModels.addAll(result.getItems()); | ||||
|                 if (result.isMoreAvailable()) { | ||||
|                     endCursor = result.getNextMaxId(); | ||||
|                     friendshipService.getList(true, profileId, endCursor, this); | ||||
|                 } else if (followingModels.size() == 0) { | ||||
|                     if (isFollowersList) moreAvailable = false; | ||||
|                     friendshipService.getList(false, profileId, null, followingFetchCb); | ||||
|                 } else { | ||||
|                     if (isFollowersList) moreAvailable = false; | ||||
|                     showCompare(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(final Throwable t) { | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             Log.e(TAG, "Error fetching list (double, follower)", t); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
| @ -141,13 +188,13 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | ||||
|     public void onRefresh() { | ||||
|         if (isCompare) listCompare(); | ||||
|         else listFollows(); | ||||
|         endCursor = null; | ||||
|         lazyLoader.resetState(); | ||||
|     } | ||||
| 
 | ||||
|     private void listFollows() { | ||||
|         loading = true; | ||||
|         type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following); | ||||
|         setSubtitle(type); | ||||
|         followModels.clear(); | ||||
|         final ServiceCallback<FriendshipRepoListFetchResponse> cb = new ServiceCallback<FriendshipRepoListFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final FriendshipRepoListFetchResponse result) { | ||||
| @ -156,82 +203,70 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | ||||
|                     return; | ||||
|                 } | ||||
|                 else { | ||||
|                     int oldSize = followModels.size() == 0 ? 0 : followModels.size() - 1; | ||||
|                     followModels.addAll(result.getItems()); | ||||
|                     if (result.isMoreAvailable()) { | ||||
|                         friendshipService.getList(isFollowersList, profileId, result.getNextMaxId(), this); | ||||
|                     } | ||||
|                     else { | ||||
|                         binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                         if (isFollowersList) followersModels.addAll(followModels); | ||||
|                         else followingModels.addAll(followModels); | ||||
|                         refreshAdapter(followModels, null, null, null); | ||||
|                         moreAvailable = true; | ||||
|                         endCursor = result.getNextMaxId(); | ||||
|                     } | ||||
|                     else moreAvailable = false; | ||||
|                     binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                     if (isFollowersList) followersModels.addAll(result.getItems()); | ||||
|                     else followingModels.addAll(result.getItems()); | ||||
|                     refreshAdapter(followModels, null, null, null); | ||||
|                     layoutManager.scrollToPosition(oldSize); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(final Throwable t) { | ||||
|                 binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                 Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                 Log.e(TAG, "Error fetching list (single)", t); | ||||
|             } | ||||
|         }; | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         friendshipService.getList(isFollowersList, profileId, null, cb); | ||||
|         layoutManager = new LinearLayoutManager(getContext()); | ||||
|         lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { | ||||
|             if (!TextUtils.isEmpty(endCursor)) { | ||||
|                 binding.swipeRefreshLayout.setRefreshing(true); | ||||
|                 layoutManager.setStackFromEnd(true); | ||||
|                 friendshipService.getList(isFollowersList, profileId, endCursor, cb); | ||||
|             } | ||||
|             endCursor = null; | ||||
|         }); | ||||
|         binding.rvFollow.addOnScrollListener(lazyLoader); | ||||
|         binding.rvFollow.setLayoutManager(layoutManager); | ||||
|         if (moreAvailable) { | ||||
|             binding.swipeRefreshLayout.setRefreshing(true); | ||||
|             friendshipService.getList(isFollowersList, profileId, endCursor, cb); | ||||
|         } | ||||
|         else { | ||||
|             refreshAdapter(followModels, null, null, null); | ||||
|             layoutManager.scrollToPosition(0); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void listCompare() { | ||||
|         layoutManager.setStackFromEnd(false); | ||||
|         binding.rvFollow.clearOnScrollListeners(); | ||||
|         loading = true; | ||||
|         setSubtitle(R.string.followers_compare); | ||||
|         allFollowing.clear(); | ||||
|         followersModels.clear(); | ||||
|         followingModels.clear(); | ||||
|         final ServiceCallback<FriendshipRepoListFetchResponse> followingFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final FriendshipRepoListFetchResponse result) { | ||||
|                 if (result != null) { | ||||
|                     followingModels.addAll(result.getItems()); | ||||
| 
 | ||||
|                     if (result.isMoreAvailable()) { | ||||
|                         friendshipService.getList(false, profileId, result.getNextMaxId(), this); | ||||
|                     } else { | ||||
|                         showCompare(); | ||||
|                     } | ||||
|                 } else binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(final Throwable t) { | ||||
|                 binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                 Log.e(TAG, "Error fetching list (double, following)", t); | ||||
|             } | ||||
|         }; | ||||
|         final ServiceCallback<FriendshipRepoListFetchResponse> followersFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() { | ||||
|             @Override | ||||
|             public void onSuccess(final FriendshipRepoListFetchResponse result) { | ||||
|                 if (result != null) { | ||||
|                     followersModels.addAll(result.getItems()); | ||||
|                     if (result.isMoreAvailable()) { | ||||
|                         friendshipService.getList(true, profileId, result.getNextMaxId(), this); | ||||
|                     } else if (followingModels.size() == 0) { | ||||
|                         friendshipService.getList(false, profileId, null, followingFetchCb); | ||||
|                     } else { | ||||
|                         showCompare(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(final Throwable t) { | ||||
|                 binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                 Log.e(TAG, "Error fetching list (double, follower)", t); | ||||
|             } | ||||
|         }; | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         if (followersModels.size() == 0) { | ||||
|             friendshipService.getList(true, profileId, null, followersFetchCb); | ||||
|         if (moreAvailable) { | ||||
|             binding.swipeRefreshLayout.setRefreshing(true); | ||||
|             Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show(); | ||||
|             friendshipService.getList(isFollowersList, | ||||
|                     profileId, | ||||
|                     endCursor, | ||||
|                     isFollowersList ? followersFetchCb : followingFetchCb); | ||||
|         } | ||||
|         else if (followingModels.size() == 0) { | ||||
|             friendshipService.getList(false, profileId, null, followingFetchCb); | ||||
|         else if (followersModels.size() == 0 || followingModels.size() == 0) { | ||||
|             binding.swipeRefreshLayout.setRefreshing(true); | ||||
|             Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show(); | ||||
|             friendshipService.getList(!isFollowersList, | ||||
|                     profileId, | ||||
|                     null, | ||||
|                     isFollowersList ? followingFetchCb : followersFetchCb); | ||||
|         } | ||||
|         else showCompare(); | ||||
|     } | ||||
| @ -346,12 +381,12 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | ||||
|         final Context context = getContext(); | ||||
|         if (loading) Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_LONG).show(); | ||||
|         else if (isCompare) { | ||||
|             listFollows(); | ||||
|             isCompare = !isCompare; | ||||
|             listFollows(); | ||||
|         } | ||||
|         else { | ||||
|             listCompare(); | ||||
|             isCompare = !isCompare; | ||||
|             listCompare(); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| @ -371,8 +406,7 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | ||||
|             if (allFollowing != null && allFollowing.size() > 0) | ||||
|                 groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing)); | ||||
|         } else { | ||||
|             final ExpandableGroup group = new ExpandableGroup(type, followModels); | ||||
|             groups.add(group); | ||||
|             groups.add(new ExpandableGroup(type, followModels)); | ||||
|         } | ||||
|         adapter = new FollowAdapter(clickListener, groups); | ||||
|         adapter.toggleGroup(0); | ||||
|  | ||||
| @ -54,6 +54,7 @@ import awais.instagrabber.db.entities.Favorite; | ||||
| import awais.instagrabber.db.repositories.FavoriteRepository; | ||||
| import awais.instagrabber.db.repositories.RepositoryCallback; | ||||
| import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.HashtagModel; | ||||
| import awais.instagrabber.models.PostsLayoutPreferences; | ||||
| @ -363,19 +364,27 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|     private void fetchHashtagModel() { | ||||
|         stopCurrentExecutor(); | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> { | ||||
|             hashtagModel = result; | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             final Context context = getContext(); | ||||
|             if (context == null) return; | ||||
|             if (hashtagModel == null) { | ||||
|                 Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); | ||||
|                 return; | ||||
|         currentlyExecuting = new HashtagFetcher(hashtag.substring(1), new FetchListener<HashtagModel>() { | ||||
|             @Override | ||||
|             public void onResult(final HashtagModel result) { | ||||
|                 hashtagModel = result; | ||||
|                 binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                 final Context context = getContext(); | ||||
|                 if (context == null) return; | ||||
|                 if (hashtagModel == null) { | ||||
|                     Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); | ||||
|                     return; | ||||
|                 } | ||||
|                 setTitle(); | ||||
|                 setHashtagDetails(); | ||||
|                 setupPosts(); | ||||
|                 fetchStories(); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(Throwable t) { | ||||
|                 Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|             setTitle(); | ||||
|             setHashtagDetails(); | ||||
|             setupPosts(); | ||||
|             fetchStories(); | ||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|     } | ||||
| 
 | ||||
| @ -410,9 +419,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                                 hashtagDetailsBinding.btnFollowTag.setClickable(true); | ||||
|                                 if (!result) { | ||||
|                                     Log.e(TAG, "onSuccess: result is false"); | ||||
|                                     Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG) | ||||
|                                             .show(); | ||||
|                                     return; | ||||
|                                 } | ||||
|                                 onRefresh(); | ||||
|                                 hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow); | ||||
|                                 hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); | ||||
|                             } | ||||
| 
 | ||||
|                             @Override | ||||
| @ -435,9 +447,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                             hashtagDetailsBinding.btnFollowTag.setClickable(true); | ||||
|                             if (!result) { | ||||
|                                 Log.e(TAG, "onSuccess: result is false"); | ||||
|                                 Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG) | ||||
|                                         .show(); | ||||
|                                 return; | ||||
|                             } | ||||
|                             onRefresh(); | ||||
|                             hashtagDetailsBinding.btnFollowTag.setText(R.string.follow); | ||||
|                             hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_24); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
| @ -462,8 +477,23 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() { | ||||
|             @Override | ||||
|             public void onSuccess(final Favorite result) { | ||||
|                 hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                 hashtagDetailsBinding.favChip.setText(R.string.favorite_short); | ||||
|                 favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||
|                         result.getId(), | ||||
|                         hashtag.substring(1), | ||||
|                         FavoriteType.HASHTAG, | ||||
|                         hashtagModel.getName(), | ||||
|                         hashtagModel.getSdProfilePic(), | ||||
|                         result.getDateAdded() | ||||
|                 ), new RepositoryCallback<Void>() { | ||||
|                     @Override | ||||
|                     public void onSuccess(final Void result) { | ||||
|                         hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                         hashtagDetailsBinding.favChip.setText(R.string.favorite_short); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onDataNotAvailable() {} | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
| @ -492,18 +522,18 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                     @Override | ||||
|                     public void onDataNotAvailable() { | ||||
|                         favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||
|                                 -1, | ||||
|                                 0, | ||||
|                                 hashtag.substring(1), | ||||
|                                 FavoriteType.HASHTAG, | ||||
|                                 hashtagModel.getName(), | ||||
|                                 null, | ||||
|                                 hashtagModel.getSdProfilePic(), | ||||
|                                 new Date() | ||||
|                         ), new RepositoryCallback<Void>() { | ||||
|                             @Override | ||||
|                             public void onSuccess(final Void result) { | ||||
|                                 hashtagDetailsBinding.favChip.setText(R.string.favorite_short); | ||||
|                                 hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                                 showSnackbar(getString(R.string.added_to_favs)); | ||||
|                                 showSnackbar(getString(R.string.added_to_favs_short)); | ||||
|                             } | ||||
| 
 | ||||
|                             @Override | ||||
| @ -513,7 +543,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                 })); | ||||
|         hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); | ||||
|         final String postCount = String.valueOf(hashtagModel.getPostCount()); | ||||
|         final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); | ||||
|         final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, | ||||
|                 hashtagModel.getPostCount() > 2000000000L ? 2000000000 : hashtagModel.getPostCount().intValue(), | ||||
|                 postCount)); | ||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); | ||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); | ||||
|         hashtagDetailsBinding.mainTagPostCount.setText(span); | ||||
| @ -522,7 +554,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|             if (!hasStories) return; | ||||
|             // show stories | ||||
|             final NavDirections action = HashTagFragmentDirections | ||||
|                     .actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName()); | ||||
|                     .actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName(), false, false); | ||||
|             NavHostFragment.findNavController(this).navigate(action); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @ -0,0 +1,170 @@ | ||||
| package awais.instagrabber.fragments; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.content.res.Resources; | ||||
| import android.os.AsyncTask; | ||||
| 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; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.LinearLayoutCompat; | ||||
| import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| 
 | ||||
| import com.google.android.material.bottomsheet.BottomSheetDialogFragment; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.LikesAdapter; | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||
| import awais.instagrabber.databinding.FragmentLikesBinding; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.repositories.responses.GraphQLUserListFetchResponse; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.webservices.GraphQLService; | ||||
| import awais.instagrabber.webservices.MediaService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public final class LikesViewerFragment extends BottomSheetDialogFragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final String TAG = "LikesViewerFragment"; | ||||
| 
 | ||||
|     private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); | ||||
| 
 | ||||
|     private LikesAdapter likesAdapter; | ||||
|     private FragmentLikesBinding binding; | ||||
|     private LinearLayoutManager layoutManager; | ||||
|     private Resources resources; | ||||
|     private AppCompatActivity fragmentActivity; | ||||
|     private LinearLayoutCompat root; | ||||
|     private RecyclerLazyLoader lazyLoader; | ||||
|     private MediaService mediaService; | ||||
|     private GraphQLService graphQLService; | ||||
|     private boolean isLoggedIn; | ||||
|     private String postId, endCursor; | ||||
|     private boolean isComment; | ||||
| 
 | ||||
|     private final ServiceCallback<List<ProfileModel>> cb = new ServiceCallback<List<ProfileModel>>() { | ||||
|         @Override | ||||
|         public void onSuccess(final List<ProfileModel> result) { | ||||
|             final LikesAdapter likesAdapter = new LikesAdapter(result, v -> { | ||||
|                 final Object tag = v.getTag(); | ||||
|                 if (tag instanceof ProfileModel) { | ||||
|                     ProfileModel model = (ProfileModel) tag; | ||||
|                     final Bundle bundle = new Bundle(); | ||||
|                     bundle.putString("username", "@" + model.getUsername()); | ||||
|                     NavHostFragment.findNavController(LikesViewerFragment.this).navigate(R.id.action_global_profileFragment, bundle); | ||||
|                 } | ||||
|             }); | ||||
|             binding.rvLikes.setAdapter(likesAdapter); | ||||
|             binding.rvLikes.setLayoutManager(new LinearLayoutManager(getContext())); | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(final Throwable t) { | ||||
|             Log.e(TAG, "Error", t); | ||||
|             try { | ||||
|                 final Context context = getContext(); | ||||
|                 Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|             catch (Exception e) {} | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     private final ServiceCallback<GraphQLUserListFetchResponse> acb = new ServiceCallback<GraphQLUserListFetchResponse>() { | ||||
|         @Override | ||||
|         public void onSuccess(final GraphQLUserListFetchResponse result) { | ||||
|             endCursor = result.getNextMaxId(); | ||||
|             final LikesAdapter likesAdapter = new LikesAdapter(result.getItems(), v -> { | ||||
|                 final Object tag = v.getTag(); | ||||
|                 if (tag instanceof ProfileModel) { | ||||
|                     ProfileModel model = (ProfileModel) tag; | ||||
|                     final Bundle bundle = new Bundle(); | ||||
|                     bundle.putString("username", "@" + model.getUsername()); | ||||
|                     NavHostFragment.findNavController(LikesViewerFragment.this).navigate(R.id.action_global_profileFragment, bundle); | ||||
|                 } | ||||
|             }); | ||||
|             binding.rvLikes.setAdapter(likesAdapter); | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(final Throwable t) { | ||||
|             Log.e(TAG, "Error", t); | ||||
|             try { | ||||
|                 final Context context = getContext(); | ||||
|                 Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|             catch (Exception e) {} | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; | ||||
|         fragmentActivity = (AppCompatActivity) getActivity(); | ||||
|         mediaService = isLoggedIn ? MediaService.getInstance() : null; | ||||
|         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||
|         // setHasOptionsMenu(true); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { | ||||
|         binding = FragmentLikesBinding.inflate(getLayoutInflater()); | ||||
|         binding.swipeRefreshLayout.setEnabled(false); | ||||
|         binding.swipeRefreshLayout.setNestedScrollingEnabled(false); | ||||
|         root = binding.getRoot(); | ||||
|         return root; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { | ||||
|         init(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         if (isComment && !isLoggedIn) { | ||||
|             lazyLoader.resetState(); | ||||
|             graphQLService.fetchCommentLikers(postId, null, acb); | ||||
|         } | ||||
|         else mediaService.fetchLikes(postId, isComment, cb); | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
|         if (getArguments() == null) return; | ||||
|         final LikesViewerFragmentArgs fragmentArgs = LikesViewerFragmentArgs.fromBundle(getArguments()); | ||||
|         postId = fragmentArgs.getPostId(); | ||||
|         isComment = fragmentArgs.getIsComment(); | ||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         resources = getResources(); | ||||
|         if (isComment && !isLoggedIn) { | ||||
|             layoutManager = new LinearLayoutManager(getContext()); | ||||
|             binding.rvLikes.setLayoutManager(layoutManager); | ||||
|             lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { | ||||
|                 if (!TextUtils.isEmpty(endCursor)) | ||||
|                     graphQLService.fetchCommentLikers(postId, endCursor, acb); | ||||
|                 endCursor = null; | ||||
|             }); | ||||
|             binding.rvLikes.addOnScrollListener(lazyLoader); | ||||
|         } | ||||
|         onRefresh(); | ||||
|     } | ||||
| } | ||||
| @ -400,8 +400,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | ||||
|         // binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         locationDetailsBinding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); | ||||
|         final String postCount = String.valueOf(locationModel.getPostCount()); | ||||
|         final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, | ||||
|                                                                                  postCount)); | ||||
|         final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, | ||||
|                 locationModel.getPostCount() > 2000000000L ? 2000000000 : locationModel.getPostCount().intValue(), | ||||
|                 postCount)); | ||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); | ||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); | ||||
|         locationDetailsBinding.mainLocPostCount.setText(span); | ||||
| @ -455,6 +456,20 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | ||||
|                 locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                 locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                 locationDetailsBinding.favChip.setText(R.string.favorite_short); | ||||
|                 favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||
|                         result.getId(), | ||||
|                         locationId, | ||||
|                         FavoriteType.LOCATION, | ||||
|                         locationModel.getName(), | ||||
|                         locationModel.getSdProfilePic(), | ||||
|                         result.getDateAdded() | ||||
|                 ), new RepositoryCallback<Void>() { | ||||
|                     @Override | ||||
|                     public void onSuccess(final Void result) {} | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onDataNotAvailable() {} | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
| @ -484,7 +499,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | ||||
|                 @Override | ||||
|                 public void onDataNotAvailable() { | ||||
|                     favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||
|                             -1, | ||||
|                             0, | ||||
|                             locationId, | ||||
|                             FavoriteType.LOCATION, | ||||
|                             locationModel.getName(), | ||||
| @ -495,7 +510,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | ||||
|                         public void onSuccess(final Void result) { | ||||
|                             locationDetailsBinding.favChip.setText(R.string.favorite_short); | ||||
|                             locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                             showSnackbar(getString(R.string.added_to_favs)); | ||||
|                             showSnackbar(getString(R.string.added_to_favs_short)); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
| @ -508,7 +523,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | ||||
|             if (hasStories) { | ||||
|                 // show stories | ||||
|                 final NavDirections action = LocationFragmentDirections | ||||
|                         .actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName()); | ||||
|                         .actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName(), false, false); | ||||
|                 NavHostFragment.findNavController(this).navigate(action); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
| @ -18,20 +18,28 @@ import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.core.app.NotificationManagerCompat; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.fragment.app.FragmentManager; | ||||
| import androidx.fragment.app.FragmentTransaction; | ||||
| import androidx.lifecycle.ViewModelProvider; | ||||
| import androidx.navigation.NavDirections; | ||||
| import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.NotificationsAdapter; | ||||
| import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; | ||||
| import awais.instagrabber.asyncs.NotificationsFetcher; | ||||
| import awais.instagrabber.asyncs.PostFetcher; | ||||
| import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; | ||||
| import awais.instagrabber.dialogs.ProfilePicDialogFragment; | ||||
| import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.interfaces.MentionClickListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.NotificationModel; | ||||
| import awais.instagrabber.models.enums.NotificationType; | ||||
| import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| @ -40,6 +48,7 @@ import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.viewmodels.NotificationViewModel; | ||||
| import awais.instagrabber.webservices.FriendshipService; | ||||
| import awais.instagrabber.webservices.MediaService; | ||||
| import awais.instagrabber.webservices.NewsService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| 
 | ||||
| @ -53,96 +62,146 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|     private boolean shouldRefresh = true; | ||||
|     private NotificationViewModel notificationViewModel; | ||||
|     private FriendshipService friendshipService; | ||||
|     private String userId; | ||||
|     private String csrfToken; | ||||
|     private MediaService mediaService; | ||||
|     private NewsService newsService; | ||||
|     private String userId, csrfToken, type; | ||||
|     private Context context; | ||||
| 
 | ||||
|     private final OnNotificationClickListener clickListener = model -> { | ||||
|         if (model == null) return; | ||||
|         final String username = model.getUsername(); | ||||
|         final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText()))); | ||||
|         title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); | ||||
| 
 | ||||
|         String[] commentDialogList; | ||||
|         if (model.getShortCode() != null) { | ||||
|             commentDialogList = new String[]{ | ||||
|                     getString(R.string.open_profile), | ||||
|                     getString(R.string.view_post) | ||||
|             }; | ||||
|         } else if (model.getType() == NotificationType.REQUEST) { | ||||
|             commentDialogList = new String[]{ | ||||
|                     getString(R.string.open_profile), | ||||
|                     getString(R.string.request_approve), | ||||
|                     getString(R.string.request_reject) | ||||
|             }; | ||||
|         } else { | ||||
|             commentDialogList = new String[]{getString(R.string.open_profile)}; | ||||
|     private final OnNotificationClickListener clickListener = new OnNotificationClickListener() { | ||||
|         @Override | ||||
|         public void onProfileClick(final String username) { | ||||
|             openProfile(username); | ||||
|         } | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { | ||||
|             switch (which) { | ||||
|                 case 0: | ||||
|                     openProfile(model.getUsername()); | ||||
|                     break; | ||||
|                 case 1: | ||||
|                     if (model.getType() == NotificationType.REQUEST) { | ||||
|                         friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||
|                             @Override | ||||
|                             public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||
|                                 // Log.d(TAG, "onSuccess: " + result); | ||||
|                                 if (result.getStatus().equals("ok")) { | ||||
|                                     onRefresh(); | ||||
|                                     return; | ||||
|                                 } | ||||
|                                 Log.e(TAG, "approve: status was not ok!"); | ||||
|                             } | ||||
| 
 | ||||
|                             @Override | ||||
|                             public void onFailure(final Throwable t) { | ||||
|                                 Log.e(TAG, "approve: onFailure: ", t); | ||||
|                             } | ||||
|                         }); | ||||
|                         return; | ||||
|                     } | ||||
|                     final AlertDialog alertDialog = new AlertDialog.Builder(context) | ||||
|                             .setCancelable(false) | ||||
|                             .setView(R.layout.dialog_opening_post) | ||||
|                             .create(); | ||||
|                     alertDialog.show(); | ||||
|                     new PostFetcher(model.getShortCode(), feedModel -> { | ||||
|         @Override | ||||
|         public void onPreviewClick(final NotificationModel model) { | ||||
|             if (model.getType() == NotificationType.RESPONDED_STORY) { | ||||
|                 final NavDirections action = NotificationsViewerFragmentDirections.actionNotificationsViewerFragmentToStoryViewerFragment( | ||||
|                         -1, null, false, false, model.getPostId(), model.getUsername(), false, true); | ||||
|                 NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action); | ||||
|             } | ||||
|             else { | ||||
|                 mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() { | ||||
|                     @Override | ||||
|                     public void onSuccess(final FeedModel feedModel) { | ||||
|                         final PostViewV2Fragment fragment = PostViewV2Fragment | ||||
|                                 .builder(feedModel) | ||||
|                                 .build(); | ||||
|                         fragment.setOnShowListener(dialog1 -> alertDialog.dismiss()); | ||||
|                         fragment.show(getChildFragmentManager(), "post_view"); | ||||
|                     }).execute(); | ||||
|                     break; | ||||
|                 case 2: | ||||
|                     friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||
|                         @Override | ||||
|                         public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||
|                             // Log.d(TAG, "onSuccess: " + result); | ||||
|                             if (result.getStatus().equals("ok")) { | ||||
|                                 onRefresh(); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onFailure(final Throwable t) { | ||||
|                         Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onNotificationClick(final NotificationModel model) { | ||||
|             if (model == null) return; | ||||
|             final String username = model.getUsername(); | ||||
|             if (model.getType() == NotificationType.FOLLOW || model.getType() == NotificationType.AYML) { | ||||
|                 openProfile(username); | ||||
|             } | ||||
|             else { | ||||
|                 final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText()))); | ||||
|                 title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); | ||||
| 
 | ||||
|                 String[] commentDialogList; | ||||
|                 if (model.getType() == NotificationType.RESPONDED_STORY) { | ||||
|                     commentDialogList = new String[]{ | ||||
|                             getString(R.string.open_profile), | ||||
|                             getString(R.string.view_story) | ||||
|                     }; | ||||
|                 } | ||||
|                 else if (model.getPostId() != null) { | ||||
|                     commentDialogList = new String[]{ | ||||
|                             getString(R.string.open_profile), | ||||
|                             getString(R.string.view_post) | ||||
|                     }; | ||||
|                 } | ||||
|                 else if (model.getType() == NotificationType.REQUEST) { | ||||
|                     commentDialogList = new String[]{ | ||||
|                             getString(R.string.open_profile), | ||||
|                             getString(R.string.request_approve), | ||||
|                             getString(R.string.request_reject) | ||||
|                     }; | ||||
|                 } | ||||
|                 else commentDialogList = null; // shouldn't happen | ||||
|                 final Context context = getContext(); | ||||
|                 if (context == null) return; | ||||
|                 final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { | ||||
|                     switch (which) { | ||||
|                         case 0: | ||||
|                             openProfile(username); | ||||
|                             break; | ||||
|                         case 1: | ||||
|                             if (model.getType() == NotificationType.REQUEST) { | ||||
|                                 friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||
|                                     @Override | ||||
|                                     public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||
|                                         onRefresh(); | ||||
|                                         Log.e(TAG, "approve: status was not ok!"); | ||||
|                                     } | ||||
| 
 | ||||
|                                     @Override | ||||
|                                     public void onFailure(final Throwable t) { | ||||
|                                         Log.e(TAG, "approve: onFailure: ", t); | ||||
|                                     } | ||||
|                                 }); | ||||
|                                 return; | ||||
|                             } | ||||
|                             Log.e(TAG, "ignore: status was not ok!"); | ||||
|                         } | ||||
|                             else if (model.getType() == NotificationType.RESPONDED_STORY) { | ||||
|                                 final NavDirections action = NotificationsViewerFragmentDirections.actionNotificationsViewerFragmentToStoryViewerFragment( | ||||
|                                         -1, null, false, false, model.getPostId(), model.getUsername(), false, true); | ||||
|                                 NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action); | ||||
|                                 return; | ||||
|                             } | ||||
|                             final AlertDialog alertDialog = new AlertDialog.Builder(context) | ||||
|                                     .setCancelable(false) | ||||
|                                     .setView(R.layout.dialog_opening_post) | ||||
|                                     .create(); | ||||
|                             alertDialog.show(); | ||||
|                             mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() { | ||||
|                                 @Override | ||||
|                                 public void onSuccess(final FeedModel feedModel) { | ||||
|                                     final PostViewV2Fragment fragment = PostViewV2Fragment | ||||
|                                             .builder(feedModel) | ||||
|                                             .build(); | ||||
|                                     fragment.setOnShowListener(dialog1 -> alertDialog.dismiss()); | ||||
|                                     fragment.show(getChildFragmentManager(), "post_view"); | ||||
|                                 } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onFailure(final Throwable t) { | ||||
|                             Log.e(TAG, "ignore: onFailure: ", t); | ||||
|                         } | ||||
|                     }); | ||||
|                     break; | ||||
|                                 @Override | ||||
|                                 public void onFailure(final Throwable t) { | ||||
|                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                 } | ||||
|                             }); | ||||
|                             break; | ||||
|                         case 2: | ||||
|                             friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||
|                                 @Override | ||||
|                                 public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||
|                                     onRefresh(); | ||||
|                                 } | ||||
| 
 | ||||
|                                 @Override | ||||
|                                 public void onFailure(final Throwable t) { | ||||
|                                     Log.e(TAG, "ignore: onFailure: ", t); | ||||
|                                 } | ||||
|                             }); | ||||
|                             break; | ||||
|                     } | ||||
|                 }; | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setTitle(title) | ||||
|                         .setItems(commentDialogList, profileDialogListener) | ||||
|                         .setNegativeButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             } | ||||
|         }; | ||||
|         new AlertDialog.Builder(context) | ||||
|                 .setTitle(title) | ||||
|                 .setItems(commentDialogList, profileDialogListener) | ||||
|                 .setNegativeButton(R.string.cancel, null) | ||||
|                 .show(); | ||||
|         } | ||||
|     }; | ||||
|     private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> { | ||||
|         if (getContext() == null) return; | ||||
| @ -158,7 +217,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         final Context context = getContext(); | ||||
|         context = getContext(); | ||||
|         if (context == null) return; | ||||
|         NotificationManagerCompat.from(context.getApplicationContext()).cancel(Constants.ACTIVITY_NOTIFICATION_ID); | ||||
|         final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); | ||||
| @ -166,7 +225,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|             Toast.makeText(context, R.string.activity_notloggedin, Toast.LENGTH_SHORT).show(); | ||||
|         } | ||||
|         friendshipService = FriendshipService.getInstance(); | ||||
|         newsService = NewsService.getInstance(); | ||||
|         mediaService = MediaService.getInstance(); | ||||
|         userId = CookieUtils.getUserIdFromCookie(cookie); | ||||
|         csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); | ||||
|     } | ||||
| @ -191,6 +250,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
|         final NotificationsViewerFragmentArgs fragmentArgs = NotificationsViewerFragmentArgs.fromBundle(getArguments()); | ||||
|         type = fragmentArgs.getType(); | ||||
|         final Context context = getContext(); | ||||
|         CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); | ||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||
| @ -205,23 +266,39 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         new NotificationsFetcher(notificationModels -> { | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             notificationViewModel.getList().postValue(notificationModels); | ||||
|             final String timestamp = String.valueOf(System.currentTimeMillis() / 1000); | ||||
|             newsService.markChecked(timestamp, csrfToken, new ServiceCallback<Boolean>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(@NonNull final Boolean result) { | ||||
|                     // Log.d(TAG, "onResponse: body: " + result); | ||||
|                     if (!result) Log.e(TAG, "onSuccess: Error marking activity checked, response is false"); | ||||
|                 } | ||||
|         switch (type) { | ||||
|             case "notif": | ||||
|                 new NotificationsFetcher(true, new FetchListener<List<NotificationModel>>() { | ||||
|                     @Override | ||||
|                     public void onResult(final List<NotificationModel> notificationModels) { | ||||
|                         binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                         notificationViewModel.getList().postValue(notificationModels); | ||||
|                     } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFailure(final Throwable t) { | ||||
|                     Log.e(TAG, "onFailure: Error marking activity checked", t); | ||||
|                 } | ||||
|             }); | ||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|                     @Override | ||||
|                     public void onFailure(Throwable t) { | ||||
|                         binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                         Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                     } | ||||
|                 }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|                 break; | ||||
|             case "ayml": | ||||
|                 newsService = NewsService.getInstance(); | ||||
|                 newsService.fetchSuggestions(csrfToken, new ServiceCallback<List<NotificationModel>>() { | ||||
|                     @Override | ||||
|                     public void onSuccess(final List<NotificationModel> notificationModels) { | ||||
|                         binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                         notificationViewModel.getList().postValue(notificationModels); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onFailure(final Throwable t) { | ||||
|                         binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                         Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                     } | ||||
|                 }); | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void openProfile(final String username) { | ||||
|  | ||||
| @ -26,6 +26,7 @@ import android.view.ViewGroup; | ||||
| import android.view.ViewTreeObserver; | ||||
| import android.view.Window; | ||||
| import android.view.WindowManager; | ||||
| import android.widget.EditText; | ||||
| import android.widget.FrameLayout; | ||||
| import android.widget.ScrollView; | ||||
| import android.widget.Toast; | ||||
| @ -33,6 +34,7 @@ import android.widget.ViewSwitcher; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.constraintlayout.widget.ConstraintLayout; | ||||
| import androidx.core.content.PermissionChecker; | ||||
| import androidx.core.view.ViewCompat; | ||||
| @ -70,6 +72,7 @@ import awais.instagrabber.customviews.VideoPlayerCallbackAdapter; | ||||
| import awais.instagrabber.customviews.VideoPlayerViewHelper; | ||||
| import awais.instagrabber.customviews.drawee.AnimatedZoomableController; | ||||
| import awais.instagrabber.databinding.DialogPostViewBinding; | ||||
| import awais.instagrabber.fragments.main.ProfileFragment; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.PostChild; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| @ -114,6 +117,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
|     private DialogInterface.OnShowListener onShowListener; | ||||
|     private boolean isLoggedIn; | ||||
|     private boolean hasBeenToggled = false; | ||||
|     private CharSequence postCaption = null; | ||||
| 
 | ||||
|     private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() { | ||||
| 
 | ||||
| @ -306,6 +310,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
|     @Override | ||||
|     public void onDestroyView() { | ||||
|         super.onDestroyView(); | ||||
|         if (feedModel == null) return; | ||||
|         switch (feedModel.getItemType()) { | ||||
|             case MEDIA_TYPE_VIDEO: | ||||
|                 if (videoPlayerViewHelper != null) { | ||||
| @ -414,8 +419,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
| 
 | ||||
|     private void init() { | ||||
|         if (feedModel == null) return; | ||||
|         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; | ||||
|         isLoggedIn = !TextUtils.isEmpty(COOKIE) && CookieUtils.getUserIdFromCookie(COOKIE) != null; | ||||
|         if (!wasPaused && (sharedProfilePicElement != null || sharedMainPostElement != null)) { | ||||
|             binding.getRoot().getBackground().mutate().setAlpha(0); | ||||
|         } | ||||
| @ -534,7 +538,16 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
|             } | ||||
|         }); | ||||
|         binding.like.setOnLongClickListener(v -> { | ||||
|             Utils.displayToastAboveView(context, v, getString(R.string.like_without_count)); | ||||
|             final NavController navController = getNavController(); | ||||
|             if (navController != null && isLoggedIn) { | ||||
|                 final Bundle bundle = new Bundle(); | ||||
|                 bundle.putString("postId", feedModel.getPostId()); | ||||
|                 bundle.putBoolean("isComment", false); | ||||
|                 navController.navigate(R.id.action_global_likesViewerFragment, bundle); | ||||
|             } | ||||
|             else { | ||||
|                 Utils.displayToastAboveView(context, v, getString(R.string.like_without_count)); | ||||
|             } | ||||
|             return true; | ||||
|         }); | ||||
|     } | ||||
| @ -701,13 +714,52 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
|     } | ||||
| 
 | ||||
|     private void setupCaption() { | ||||
|         final CharSequence postCaption = feedModel.getPostCaption(); | ||||
|         postCaption = feedModel.getPostCaption(); | ||||
|         binding.date.setText(Utils.datetimeParser.format(new Date(feedModel.getTimestamp() * 1000L))); | ||||
|         if (TextUtils.isEmpty(postCaption)) { | ||||
|         if (!feedModel.getProfileModel().getId().equals(CookieUtils.getUserIdFromCookie(COOKIE)) && TextUtils.isEmpty(postCaption)) { | ||||
|             binding.caption.setVisibility(View.GONE); | ||||
|             binding.translateTitle.setVisibility(View.GONE); | ||||
|             binding.captionToggle.setVisibility(View.GONE); | ||||
|             return; | ||||
|         } | ||||
|         if (feedModel.getProfileModel().getId().equals(CookieUtils.getUserIdFromCookie(COOKIE))) { | ||||
|             binding.editCaption.setVisibility(View.VISIBLE); | ||||
|             binding.editCaption.setOnClickListener(v -> { | ||||
|                 final EditText input = new EditText(context); | ||||
|                 input.setText(postCaption); | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setTitle(R.string.edit_caption) | ||||
|                         .setView(input) | ||||
|                         .setPositiveButton(R.string.confirm, (d, w) -> { | ||||
|                             binding.editCaption.setVisibility(View.GONE); | ||||
|                             mediaService.editCaption( | ||||
|                                     feedModel.getPostId(), | ||||
|                                     CookieUtils.getUserIdFromCookie(COOKIE), | ||||
|                                     input.getText().toString(), | ||||
|                                     CookieUtils.getCsrfTokenFromCookie(COOKIE), | ||||
|                                     new ServiceCallback<Boolean>() { | ||||
|                                         @Override | ||||
|                                         public void onSuccess(final Boolean result) { | ||||
|                                             binding.editCaption.setVisibility(View.VISIBLE); | ||||
|                                             if (result) { | ||||
|                                                 feedModel.setPostCaption(input.getText().toString()); | ||||
|                                                 binding.caption.setText(input.getText().toString()); | ||||
|                                             } | ||||
|                                             else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onFailure(final Throwable t) { | ||||
|                                             Log.e(TAG, "Error editing caption", t); | ||||
|                                             Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                             binding.editCaption.setVisibility(View.VISIBLE); | ||||
|                                         } | ||||
|                                     }); | ||||
|                         }) | ||||
|                         .setNegativeButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             }); | ||||
|         } | ||||
|         binding.caption.addOnHashtagListener(autoLinkItem -> { | ||||
|             final NavController navController = NavHostFragment.findNavController(this); | ||||
|             final Bundle bundle = new Bundle(); | ||||
| @ -737,11 +789,33 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
|                 binding.captionParent.getBackground().mutate().setAlpha((int) (128 + (128 * (slideOffset < 0 ? 0 : slideOffset)))); | ||||
|             } | ||||
|         }); | ||||
|         binding.caption.setOnClickListener(v -> { | ||||
|         binding.captionFrame.setOnClickListener(v -> { | ||||
|             if (bottomSheetBehavior == null) return; | ||||
|             if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) return; | ||||
|             bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); | ||||
|         }); | ||||
|         if (TextUtils.isEmpty(feedModel.getCaptionId())) | ||||
|             binding.translateTitle.setVisibility(View.GONE); | ||||
|         else binding.translateTitle.setOnClickListener(v -> { | ||||
|             mediaService.translate(feedModel.getCaptionId(), "1", new ServiceCallback<String>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final String result) { | ||||
|                     if (TextUtils.isEmpty(result)) { | ||||
|                         Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                         return; | ||||
|                     } | ||||
|                     binding.translateTitle.setOnClickListener(null); | ||||
|                     binding.translatedCaption.setVisibility(View.VISIBLE); | ||||
|                     binding.translatedCaption.setText(result); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFailure(final Throwable t) { | ||||
|                     Log.e(TAG, "Error translating comment", t); | ||||
|                     Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|         binding.captionToggle.setOnClickListener(v -> { | ||||
|             if (bottomSheetBehavior == null) return; | ||||
|             switch (bottomSheetBehavior.getState()) { | ||||
| @ -894,6 +968,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | ||||
| 
 | ||||
|             @Override | ||||
|             public void onItemClicked(final int position) { | ||||
|                 toggleDetails(); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|  | ||||
| @ -40,24 +40,26 @@ import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.PostsLayoutPreferences; | ||||
| import awais.instagrabber.models.enums.PostItemType; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| import static androidx.core.content.PermissionChecker.checkSelfPermission; | ||||
| import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final int STORAGE_PERM_REQUEST_CODE = 8020; | ||||
|     private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; | ||||
| 
 | ||||
|     private FragmentSavedBinding binding; | ||||
|     private String username; | ||||
|     private String username, cookie, profileId; | ||||
|     private ActionMode actionMode; | ||||
|     private SwipeRefreshLayout root; | ||||
|     private AppCompatActivity fragmentActivity; | ||||
|     private boolean shouldRefresh = true; | ||||
|     private boolean isLoggedIn, shouldRefresh = true; | ||||
|     private PostItemType type; | ||||
|     private String profileId; | ||||
|     private Set<FeedModel> selectedFeedModels; | ||||
|     private FeedModel downloadFeedModel; | ||||
|     private int downloadChildPosition = -1; | ||||
| @ -225,6 +227,8 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL | ||||
| 
 | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { | ||||
|         cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; | ||||
|         if (root != null) { | ||||
|             shouldRefresh = false; | ||||
|             return root; | ||||
| @ -281,7 +285,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL | ||||
|     private void setupPosts() { | ||||
|         binding.posts.setViewModelStoreOwner(this) | ||||
|                      .setLifeCycleOwner(this) | ||||
|                      .setPostFetchService(new SavedPostFetchService(profileId, type)) | ||||
|                      .setPostFetchService(new SavedPostFetchService(profileId, type, isLoggedIn)) | ||||
|                      .setLayoutPreferences(layoutPreferences) | ||||
|                      .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) | ||||
|                      .setFeedItemCallback(feedItemCallback) | ||||
|  | ||||
| @ -0,0 +1,220 @@ | ||||
| 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; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.fragment.app.Fragment; | ||||
| import androidx.lifecycle.ViewModelProvider; | ||||
| import androidx.navigation.NavDirections; | ||||
| import androidx.navigation.fragment.NavHostFragment; | ||||
| import androidx.recyclerview.widget.LinearLayoutManager; | ||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.FeedStoriesListAdapter; | ||||
| import awais.instagrabber.adapters.FeedStoriesListAdapter.OnFeedStoryClickListener; | ||||
| import awais.instagrabber.adapters.HighlightStoriesListAdapter; | ||||
| import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener; | ||||
| import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||
| import awais.instagrabber.databinding.FragmentStoryListViewerBinding; | ||||
| import awais.instagrabber.fragments.main.FeedFragment; | ||||
| import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
| import awais.instagrabber.models.HighlightModel; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.viewmodels.FeedStoriesViewModel; | ||||
| import awais.instagrabber.viewmodels.ArchivesViewModel; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awais.instagrabber.webservices.StoriesService; | ||||
| import awais.instagrabber.webservices.StoriesService.ArchiveFetchResponse; | ||||
| 
 | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public final class StoryListViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final String TAG = "StoryListViewerFragment"; | ||||
| 
 | ||||
|     private AppCompatActivity fragmentActivity; | ||||
|     private FragmentStoryListViewerBinding binding; | ||||
|     private SwipeRefreshLayout root; | ||||
|     private boolean shouldRefresh = true, firstRefresh = true; | ||||
|     private FeedStoriesViewModel feedStoriesViewModel; | ||||
|     private ArchivesViewModel archivesViewModel; | ||||
|     private StoriesService storiesService; | ||||
|     private Context context; | ||||
|     private String type, endCursor = null; | ||||
|     private RecyclerLazyLoader lazyLoader; | ||||
|     private FeedStoriesListAdapter adapter; | ||||
| 
 | ||||
|     private final OnFeedStoryClickListener clickListener = new OnFeedStoryClickListener() { | ||||
|         @Override | ||||
|         public void onFeedStoryClick(final FeedStoryModel model, final int position) { | ||||
|             if (model == null) return; | ||||
|             final NavDirections action = StoryListViewerFragmentDirections.actionStoryListFragmentToStoryViewerFragment(position, null, false, false, null, null, false, false); | ||||
|             NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onProfileClick(final String username) { | ||||
|             openProfile(username); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     private final OnHighlightStoryClickListener archiveClickListener = new OnHighlightStoryClickListener() { | ||||
|         @Override | ||||
|         public void onHighlightClick(final HighlightModel model, final int position) { | ||||
|             if (model == null) return; | ||||
|             final NavDirections action = StoryListViewerFragmentDirections.actionStoryListFragmentToStoryViewerFragment( | ||||
|                     position, getString(R.string.action_archive), false, false, null, null, true, false); | ||||
|             NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onProfileClick(final String username) { | ||||
|             openProfile(username); | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     private final ServiceCallback<ArchiveFetchResponse> cb = new ServiceCallback<ArchiveFetchResponse>() { | ||||
|         @Override | ||||
|         public void onSuccess(final ArchiveFetchResponse result) { | ||||
|             endCursor = result.getNextCursor(); | ||||
|             final List<HighlightModel> models = archivesViewModel.getList().getValue(); | ||||
|             final List<HighlightModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); | ||||
|             modelsCopy.addAll(result.getResult()); | ||||
|             archivesViewModel.getList().postValue(modelsCopy); | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public void onFailure(final Throwable t) { | ||||
|             Log.e(TAG, "Error", t); | ||||
|             try { | ||||
|                 final Context context = getContext(); | ||||
|                 Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|             } | ||||
|             catch (Exception e) {} | ||||
|         } | ||||
|     }; | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         fragmentActivity = (AppCompatActivity) requireActivity(); | ||||
|         context = getContext(); | ||||
|         if (context == null) return; | ||||
|         storiesService = StoriesService.getInstance(); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { | ||||
|         if (root != null) { | ||||
|             shouldRefresh = false; | ||||
|             return root; | ||||
|         } | ||||
|         binding = FragmentStoryListViewerBinding.inflate(getLayoutInflater()); | ||||
|         root = binding.getRoot(); | ||||
|         return root; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { | ||||
|         if (!shouldRefresh) return; | ||||
|         init(); | ||||
|         shouldRefresh = false; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (actionBar != null) actionBar.setTitle(type == "feed" ? R.string.feed_stories : R.string.action_archive); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onDestroy() { | ||||
|         if (archivesViewModel != null) archivesViewModel.getList().postValue(null); | ||||
|         super.onDestroy(); | ||||
|     } | ||||
| 
 | ||||
|     private void init() { | ||||
|         final Context context = getContext(); | ||||
|         if (getArguments() == null) return; | ||||
|         final StoryListViewerFragmentArgs fragmentArgs = StoryListViewerFragmentArgs.fromBundle(getArguments()); | ||||
|         type = fragmentArgs.getType(); | ||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||
|         final LinearLayoutManager layoutManager = new LinearLayoutManager(context); | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (type == "feed") { | ||||
|             if (actionBar != null) actionBar.setTitle(R.string.feed_stories); | ||||
|             feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); | ||||
|             adapter = new FeedStoriesListAdapter(clickListener); | ||||
|             binding.rvStories.setLayoutManager(layoutManager); | ||||
|             binding.rvStories.setAdapter(adapter); | ||||
|             feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); | ||||
|         } | ||||
|         else { | ||||
|             if (actionBar != null) actionBar.setTitle(R.string.action_archive); | ||||
|             lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { | ||||
|                 if (!TextUtils.isEmpty(endCursor)) onRefresh(); | ||||
|                 endCursor = null; | ||||
|             }); | ||||
|             binding.rvStories.addOnScrollListener(lazyLoader); | ||||
|             archivesViewModel = new ViewModelProvider(fragmentActivity).get(ArchivesViewModel.class); | ||||
|             final HighlightStoriesListAdapter adapter = new HighlightStoriesListAdapter(archiveClickListener); | ||||
|             binding.rvStories.setLayoutManager(layoutManager); | ||||
|             binding.rvStories.setAdapter(adapter); | ||||
|             archivesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); | ||||
|         } | ||||
|         onRefresh(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         binding.swipeRefreshLayout.setRefreshing(true); | ||||
|         if (type == "feed" && firstRefresh) { | ||||
|             binding.swipeRefreshLayout.setRefreshing(false); | ||||
|             adapter.submitList(feedStoriesViewModel.getList().getValue()); | ||||
|             firstRefresh = false; | ||||
|         } | ||||
|         else if (type == "feed") { | ||||
|             final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|             storiesService.getFeedStories(CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<List<FeedStoryModel>>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final List<FeedStoryModel> result) { | ||||
|                     feedStoriesViewModel.getList().postValue(result); | ||||
|                     binding.swipeRefreshLayout.setRefreshing(false); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFailure(final Throwable t) { | ||||
|                     Log.e(TAG, "failed", t); | ||||
|                     Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|         else if (type == "archive") { | ||||
|             storiesService.fetchArchive(endCursor, cb); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void openProfile(final String username) { | ||||
|         final NavDirections action = MorePreferencesFragmentDirections | ||||
|                 .actionGlobalProfileFragment("@" + username); | ||||
|         NavHostFragment.findNavController(this).navigate(action); | ||||
|     } | ||||
| } | ||||
| @ -11,6 +11,7 @@ import android.os.Handler; | ||||
| import android.util.Log; | ||||
| import android.util.Pair; | ||||
| import android.view.GestureDetector; | ||||
| import android.view.Gravity; | ||||
| import android.view.LayoutInflater; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuInflater; | ||||
| @ -20,6 +21,9 @@ import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.ArrayAdapter; | ||||
| import android.widget.EditText; | ||||
| import android.widget.LinearLayout; | ||||
| import android.widget.SeekBar; | ||||
| import android.widget.TextView; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| @ -54,6 +58,9 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource; | ||||
| import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.UnsupportedEncodingException; | ||||
| import java.text.NumberFormat; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.Date; | ||||
| import java.util.List; | ||||
| @ -62,10 +69,7 @@ import awais.instagrabber.BuildConfig; | ||||
| import awais.instagrabber.R; | ||||
| import awais.instagrabber.adapters.StoriesAdapter; | ||||
| import awais.instagrabber.asyncs.PostFetcher; | ||||
| import awais.instagrabber.asyncs.QuizAction; | ||||
| import awais.instagrabber.asyncs.RespondAction; | ||||
| import awais.instagrabber.asyncs.SeenAction; | ||||
| import awais.instagrabber.asyncs.VoteAction; | ||||
| import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; | ||||
| import awais.instagrabber.customviews.helpers.SwipeGestureListener; | ||||
| import awais.instagrabber.databinding.FragmentStoryViewerBinding; | ||||
| @ -78,16 +82,27 @@ import awais.instagrabber.models.enums.MediaItemType; | ||||
| import awais.instagrabber.models.stickers.PollModel; | ||||
| import awais.instagrabber.models.stickers.QuestionModel; | ||||
| import awais.instagrabber.models.stickers.QuizModel; | ||||
| import awais.instagrabber.models.stickers.SliderModel; | ||||
| import awais.instagrabber.models.stickers.SwipeUpModel; | ||||
| import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions; | ||||
| import awais.instagrabber.repositories.responses.StoryStickerResponse; | ||||
| import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.viewmodels.ArchivesViewModel; | ||||
| import awais.instagrabber.viewmodels.FeedStoriesViewModel; | ||||
| import awais.instagrabber.viewmodels.HighlightsViewModel; | ||||
| import awais.instagrabber.viewmodels.StoriesViewModel; | ||||
| import awais.instagrabber.webservices.DirectMessagesService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awais.instagrabber.webservices.StoriesService; | ||||
| import awaisomereport.LogCollector; | ||||
| import retrofit2.Call; | ||||
| import retrofit2.Callback; | ||||
| import retrofit2.Response; | ||||
| 
 | ||||
| import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD; | ||||
| import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD; | ||||
| @ -100,8 +115,7 @@ public class StoryViewerFragment extends Fragment { | ||||
| 
 | ||||
|     private AppCompatActivity fragmentActivity; | ||||
|     private View root; | ||||
|     private @NonNull | ||||
|     FragmentStoryViewerBinding binding; | ||||
|     private FragmentStoryViewerBinding binding; | ||||
|     private String currentStoryUsername; | ||||
|     private StoriesAdapter storiesAdapter; | ||||
|     private SwipeEvent swipeEvent; | ||||
| @ -115,26 +129,32 @@ public class StoryViewerFragment extends Fragment { | ||||
|     private QuestionModel question; | ||||
|     private String[] mentions; | ||||
|     private QuizModel quiz; | ||||
|     private SliderModel slider; | ||||
|     private MenuItem menuDownload; | ||||
|     private MenuItem menuDm; | ||||
|     private SimpleExoPlayer player; | ||||
|     private boolean isHashtag, isLoc; | ||||
|     private String highlight; | ||||
|     private boolean fetching = false; | ||||
|     private boolean fetching = false, sticking = false, shouldRefresh = true; | ||||
|     private int currentFeedStoryIndex; | ||||
|     private double sliderValue; | ||||
|     private StoriesViewModel storiesViewModel; | ||||
|     private boolean shouldRefresh = true; | ||||
|     private StoryViewerFragmentArgs fragmentArgs; | ||||
|     private ViewModel viewModel; | ||||
|     private boolean isHighlight; | ||||
|     private boolean isHighlight, isArchive, isNotification; | ||||
|     private DirectMessagesService directMessagesService; | ||||
| 
 | ||||
|     private final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|     private final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); | ||||
|     private final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); | ||||
|     private final String deviceId = settingsHelper.getString(Constants.DEVICE_UUID); | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         fragmentActivity = (AppCompatActivity) requireActivity(); | ||||
|         storiesService = StoriesService.getInstance(); | ||||
|         directMessagesService = DirectMessagesService.getInstance(csrfToken, userIdFromCookie, deviceId); | ||||
|         setHasOptionsMenu(true); | ||||
|     } | ||||
| 
 | ||||
| @ -188,24 +208,33 @@ public class StoryViewerFragment extends Fragment { | ||||
|             new AlertDialog.Builder(context) | ||||
|                     .setTitle(R.string.reply_story) | ||||
|                     .setView(input) | ||||
|                     .setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> { | ||||
|                         // try { | ||||
|                         // final StoryReplyBroadcastOptions options = new StoryReplyBroadcastOptions( | ||||
|                         //         threadId, | ||||
|                         //         input.getText().toString(), | ||||
|                         //         currentStory.getStoryMediaId(), | ||||
|                         //         currentStory.getUserId() | ||||
|                         // ); | ||||
|                         // final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadId); | ||||
|                         // broadcast.setOnTaskCompleteListener(result -> Toast.makeText( | ||||
|                         //         context, | ||||
|                         //         result != null ? R.string.answered_story : R.string.downloader_unknown_error, | ||||
|                         //         Toast.LENGTH_SHORT | ||||
|                         // ).show()); | ||||
|                         // broadcast.execute(options); | ||||
|                         // } catch (UnsupportedEncodingException e) { | ||||
|                         //     Log.e(TAG, "Error", e); | ||||
|                         // } | ||||
|                     .setPositiveButton(R.string.confirm, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> { | ||||
|                         try { | ||||
|                             final Call<DirectThreadBroadcastResponse> request = directMessagesService | ||||
|                                     .broadcastStoryReply(BroadcastOptions.ThreadIdOrUserIds.of(threadId), | ||||
|                                                          input.getText().toString(), | ||||
|                                                          currentStory.getStoryMediaId(), | ||||
|                                                          currentStory.getUserId()); | ||||
|                             request.enqueue(new Callback<DirectThreadBroadcastResponse>() { | ||||
|                                 @Override | ||||
|                                 public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call, | ||||
|                                                        @NonNull final Response<DirectThreadBroadcastResponse> response) { | ||||
|                                     if (!response.isSuccessful()) { | ||||
|                                         Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                         return; | ||||
|                                     } | ||||
|                                     Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||
|                                 } | ||||
| 
 | ||||
|                                 @Override | ||||
|                                 public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) { | ||||
|                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                     Log.e(TAG, "onFailure: ", t); | ||||
|                                 } | ||||
|                             }); | ||||
|                         } catch (UnsupportedEncodingException e) { | ||||
|                             Log.e(TAG, "Error", e); | ||||
|                         } | ||||
|                     }).execute()) | ||||
|                     .setNegativeButton(R.string.cancel, null) | ||||
|                     .show(); | ||||
| @ -244,9 +273,13 @@ public class StoryViewerFragment extends Fragment { | ||||
|         currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex(); | ||||
|         highlight = fragmentArgs.getHighlight(); | ||||
|         isHighlight = !TextUtils.isEmpty(highlight); | ||||
|         isArchive = fragmentArgs.getIsArchive(); | ||||
|         isNotification = fragmentArgs.getIsNotification(); | ||||
|         if (currentFeedStoryIndex >= 0) { | ||||
|             viewModel = isHighlight | ||||
|                         ? new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class) | ||||
|                         ? isArchive | ||||
|                           ? new ViewModelProvider(fragmentActivity).get(ArchivesViewModel.class) | ||||
|                           : new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class) | ||||
|                         : new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); | ||||
|         } | ||||
|         // feedStoryModels = feedStoriesViewModel.getList().getValue(); | ||||
| @ -275,7 +308,10 @@ public class StoryViewerFragment extends Fragment { | ||||
|         final boolean hasFeedStories; | ||||
|         List<?> models = null; | ||||
|         if (currentFeedStoryIndex >= 0) { | ||||
|             if (isHighlight) { | ||||
|             if (isArchive) { | ||||
|                 final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel; | ||||
|                 models = archivesViewModel.getList().getValue(); | ||||
|             } else if (isHighlight) { | ||||
|                 final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; | ||||
|                 models = highlightsViewModel.getList().getValue(); | ||||
|                 // final HighlightModel model = models.get(currentFeedStoryIndex); | ||||
| @ -296,15 +332,16 @@ public class StoryViewerFragment extends Fragment { | ||||
|         swipeEvent = isRightSwipe -> { | ||||
|             final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); | ||||
|             final int storiesLen = storyModels == null ? 0 : storyModels.size(); | ||||
|             if (sticking) { | ||||
|                 Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_SHORT).show(); | ||||
|                 return; | ||||
|             } | ||||
|             if (storiesLen <= 0) return; | ||||
|             final boolean isLeftSwipe = !isRightSwipe; | ||||
|             final boolean endOfCurrentStories = slidePos + 1 >= storiesLen; | ||||
|             final boolean swipingBeyondCurrentStories = (endOfCurrentStories && isLeftSwipe) || (slidePos == 0 && isRightSwipe); | ||||
|             if (swipingBeyondCurrentStories && hasFeedStories) { | ||||
|                 final int index = currentFeedStoryIndex; | ||||
|                 if (settingsHelper.getBoolean(MARK_AS_SEEN)) { | ||||
|                     new SeenAction(cookie, currentStory).execute(); | ||||
|                 } | ||||
|                 if ((isRightSwipe && index == 0) || (isLeftSwipe && index == finalModels.size() - 1)) { | ||||
|                     Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show(); | ||||
|                     return; | ||||
| @ -312,7 +349,7 @@ public class StoryViewerFragment extends Fragment { | ||||
|                 final Object feedStoryModel = isRightSwipe | ||||
|                                               ? finalModels.get(index - 1) | ||||
|                                               : finalModels.size() == index + 1 ? null : finalModels.get(index + 1); | ||||
|                 paginateStories(feedStoryModel, context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2); | ||||
|                 paginateStories(feedStoryModel, finalModels.get(index), context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2); | ||||
|                 return; | ||||
|             } | ||||
|             if (isRightSwipe) { | ||||
| @ -351,8 +388,12 @@ public class StoryViewerFragment extends Fragment { | ||||
|         if (hasFeedStories) { | ||||
|             binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE); | ||||
|             binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? View.INVISIBLE : View.VISIBLE); | ||||
|             binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), context, true, false)); | ||||
|             binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), context, false, | ||||
|             binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), | ||||
|                                                                         finalModels.get(currentFeedStoryIndex), | ||||
|                                                                         context, true, false)); | ||||
|             binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), | ||||
|                                                                        finalModels.get(currentFeedStoryIndex), | ||||
|                                                                        context, false, | ||||
|                                                                        currentFeedStoryIndex == finalModels.size() - 2)); | ||||
|         } | ||||
| 
 | ||||
| @ -365,6 +406,14 @@ public class StoryViewerFragment extends Fragment { | ||||
|                 startActivity(intent); | ||||
|             } | ||||
|         }); | ||||
|         binding.swipeUp.setOnClickListener(v -> { | ||||
|             final Object tag = v.getTag(); | ||||
|             if (tag instanceof CharSequence) { | ||||
|                 final Intent intent = new Intent(Intent.ACTION_VIEW); | ||||
|                 intent.setData(Uri.parse(tag.toString())); | ||||
|                 startActivity(intent); | ||||
|             } | ||||
|         }); | ||||
|         binding.viewStoryPost.setOnClickListener(v -> { | ||||
|             final Object tag = v.getTag(); | ||||
|             if (!(tag instanceof CharSequence)) return; | ||||
| @ -406,15 +455,30 @@ public class StoryViewerFragment extends Fragment { | ||||
|                                     poll.getLeftChoice() + " (" + poll.getLeftCount() + ")", | ||||
|                                     poll.getRightChoice() + " (" + poll.getRightCount() + ")" | ||||
|                             }), (d, w) -> { | ||||
|                                 if (!TextUtils.isEmpty(cookie)) | ||||
|                                     new VoteAction(currentStory, poll, cookie, choice -> { | ||||
|                                         if (choice > -1) { | ||||
|                                             poll.setMyChoice(choice); | ||||
|                                             Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); | ||||
|                                             return; | ||||
|                                         } | ||||
|                                         Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                     }).execute(w); | ||||
|                                 if (!TextUtils.isEmpty(cookie)) { | ||||
|                                     sticking = true; | ||||
|                                     storiesService.respondToPoll( | ||||
|                                             currentStory.getStoryMediaId().split("_")[0], | ||||
|                                             poll.getId(), | ||||
|                                             w, | ||||
|                                             userIdFromCookie, | ||||
|                                             csrfToken, | ||||
|                                             new ServiceCallback<StoryStickerResponse>() { | ||||
|                                                 @Override | ||||
|                                                 public void onSuccess(final StoryStickerResponse result) { | ||||
|                                                     sticking = false; | ||||
|                                                     poll.setMyChoice(w); | ||||
|                                                     Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); | ||||
|                                                 } | ||||
| 
 | ||||
|                                                 @Override | ||||
|                                                 public void onFailure(final Throwable t) { | ||||
|                                                     sticking = false; | ||||
|                                                     Log.e(TAG, "Error responding", t); | ||||
|                                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                                 } | ||||
|                                             }); | ||||
|                                 } | ||||
|                             }) | ||||
|                             .setPositiveButton(R.string.cancel, null) | ||||
|                             .show(); | ||||
| @ -426,21 +490,36 @@ public class StoryViewerFragment extends Fragment { | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setTitle(question.getQuestion()) | ||||
|                         .setView(input) | ||||
|                         .setPositiveButton(R.string.ok, (d, w) -> new RespondAction(currentStory, question, cookie, result -> { | ||||
|                             if (result) { | ||||
|                                 Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||
|                             } else | ||||
|                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                         }).execute(input.getText().toString())) | ||||
|                         .setPositiveButton(R.string.confirm, (d, w) -> { | ||||
|                             sticking = true; | ||||
|                             storiesService.respondToQuestion( | ||||
|                                     currentStory.getStoryMediaId().split("_")[0], | ||||
|                                     question.getId(), | ||||
|                                     input.getText().toString(), | ||||
|                                     userIdFromCookie, | ||||
|                                     csrfToken, | ||||
|                                     new ServiceCallback<StoryStickerResponse>() { | ||||
|                                         @Override | ||||
|                                         public void onSuccess(final StoryStickerResponse result) { | ||||
|                                             sticking = false; | ||||
|                                             Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onFailure(final Throwable t) { | ||||
|                                             sticking = false; | ||||
|                                             Log.e(TAG, "Error responding", t); | ||||
|                                             Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                         } | ||||
|                                     }); | ||||
|                         }) | ||||
|                         .setNegativeButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             } else if (tag instanceof String[]) { | ||||
|                 mentions = (String[]) tag; | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setTitle(R.string.story_mentions) | ||||
|                         .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> { | ||||
|                             openProfile(mentions[w]); | ||||
|                         }) | ||||
|                         .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> openProfile(mentions[w])) | ||||
|                         .setPositiveButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             } else if (tag instanceof QuizModel) { | ||||
| @ -451,27 +530,122 @@ public class StoryViewerFragment extends Fragment { | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion()) | ||||
|                         .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices), (d, w) -> { | ||||
|                             if (quiz.getMyChoice() == -1 && !TextUtils.isEmpty(cookie)) | ||||
|                                 new QuizAction(currentStory, quiz, cookie, choice -> { | ||||
|                                     if (choice > -1) { | ||||
|                                         quiz.setMyChoice(choice); | ||||
|                                         Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||
|                                         return; | ||||
|                                     } | ||||
|                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                 }).execute(w); | ||||
|                             if (quiz.getMyChoice() == -1 && !TextUtils.isEmpty(cookie)) { | ||||
|                                 sticking = true; | ||||
|                                 storiesService.respondToQuiz( | ||||
|                                         currentStory.getStoryMediaId().split("_")[0], | ||||
|                                         quiz.getId(), | ||||
|                                         w, | ||||
|                                         userIdFromCookie, | ||||
|                                         csrfToken, | ||||
|                                         new ServiceCallback<StoryStickerResponse>() { | ||||
|                                             @Override | ||||
|                                             public void onSuccess(final StoryStickerResponse result) { | ||||
|                                                 sticking = false; | ||||
|                                                 quiz.setMyChoice(w); | ||||
|                                                 Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||
|                                             } | ||||
| 
 | ||||
|                                             @Override | ||||
|                                             public void onFailure(final Throwable t) { | ||||
|                                                 sticking = false; | ||||
|                                                 Log.e(TAG, "Error responding", t); | ||||
|                                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                             } | ||||
|                                         }); | ||||
|                             } | ||||
|                         }) | ||||
|                         .setPositiveButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             } else if (tag instanceof SliderModel) { | ||||
|                 slider = (SliderModel) tag; | ||||
|                 NumberFormat percentage = NumberFormat.getPercentInstance(); | ||||
|                 percentage.setMaximumFractionDigits(2); | ||||
|                 LinearLayout sliderView = new LinearLayout(context); | ||||
|                 sliderView.setLayoutParams(new LinearLayout.LayoutParams( | ||||
|                         LinearLayout.LayoutParams.MATCH_PARENT, | ||||
|                         LinearLayout.LayoutParams.WRAP_CONTENT)); | ||||
|                 sliderView.setOrientation(LinearLayout.VERTICAL); | ||||
|                 TextView tv = new TextView(context); | ||||
|                 tv.setGravity(Gravity.CENTER_HORIZONTAL); | ||||
|                 final SeekBar input = new SeekBar(context); | ||||
|                 double avg = slider.getAverage() * 100; | ||||
|                 input.setProgress((int) avg); | ||||
|                 sliderView.addView(input); | ||||
|                 sliderView.addView(tv); | ||||
|                 if (slider.getMyChoice().isNaN() && slider.canVote()) { | ||||
|                     input.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { | ||||
|                         @Override | ||||
|                         public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { | ||||
|                             sliderValue = progress / 100.0; | ||||
|                             tv.setText(percentage.format(sliderValue)); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onStartTrackingTouch(SeekBar seekBar) { | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onStopTrackingTouch(SeekBar seekBar) { | ||||
|                         } | ||||
|                     }); | ||||
|                     new AlertDialog.Builder(context) | ||||
|                             .setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion()) | ||||
|                             .setMessage(getResources().getQuantityString(R.plurals.slider_info, | ||||
|                                                                          slider.getVoteCount(), | ||||
|                                                                          slider.getVoteCount(), | ||||
|                                                                          percentage.format(slider.getAverage()))) | ||||
|                             .setView(sliderView) | ||||
|                             .setPositiveButton(R.string.confirm, (d, w) -> { | ||||
|                                 sticking = true; | ||||
|                                 storiesService.respondToSlider( | ||||
|                                         currentStory.getStoryMediaId().split("_")[0], | ||||
|                                         slider.getId(), | ||||
|                                         sliderValue, | ||||
|                                         userIdFromCookie, | ||||
|                                         csrfToken, | ||||
|                                         new ServiceCallback<StoryStickerResponse>() { | ||||
|                                             @Override | ||||
|                                             public void onSuccess(final StoryStickerResponse result) { | ||||
|                                                 sticking = false; | ||||
|                                                 slider.setMyChoice(sliderValue); | ||||
|                                                 Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||
|                                             } | ||||
| 
 | ||||
|                                             @Override | ||||
|                                             public void onFailure(final Throwable t) { | ||||
|                                                 sticking = false; | ||||
|                                                 Log.e(TAG, "Error responding", t); | ||||
|                                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                             } | ||||
|                                         }); | ||||
|                             }) | ||||
|                             .setNegativeButton(R.string.cancel, null) | ||||
|                             .show(); | ||||
|                 } else { | ||||
|                     input.setEnabled(false); | ||||
|                     tv.setText(getString(R.string.slider_answer, percentage.format(slider.getMyChoice()))); | ||||
|                     new AlertDialog.Builder(context) | ||||
|                             .setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion()) | ||||
|                             .setMessage(getResources().getQuantityString(R.plurals.slider_info, | ||||
|                                                                          slider.getVoteCount(), | ||||
|                                                                          slider.getVoteCount(), | ||||
|                                                                          percentage.format(slider.getAverage()))) | ||||
|                             .setView(sliderView) | ||||
|                             .setPositiveButton(R.string.ok, null) | ||||
|                             .show(); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|         binding.poll.setOnClickListener(storyActionListener); | ||||
|         binding.answer.setOnClickListener(storyActionListener); | ||||
|         binding.mention.setOnClickListener(storyActionListener); | ||||
|         binding.quiz.setOnClickListener(storyActionListener); | ||||
|         binding.slider.setOnClickListener(storyActionListener); | ||||
|     } | ||||
| 
 | ||||
|     private void resetView() { | ||||
|         final Context context = getContext(); | ||||
|         slidePos = 0; | ||||
|         lastSlidePos = 0; | ||||
|         if (menuDownload != null) menuDownload.setVisible(false); | ||||
| @ -480,10 +654,23 @@ public class StoryViewerFragment extends Fragment { | ||||
|         releasePlayer(); | ||||
|         String currentStoryMediaId = null; | ||||
|         if (currentFeedStoryIndex >= 0) { | ||||
|             if (isHighlight) { | ||||
|             if (isArchive) { | ||||
|                 final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel; | ||||
|                 final List<HighlightModel> models = archivesViewModel.getList().getValue(); | ||||
|                 if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) { | ||||
|                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                     return; | ||||
|                 } | ||||
|                 final HighlightModel model = models.get(currentFeedStoryIndex); | ||||
|                 currentStoryMediaId = model.getId(); | ||||
|                 currentStoryUsername = model.getTitle(); | ||||
|             } else if (isHighlight) { | ||||
|                 final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; | ||||
|                 final List<HighlightModel> models = highlightsViewModel.getList().getValue(); | ||||
|                 if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) return; | ||||
|                 if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) { | ||||
|                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                     return; | ||||
|                 } | ||||
|                 final HighlightModel model = models.get(currentFeedStoryIndex); | ||||
|                 currentStoryMediaId = model.getId(); | ||||
|                 currentStoryUsername = model.getTitle(); | ||||
| @ -502,7 +689,12 @@ public class StoryViewerFragment extends Fragment { | ||||
|         isHashtag = fragmentArgs.getIsHashtag(); | ||||
|         isLoc = fragmentArgs.getIsLoc(); | ||||
|         final boolean hasUsername = !TextUtils.isEmpty(currentStoryUsername); | ||||
|         if (hasUsername) { | ||||
|         if (isHighlight) { | ||||
|             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|             if (actionBar != null) { | ||||
|                 actionBar.setTitle(highlight); | ||||
|             } | ||||
|         } else if (hasUsername) { | ||||
|             currentStoryUsername = currentStoryUsername.replace("@", ""); | ||||
|             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|             if (actionBar != null) { | ||||
| @ -511,6 +703,31 @@ public class StoryViewerFragment extends Fragment { | ||||
|         } | ||||
|         storiesViewModel.getList().setValue(Collections.emptyList()); | ||||
|         if (currentStoryMediaId == null) return; | ||||
|         if (isNotification) { | ||||
|             storiesService.fetch(currentStoryMediaId, new ServiceCallback<StoryModel>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final StoryModel storyModel) { | ||||
|                     fetching = false; | ||||
|                     binding.storiesList.setVisibility(View.GONE); | ||||
|                     if (storyModel == null) { | ||||
|                         storiesViewModel.getList().setValue(Collections.emptyList()); | ||||
|                         currentStory = null; | ||||
|                         return; | ||||
|                     } | ||||
|                     storiesViewModel.getList().setValue(Collections.singletonList(storyModel)); | ||||
|                     currentStory = storyModel; | ||||
|                     refreshStory(); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFailure(final Throwable t) { | ||||
|                     final Context context = getContext(); | ||||
|                     Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                     Log.e(TAG, "Error", t); | ||||
|                 } | ||||
|             }); | ||||
|             return; | ||||
|         } | ||||
|         final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() { | ||||
|             @Override | ||||
|             public void onSuccess(final List<StoryModel> storyModels) { | ||||
| @ -521,7 +738,9 @@ public class StoryViewerFragment extends Fragment { | ||||
|                     binding.storiesList.setVisibility(View.GONE); | ||||
|                     return; | ||||
|                 } | ||||
|                 binding.storiesList.setVisibility(View.VISIBLE); | ||||
|                 binding.storiesList.setVisibility((storyModels.size() == 1 && currentFeedStoryIndex == -1) ? View.GONE : View.VISIBLE); | ||||
|                 binding.btnBackward.setVisibility(currentFeedStoryIndex == -1 ? View.GONE : View.VISIBLE); | ||||
|                 binding.btnForward.setVisibility(currentFeedStoryIndex == -1 ? View.GONE : View.VISIBLE); | ||||
|                 storiesViewModel.getList().setValue(storyModels); | ||||
|                 currentStory = storyModels.get(0); | ||||
|                 refreshStory(); | ||||
| @ -529,6 +748,8 @@ public class StoryViewerFragment extends Fragment { | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(final Throwable t) { | ||||
|                 final Context context = getContext(); | ||||
|                 Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                 Log.e(TAG, "Error", t); | ||||
|             } | ||||
|         }; | ||||
| @ -543,7 +764,7 @@ public class StoryViewerFragment extends Fragment { | ||||
|     private void refreshStory() { | ||||
|         if (binding.storiesList.getVisibility() == View.VISIBLE) { | ||||
|             final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); | ||||
|             if (storyModels != null) { | ||||
|             if (storyModels != null && storyModels.size() > 0) { | ||||
|                 StoryModel item = storyModels.get(lastSlidePos); | ||||
|                 if (item != null) { | ||||
|                     item.setCurrentSlide(false); | ||||
| @ -587,6 +808,17 @@ public class StoryViewerFragment extends Fragment { | ||||
|         binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE); | ||||
|         binding.quiz.setTag(quiz); | ||||
| 
 | ||||
|         slider = currentStory.getSlider(); | ||||
|         binding.slider.setVisibility(slider != null ? View.VISIBLE : View.GONE); | ||||
|         binding.slider.setTag(slider); | ||||
| 
 | ||||
|         final SwipeUpModel swipeUp = currentStory.getSwipeUp(); | ||||
|         if (swipeUp != null) { | ||||
|             binding.swipeUp.setVisibility(View.VISIBLE); | ||||
|             binding.swipeUp.setText(swipeUp.getText()); | ||||
|             binding.swipeUp.setTag(swipeUp.getUrl()); | ||||
|         } | ||||
| 
 | ||||
|         releasePlayer(); | ||||
|         if (isHashtag || isLoc) { | ||||
|             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
| @ -670,8 +902,8 @@ public class StoryViewerFragment extends Fragment { | ||||
|             @Override | ||||
|             public void onLoadCompleted(final int windowIndex, | ||||
|                                         @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||
|                                         final LoadEventInfo loadEventInfo, | ||||
|                                         final MediaLoadData mediaLoadData) { | ||||
|                                         @NonNull final LoadEventInfo loadEventInfo, | ||||
|                                         @NonNull final MediaLoadData mediaLoadData) { | ||||
|                 if (menuDownload != null) menuDownload.setVisible(true); | ||||
|                 if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) | ||||
|                     menuDm.setVisible(true); | ||||
| @ -681,8 +913,8 @@ public class StoryViewerFragment extends Fragment { | ||||
|             @Override | ||||
|             public void onLoadStarted(final int windowIndex, | ||||
|                                       @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||
|                                       final LoadEventInfo loadEventInfo, | ||||
|                                       final MediaLoadData mediaLoadData) { | ||||
|                                       @NonNull final LoadEventInfo loadEventInfo, | ||||
|                                       @NonNull final MediaLoadData mediaLoadData) { | ||||
|                 if (menuDownload != null) menuDownload.setVisible(true); | ||||
|                 if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) | ||||
|                     menuDm.setVisible(true); | ||||
| @ -692,17 +924,17 @@ public class StoryViewerFragment extends Fragment { | ||||
|             @Override | ||||
|             public void onLoadCanceled(final int windowIndex, | ||||
|                                        @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||
|                                        final LoadEventInfo loadEventInfo, | ||||
|                                        final MediaLoadData mediaLoadData) { | ||||
|                                        @NonNull final LoadEventInfo loadEventInfo, | ||||
|                                        @NonNull final MediaLoadData mediaLoadData) { | ||||
|                 binding.progressView.setVisibility(View.GONE); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onLoadError(final int windowIndex, | ||||
|                                     @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||
|                                     final LoadEventInfo loadEventInfo, | ||||
|                                     final MediaLoadData mediaLoadData, | ||||
|                                     final IOException error, | ||||
|                                     @NonNull final LoadEventInfo loadEventInfo, | ||||
|                                     @NonNull final MediaLoadData mediaLoadData, | ||||
|                                     @NonNull final IOException error, | ||||
|                                     final boolean wasCanceled) { | ||||
|                 if (menuDownload != null) menuDownload.setVisible(false); | ||||
|                 if (menuDm != null) menuDm.setVisible(false); | ||||
| @ -745,12 +977,25 @@ public class StoryViewerFragment extends Fragment { | ||||
|         player = null; | ||||
|     } | ||||
| 
 | ||||
|     private void paginateStories(Object feedStory, Context context, boolean backward, boolean last) { | ||||
|         if (feedStory != null) { | ||||
|     private void paginateStories(Object newFeedStory, Object oldFeedStory, Context context, boolean backward, boolean last) { | ||||
|         if (newFeedStory != null) { | ||||
|             if (fetching) { | ||||
|                 Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show(); | ||||
|                 return; | ||||
|             } | ||||
|             if (settingsHelper.getBoolean(MARK_AS_SEEN) | ||||
|                     && oldFeedStory instanceof FeedStoryModel | ||||
|                     && viewModel instanceof FeedStoriesViewModel) { | ||||
|                 final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel; | ||||
|                 final FeedStoryModel oldFeedStoryModel = (FeedStoryModel) oldFeedStory; | ||||
|                 if (!oldFeedStoryModel.isFullyRead()) { | ||||
|                     oldFeedStoryModel.setFullyRead(true); | ||||
|                     final List<FeedStoryModel> models = feedStoriesViewModel.getList().getValue(); | ||||
|                     final List<FeedStoryModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); | ||||
|                     modelsCopy.set(currentFeedStoryIndex, oldFeedStoryModel); | ||||
|                     feedStoriesViewModel.getList().postValue(models); | ||||
|                 } | ||||
|             } | ||||
|             fetching = true; | ||||
|             binding.btnBackward.setVisibility(currentFeedStoryIndex == 1 && backward ? View.INVISIBLE : View.VISIBLE); | ||||
|             binding.btnForward.setVisibility(last ? View.INVISIBLE : View.VISIBLE); | ||||
|  | ||||
| @ -18,7 +18,9 @@ import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| import androidx.annotation.Nullable; | ||||
| import androidx.appcompat.app.ActionBar; | ||||
| import androidx.appcompat.app.AlertDialog; | ||||
| import androidx.appcompat.app.AppCompatActivity; | ||||
| import androidx.appcompat.widget.AppCompatButton; | ||||
| import androidx.appcompat.widget.AppCompatImageView; | ||||
| import androidx.fragment.app.Fragment; | ||||
| @ -42,6 +44,7 @@ import awais.instagrabber.utils.Utils; | ||||
| public class DirectMessageSettingsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final String TAG = "DirectMsgsSettingsFrag"; | ||||
| 
 | ||||
|     private AppCompatActivity fragmentActivity; | ||||
|     private RecyclerView userList; | ||||
|     private RecyclerView leftUserList; | ||||
|     private EditText titleText; | ||||
| @ -82,6 +85,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr | ||||
|     @Override | ||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         fragmentActivity = (AppCompatActivity) requireActivity(); | ||||
|         basicClickListener = v -> { | ||||
|             final Object tag = v.getTag(); | ||||
|             if (tag instanceof ProfileModel) { | ||||
| @ -147,6 +151,11 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr | ||||
|         threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle(); | ||||
|         binding.swipeRefreshLayout.setEnabled(false); | ||||
| 
 | ||||
|         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||
|         if (actionBar != null) { | ||||
|             actionBar.setTitle(threadTitle); | ||||
|         } | ||||
| 
 | ||||
|         userList = binding.userList; | ||||
|         userList.setHasFixedSize(true); | ||||
|         userList.setLayoutManager(layoutManager); | ||||
| @ -209,7 +218,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr | ||||
|     class ChangeSettings extends AsyncTask<String, Void, Void> { | ||||
|         String action, argument; | ||||
|         boolean ok = false; | ||||
|         private String text; | ||||
|         private final String text; | ||||
| 
 | ||||
|         public ChangeSettings(final String text) { | ||||
|             this.text = text; | ||||
|  | ||||
| @ -12,6 +12,7 @@ import android.view.MenuInflater; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.ViewGroup; | ||||
| import android.widget.Toast; | ||||
| 
 | ||||
| import androidx.activity.OnBackPressedCallback; | ||||
| import androidx.activity.OnBackPressedDispatcher; | ||||
| @ -47,6 +48,7 @@ import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
| import awais.instagrabber.models.PostsLayoutPreferences; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.CookieUtils; | ||||
| import awais.instagrabber.utils.DownloadUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.viewmodels.FeedStoriesViewModel; | ||||
| @ -55,6 +57,7 @@ import awais.instagrabber.webservices.StoriesService; | ||||
| 
 | ||||
| import static androidx.core.content.PermissionChecker.checkSelfPermission; | ||||
| import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | ||||
| import static awais.instagrabber.utils.Utils.settingsHelper; | ||||
| 
 | ||||
| public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||
|     private static final String TAG = "FeedFragment"; | ||||
| @ -74,6 +77,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|     private int downloadChildPosition = -1; | ||||
|     private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_POSTS_LAYOUT); | ||||
|     private RecyclerView storiesRecyclerView; | ||||
|     private MenuItem storyListMenu; | ||||
| 
 | ||||
|     private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { | ||||
|         @Override | ||||
| @ -272,26 +276,23 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|     @Override | ||||
|     public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { | ||||
|         inflater.inflate(R.menu.feed_menu, menu); | ||||
|         storyListMenu = menu.findItem(R.id.storyList); | ||||
|         storyListMenu.setVisible(!storiesFetching); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onOptionsItemSelected(@NonNull final MenuItem item) { | ||||
|         if (item.getItemId() == R.id.layout) { | ||||
|         if (item.getItemId() == R.id.storyList) { | ||||
|             final NavDirections action = FeedFragmentDirections.actionGlobalStoryListViewerFragment("feed"); | ||||
|             NavHostFragment.findNavController(FeedFragment.this).navigate(action); | ||||
|         } | ||||
|         else if (item.getItemId() == R.id.layout) { | ||||
|             showPostsLayoutPreferences(); | ||||
|             return true; | ||||
|         } | ||||
|         return super.onOptionsItemSelected(item); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onResume() { | ||||
|         super.onResume(); | ||||
|         updateSwipeRefreshState(); | ||||
|         // if (videoAwareRecyclerScroller != null && shouldAutoPlay) { | ||||
|         //     videoAwareRecyclerScroller.startPlaying(); | ||||
|         // } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         binding.feedRecyclerView.refresh(); | ||||
| @ -309,9 +310,13 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|     @Override | ||||
|     public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { | ||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||
|         final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; | ||||
|         final boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         if (!granted) { | ||||
|             Toast.makeText(context, R.string.download_permission, Toast.LENGTH_SHORT).show(); | ||||
|             return; | ||||
|         } | ||||
|         if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { | ||||
|             if (downloadFeedModel == null) return; | ||||
|             DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition); | ||||
| @ -346,11 +351,22 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|     } | ||||
| 
 | ||||
|     private void setupFeedStories() { | ||||
|         if (storyListMenu != null) storyListMenu.setVisible(false); | ||||
|         feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); | ||||
|         final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter((model, position) -> { | ||||
|             final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null); | ||||
|             NavHostFragment.findNavController(this).navigate(action); | ||||
|         }); | ||||
|         final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter( | ||||
|             new FeedStoriesAdapter.OnFeedStoryClickListener() { | ||||
|                 @Override | ||||
|                 public void onFeedStoryClick(FeedStoryModel model, int position) { | ||||
|                     final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null, false, false); | ||||
|                     NavHostFragment.findNavController(FeedFragment.this).navigate(action); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onFeedStoryLongClick(FeedStoryModel model, int position) { | ||||
|                     navigateToProfile("@" + model.getProfileModel().getUsername()); | ||||
|                 } | ||||
|             } | ||||
|         ); | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return; | ||||
|         storiesRecyclerView = new RecyclerView(context); | ||||
| @ -362,18 +378,20 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | ||||
|         storiesRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)); | ||||
|         storiesRecyclerView.setAdapter(feedStoriesAdapter); | ||||
|         fragmentActivity.setCollapsingView(storiesRecyclerView); | ||||
|         feedStoriesViewModel.getList().observe(fragmentActivity, feedStoriesAdapter::submitList); | ||||
|         feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), feedStoriesAdapter::submitList); | ||||
|         fetchStories(); | ||||
|     } | ||||
| 
 | ||||
|     private void fetchStories() { | ||||
|         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||
|         storiesFetching = true; | ||||
|         updateSwipeRefreshState(); | ||||
|         storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { | ||||
|         storiesService.getFeedStories(CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<List<FeedStoryModel>>() { | ||||
|             @Override | ||||
|             public void onSuccess(final List<FeedStoryModel> result) { | ||||
|                 feedStoriesViewModel.getList().postValue(result); | ||||
|                 storiesFetching = false; | ||||
|                 if (storyListMenu != null) storyListMenu.setVisible(true); | ||||
|                 updateSwipeRefreshState(); | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -54,7 +54,6 @@ import awais.instagrabber.R; | ||||
| import awais.instagrabber.activities.MainActivity; | ||||
| import awais.instagrabber.adapters.FeedAdapterV2; | ||||
| import awais.instagrabber.adapters.HighlightsAdapter; | ||||
| import awais.instagrabber.asyncs.HighlightsFetcher; | ||||
| import awais.instagrabber.asyncs.ProfileFetcher; | ||||
| import awais.instagrabber.asyncs.ProfilePostFetchService; | ||||
| import awais.instagrabber.asyncs.UsernameFetcher; | ||||
| @ -75,6 +74,7 @@ import awais.instagrabber.dialogs.ProfilePicDialogFragment; | ||||
| import awais.instagrabber.fragments.PostViewV2Fragment; | ||||
| import awais.instagrabber.interfaces.FetchListener; | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.HighlightModel; | ||||
| import awais.instagrabber.models.PostsLayoutPreferences; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.models.StoryModel; | ||||
| @ -89,6 +89,7 @@ import awais.instagrabber.utils.TextUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import awais.instagrabber.viewmodels.HighlightsViewModel; | ||||
| import awais.instagrabber.webservices.FriendshipService; | ||||
| import awais.instagrabber.webservices.MediaService; | ||||
| import awais.instagrabber.webservices.ServiceCallback; | ||||
| import awais.instagrabber.webservices.StoriesService; | ||||
| 
 | ||||
| @ -113,6 +114,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|     private Handler usernameSettingHandler; | ||||
|     private FriendshipService friendshipService; | ||||
|     private StoriesService storiesService; | ||||
|     private MediaService mediaService; | ||||
|     private boolean shouldRefresh = true; | ||||
|     private boolean hasStories = false; | ||||
|     private HighlightsAdapter highlightsAdapter; | ||||
| @ -298,6 +300,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         fragmentActivity = (MainActivity) requireActivity(); | ||||
|         friendshipService = FriendshipService.getInstance(); | ||||
|         storiesService = StoriesService.getInstance(); | ||||
|         mediaService = MediaService.getInstance(); | ||||
|         accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); | ||||
|         favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); | ||||
|         setHasOptionsMenu(true); | ||||
| @ -370,10 +373,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         } | ||||
|         if (item.getItemId() == R.id.restrict) { | ||||
|             if (!isLoggedIn) return false; | ||||
|             final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; | ||||
|             final String action = profileModel.isRestricted() ? "Unrestrict" : "Restrict"; | ||||
|             friendshipService.toggleRestrict( | ||||
|                     profileModel.getId(), | ||||
|                     !profileModel.getRestricted(), | ||||
|                     !profileModel.isRestricted(), | ||||
|                     CookieUtils.getCsrfTokenFromCookie(cookie), | ||||
|                     new ServiceCallback<FriendshipRepoRestrictRootResponse>() { | ||||
|                         @Override | ||||
| @ -392,7 +395,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         if (item.getItemId() == R.id.block) { | ||||
|             final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); | ||||
|             if (!isLoggedIn) return false; | ||||
|             if (profileModel.getBlocked()) { | ||||
|             if (profileModel.isBlocked()) { | ||||
|                 friendshipService.unblock( | ||||
|                         userIdFromCookie, | ||||
|                         profileModel.getId(), | ||||
| @ -434,6 +437,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
| 
 | ||||
|     @Override | ||||
|     public void onRefresh() { | ||||
|         profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); | ||||
|         fetchProfileDetails(); | ||||
|     } | ||||
| 
 | ||||
| @ -571,44 +575,99 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|             fetchStoryAndHighlights(profileId); | ||||
|         } | ||||
|         setupButtons(profileId, myId); | ||||
|         if (!profileId.equals(myId)) { | ||||
|             profileDetailsBinding.favCb.setVisibility(View.VISIBLE); | ||||
|             favoriteRepository.getFavorite(username.substring(1), FavoriteType.USER, new RepositoryCallback<Favorite>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final Favorite result) { | ||||
|                     profileDetailsBinding.favCb.setChecked(true); | ||||
|                     profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); | ||||
|                 } | ||||
|         profileDetailsBinding.favChip.setVisibility(View.VISIBLE); | ||||
|         final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); | ||||
|         favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() { | ||||
|             @Override | ||||
|             public void onSuccess(final Favorite result) { | ||||
|                 profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                 profileDetailsBinding.favChip.setText(R.string.added_to_favs_short); | ||||
|                 favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||
|                         result.getId(), | ||||
|                         profileModel.getUsername(), | ||||
|                         FavoriteType.USER, | ||||
|                         profileModel.getName(), | ||||
|                         profileModel.getSdProfilePic(), | ||||
|                         result.getDateAdded() | ||||
|                 ), new RepositoryCallback<Void>() { | ||||
|                     @Override | ||||
|                     public void onSuccess(final Void result) {} | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onDataNotAvailable() { | ||||
|                     profileDetailsBinding.favCb.setChecked(false); | ||||
|                     profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             profileDetailsBinding.favCb.setVisibility(View.GONE); | ||||
|         } | ||||
|                     @Override | ||||
|                     public void onDataNotAvailable() {} | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onDataNotAvailable() { | ||||
|                 profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); | ||||
|                 profileDetailsBinding.favChip.setText(R.string.add_to_favorites); | ||||
|             } | ||||
|         }); | ||||
|         profileDetailsBinding.favChip.setOnClickListener( | ||||
|                 v -> favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() { | ||||
|                     @Override | ||||
|                     public void onSuccess(final Favorite result) { | ||||
|                         favoriteRepository.deleteFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Void>() { | ||||
|                             @Override | ||||
|                             public void onSuccess(final Void result) { | ||||
|                                 profileDetailsBinding.favChip.setText(R.string.add_to_favorites); | ||||
|                                 profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); | ||||
|                                 showSnackbar(getString(R.string.removed_from_favs)); | ||||
|                             } | ||||
| 
 | ||||
|                             @Override | ||||
|                             public void onDataNotAvailable() {} | ||||
|                         }); | ||||
|                     } | ||||
| 
 | ||||
|                     @Override | ||||
|                     public void onDataNotAvailable() { | ||||
|                         favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||
|                                 0, | ||||
|                                 profileModel.getUsername(), | ||||
|                                 FavoriteType.USER, | ||||
|                                 profileModel.getName(), | ||||
|                                 profileModel.getSdProfilePic(), | ||||
|                                 new Date() | ||||
|                         ), new RepositoryCallback<Void>() { | ||||
|                             @Override | ||||
|                             public void onSuccess(final Void result) { | ||||
|                                 profileDetailsBinding.favChip.setText(R.string.added_to_favs); | ||||
|                                 profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||
|                                 showSnackbar(getString(R.string.added_to_favs)); | ||||
|                             } | ||||
| 
 | ||||
|                             @Override | ||||
|                             public void onDataNotAvailable() {} | ||||
|                         }); | ||||
|                     } | ||||
|                 })); | ||||
|         profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getHdProfilePic()); | ||||
|         profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|         final long followersCount = profileModel.getFollowersCount(); | ||||
|         final long followingCount = profileModel.getFollowingCount(); | ||||
|         final Long followersCount = profileModel.getFollowersCount(); | ||||
|         final Long followingCount = profileModel.getFollowingCount(); | ||||
| 
 | ||||
|         final String postCount = String.valueOf(profileModel.getPostCount()); | ||||
| 
 | ||||
|         SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, | ||||
|                                                                            postCount)); | ||||
|         SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, | ||||
|                 profileModel.getPostCount() > 2000000000L ? 2000000000 : profileModel.getPostCount().intValue(), | ||||
|                 postCount)); | ||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); | ||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); | ||||
|         profileDetailsBinding.mainPostCount.setText(span); | ||||
|         profileDetailsBinding.mainPostCount.setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|         final String followersCountStr = String.valueOf(followersCount); | ||||
|         final int followersCountStrLen = followersCountStr.length(); | ||||
|         span = new SpannableStringBuilder(getString(R.string.main_posts_followers, | ||||
|                                                     followersCountStr)); | ||||
|         span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_followers, | ||||
|                                                                             followersCount > 2000000000L ? 2000000000 : followersCount.intValue(), | ||||
|                                                                             followersCountStr)); | ||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0); | ||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0); | ||||
|         profileDetailsBinding.mainFollowers.setText(span); | ||||
|         profileDetailsBinding.mainFollowers.setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|         final String followingCountStr = String.valueOf(followingCount); | ||||
|         final int followingCountStrLen = followingCountStr.length(); | ||||
| @ -617,6 +676,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0); | ||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0); | ||||
|         profileDetailsBinding.mainFollowing.setText(span); | ||||
|         profileDetailsBinding.mainFollowing.setVisibility(View.VISIBLE); | ||||
| 
 | ||||
|         profileDetailsBinding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername() | ||||
|                                                                                              : profileModel.getName()); | ||||
| @ -640,6 +700,51 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                                                                                                                            .trim())); | ||||
|             profileDetailsBinding.mainBiography | ||||
|                     .addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim())); | ||||
|             profileDetailsBinding.mainBiography.setOnClickListener(v -> { | ||||
|                 String[] commentDialogList; | ||||
|                 if (!TextUtils.isEmpty(cookie)) { | ||||
|                     commentDialogList = new String[]{ | ||||
|                             getResources().getString(R.string.bio_copy), | ||||
|                             getResources().getString(R.string.bio_translate) | ||||
|                     }; | ||||
|                 } else { | ||||
|                     commentDialogList = new String[]{ | ||||
|                             getResources().getString(R.string.bio_copy) | ||||
|                     }; | ||||
|                 } | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setItems(commentDialogList, (d,w) -> { | ||||
|                             switch (w) { | ||||
|                                 case 0: | ||||
|                                     Utils.copyText(context, biography); | ||||
|                                     break; | ||||
|                                 case 1: | ||||
|                                     mediaService.translate(profileModel.getId(), "3", new ServiceCallback<String>() { | ||||
|                                         @Override | ||||
|                                         public void onSuccess(final String result) { | ||||
|                                             if (TextUtils.isEmpty(result)) { | ||||
|                                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||
|                                                 return; | ||||
|                                             } | ||||
|                                             new AlertDialog.Builder(context) | ||||
|                                                     .setTitle(profileModel.getUsername()) | ||||
|                                                     .setMessage(result) | ||||
|                                                     .setPositiveButton(R.string.ok, null) | ||||
|                                                     .show(); | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onFailure(final Throwable t) { | ||||
|                                             Log.e(TAG, "Error translating bio", t); | ||||
|                                             Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||
|                                         } | ||||
|                                     }); | ||||
|                                     break; | ||||
|                             } | ||||
|                         }) | ||||
|                         .setNegativeButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             }); | ||||
|             profileDetailsBinding.mainBiography.setOnLongClickListener(v -> { | ||||
|                 Utils.copyText(context, biography); | ||||
|                 return true; | ||||
| @ -687,6 +792,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|     } | ||||
| 
 | ||||
|     private void setupButtons(final String profileId, final String myId) { | ||||
|         profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE); | ||||
|         if (isLoggedIn) { | ||||
|             if (profileId.equals(myId)) { | ||||
|                 profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE); | ||||
| @ -696,15 +802,29 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                 profileDetailsBinding.btnSaved.setText(R.string.saved); | ||||
|                 return; | ||||
|             } | ||||
|             profileDetailsBinding.btnTagged.setVisibility(View.GONE); | ||||
|             profileDetailsBinding.btnSaved.setVisibility(View.GONE); | ||||
|             profileDetailsBinding.btnLiked.setVisibility(View.GONE); | ||||
|             profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism? | ||||
|             profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE); | ||||
|             if (profileModel.getFollowing()) { | ||||
|             if (profileModel.isFollowing() || profileModel.isFollower()) { | ||||
|                 profileDetailsBinding.mainStatus.setVisibility(View.VISIBLE); | ||||
|                 if (!profileModel.isFollowing()) { | ||||
|                     profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.blue_800)); | ||||
|                     profileDetailsBinding.mainStatus.setText(R.string.status_follower); | ||||
|                 } | ||||
|                 else if (!profileModel.isFollower()) { | ||||
|                     profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.deep_orange_800)); | ||||
|                     profileDetailsBinding.mainStatus.setText(R.string.status_following); | ||||
|                 } | ||||
|                 else { | ||||
|                     profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.green_800)); | ||||
|                     profileDetailsBinding.mainStatus.setText(R.string.status_mutual); | ||||
|                 } | ||||
|             } | ||||
|             if (profileModel.isFollowing()) { | ||||
|                 profileDetailsBinding.btnFollow.setText(R.string.unfollow); | ||||
|                 profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); | ||||
|             } else if (profileModel.getRequested()) { | ||||
|             } else if (profileModel.isRequested()) { | ||||
|                 profileDetailsBinding.btnFollow.setText(R.string.cancel); | ||||
|                 profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); | ||||
|             } else { | ||||
| @ -713,16 +833,15 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|             } | ||||
|             if (restrictMenuItem != null) { | ||||
|                 restrictMenuItem.setVisible(true); | ||||
|                 if (profileModel.getRestricted()) { | ||||
|                 if (profileModel.isRestricted()) { | ||||
|                     restrictMenuItem.setTitle(R.string.unrestrict); | ||||
|                 } else { | ||||
|                     restrictMenuItem.setTitle(R.string.restrict); | ||||
|                 } | ||||
|             } | ||||
|             profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE); | ||||
|             if (blockMenuItem != null) { | ||||
|                 blockMenuItem.setVisible(true); | ||||
|                 if (profileModel.getBlocked()) { | ||||
|                 if (profileModel.isBlocked()) { | ||||
|                     blockMenuItem.setTitle(R.string.unblock); | ||||
|                 } else { | ||||
|                     blockMenuItem.setTitle(R.string.block); | ||||
| @ -732,7 +851,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         } | ||||
|         if (!profileModel.isReallyPrivate() && restrictMenuItem != null) { | ||||
|             restrictMenuItem.setVisible(true); | ||||
|             if (profileModel.getRestricted()) { | ||||
|             if (profileModel.isRestricted()) { | ||||
|                 restrictMenuItem.setTitle(R.string.unrestrict); | ||||
|             } else { | ||||
|                 restrictMenuItem.setTitle(R.string.restrict); | ||||
| @ -760,20 +879,55 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                                             Log.e(TAG, "Error", t); | ||||
|                                         } | ||||
|                                     }); | ||||
|         new HighlightsFetcher(profileId, | ||||
|                               result -> { | ||||
|                                   highlightsFetching = false; | ||||
|                                   if (result != null) { | ||||
|                                       profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); | ||||
|                                       highlightsViewModel.getList().postValue(result); | ||||
|                                   } else profileDetailsBinding.highlightsList.setVisibility(View.GONE); | ||||
|                               }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||
|         storiesService.fetchHighlights(profileId, | ||||
|                                         new ServiceCallback<List<HighlightModel>>() { | ||||
|                                             @Override | ||||
|                                             public void onSuccess(final List<HighlightModel> result) { | ||||
|                                                 highlightsFetching = false; | ||||
|                                                 if (result != null) { | ||||
|                                                     profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); | ||||
|                                                     highlightsViewModel.getList().postValue(result); | ||||
|                                                 } | ||||
|                                                 else profileDetailsBinding.highlightsList.setVisibility(View.GONE); | ||||
|                                             } | ||||
| 
 | ||||
|                                             @Override | ||||
|                                             public void onFailure(final Throwable t) { | ||||
|                                                 profileDetailsBinding.highlightsList.setVisibility(View.GONE); | ||||
|                                                 Log.e(TAG, "Error", t); | ||||
|                                             } | ||||
|                                         }); | ||||
|     } | ||||
| 
 | ||||
|     private void setupCommonListeners() { | ||||
|         final Context context = getContext(); | ||||
|         final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); | ||||
|         profileDetailsBinding.btnFollow.setOnClickListener(v -> { | ||||
|             if (profileModel.getFollowing() || profileModel.getRequested()) { | ||||
|             if (profileModel.isFollowing() && profileModel.isPrivate()) { | ||||
|                 new AlertDialog.Builder(context) | ||||
|                         .setTitle(R.string.priv_acc) | ||||
|                         .setMessage(R.string.priv_acc_confirm) | ||||
|                         .setPositiveButton(R.string.confirm, (d, w) -> | ||||
|                             friendshipService.unfollow( | ||||
|                                     userIdFromCookie, | ||||
|                                     profileModel.getId(), | ||||
|                                     CookieUtils.getCsrfTokenFromCookie(cookie), | ||||
|                                     new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||
|                                         @Override | ||||
|                                         public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||
|                                             // Log.d(TAG, "Unfollow success: " + result); | ||||
|                                             onRefresh(); | ||||
|                                         } | ||||
| 
 | ||||
|                                         @Override | ||||
|                                         public void onFailure(final Throwable t) { | ||||
|                                             Log.e(TAG, "Error unfollowing", t); | ||||
|                                         } | ||||
|                                     })) | ||||
|                         .setNegativeButton(R.string.cancel, null) | ||||
|                         .show(); | ||||
|             } | ||||
|             else if (profileModel.isFollowing() || profileModel.isRequested()) { | ||||
|                 friendshipService.unfollow( | ||||
|                         userIdFromCookie, | ||||
|                         profileModel.getId(), | ||||
| @ -854,72 +1008,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|                 if (which == 1) { | ||||
|                     // show stories | ||||
|                     final NavDirections action = ProfileFragmentDirections | ||||
|                             .actionProfileFragmentToStoryViewerFragment(-1, null, false, false, profileModel.getId(), username); | ||||
|                             .actionProfileFragmentToStoryViewerFragment(-1, null, false, false, profileModel.getId(), username, false, false); | ||||
|                     NavHostFragment.findNavController(this).navigate(action); | ||||
|                     return; | ||||
|                 } | ||||
|                 showProfilePicDialog(); | ||||
|             }; | ||||
|             final Context context = getContext(); | ||||
|             if (context == null) return; | ||||
|             new AlertDialog.Builder(context) | ||||
|                     .setItems(options, profileDialogListener) | ||||
|                     .setNegativeButton(R.string.cancel, null) | ||||
|                     .show(); | ||||
|         }); | ||||
|         profileDetailsBinding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> { | ||||
|             // do not do anything if state matches the db, as listener is set before profile details are set | ||||
|             final Context context = getContext(); | ||||
|             if (context == null) return; | ||||
|             final String finalUsername = username.startsWith("@") ? username.substring(1) : username; | ||||
|             favoriteRepository.getFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback<Favorite>() { | ||||
|                 @Override | ||||
|                 public void onSuccess(final Favorite result) { | ||||
|                     if (isChecked) return; // already a fav | ||||
|                     buttonView.setVisibility(View.GONE); | ||||
|                     profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); | ||||
|                     favoriteRepository.deleteFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback<Void>() { | ||||
|                         @Override | ||||
|                         public void onSuccess(final Void result) { | ||||
|                             profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); | ||||
|                             profileDetailsBinding.favProgress.setVisibility(View.GONE); | ||||
|                             profileDetailsBinding.favCb.setVisibility(View.VISIBLE); | ||||
|                             showSnackbar(getString(R.string.removed_from_favs)); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onDataNotAvailable() {} | ||||
|                     }); | ||||
|                 } | ||||
| 
 | ||||
|                 @Override | ||||
|                 public void onDataNotAvailable() { | ||||
|                     if (!isChecked) return; // not in fav already | ||||
|                     buttonView.setVisibility(View.GONE); | ||||
|                     profileDetailsBinding.favProgress.setVisibility(View.VISIBLE); | ||||
|                     final Favorite model = new Favorite( | ||||
|                             -1, | ||||
|                             finalUsername, | ||||
|                             FavoriteType.USER, | ||||
|                             profileModel.getName(), | ||||
|                             profileModel.getSdProfilePic(), | ||||
|                             new Date() | ||||
|                     ); | ||||
|                     favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback<Void>() { | ||||
|                         @Override | ||||
|                         public void onSuccess(final Void result) { | ||||
|                             profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); | ||||
|                             profileDetailsBinding.favProgress.setVisibility(View.GONE); | ||||
|                             profileDetailsBinding.favCb.setVisibility(View.VISIBLE); | ||||
|                             showSnackbar(getString(R.string.added_to_favs)); | ||||
|                         } | ||||
| 
 | ||||
|                         @Override | ||||
|                         public void onDataNotAvailable() {} | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private void showSnackbar(final String message) { | ||||
| @ -951,7 +1051,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|     private void setupPosts() { | ||||
|         binding.postsRecyclerView.setViewModelStoreOwner(this) | ||||
|                                  .setLifeCycleOwner(this) | ||||
|                                  .setPostFetchService(new ProfilePostFetchService(profileModel)) | ||||
|                                  .setPostFetchService(new ProfilePostFetchService(profileModel, isLoggedIn)) | ||||
|                                  .setLayoutPreferences(layoutPreferences) | ||||
|                                  .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) | ||||
|                                  .setFeedItemCallback(feedItemCallback) | ||||
| @ -969,7 +1069,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | ||||
|         highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class); | ||||
|         highlightsAdapter = new HighlightsAdapter((model, position) -> { | ||||
|             final NavDirections action = ProfileFragmentDirections | ||||
|                     .actionProfileFragmentToStoryViewerFragment(position, model.getTitle(), false, false, null, null); | ||||
|                     .actionProfileFragmentToStoryViewerFragment(position, model.getTitle(), false, false, null, null, false, false); | ||||
|             NavHostFragment.findNavController(this).navigate(action); | ||||
|         }); | ||||
|         final Context context = getContext(); | ||||
|  | ||||
| @ -88,9 +88,10 @@ public class AboutFragment extends BasePreferencesFragment { | ||||
|         preference.setSummary(R.string.about_feedback_summary); | ||||
|         preference.setIconSpaceReserved(false); | ||||
|         preference.setOnPreferenceClickListener(p -> { | ||||
|             final Intent intent = new Intent(Intent.ACTION_SENDTO); | ||||
|             intent.setData(Uri.parse(getString(R.string.about_feedback_summary) | ||||
|              + "?body=Please%20note%20that%20your%20email%20address%20and%20the%20entire%20content%20will%20be%20published%20onto%20GitHub%20issues.%20If%20you%20do%20not%20wish%20to%20do%20that%2C%20use%20other%20contact%20methods%20instead.")); | ||||
|             final Intent intent = new Intent(Intent.ACTION_SEND); | ||||
|             intent.setType("message/rfc822") | ||||
|                     .putExtra(Intent.EXTRA_EMAIL, getString(R.string.about_feedback_summary)) | ||||
|                     .putExtra(Intent.EXTRA_TEXT, "Please note that your email address and the entire content will be published onto GitHub issues. If you do not wish to do that, use other contact methods instead."); | ||||
|             if (intent.resolveActivity(context.getPackageManager()) != null) startActivity(intent); | ||||
|             return true; | ||||
|         }); | ||||
|  | ||||
| @ -26,10 +26,30 @@ public class BackupPreferencesFragment extends BasePreferencesFragment { | ||||
|         if (context == null) { | ||||
|             return; | ||||
|         } | ||||
|         screen.addPreference(getAboutPreference(context)); | ||||
|         screen.addPreference(getWarningPreference(context)); | ||||
|         screen.addPreference(getCreatePreference(context)); | ||||
|         screen.addPreference(getRestorePreference(context)); | ||||
|     } | ||||
| 
 | ||||
|     private Preference getAboutPreference(@NonNull final Context context) { | ||||
|         final Preference preference = new Preference(context); | ||||
|         preference.setSummary(R.string.backup_summary); | ||||
|         preference.setEnabled(false); | ||||
|         preference.setIcon(R.drawable.ic_outline_info_24); | ||||
|         preference.setIconSpaceReserved(true); | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     private Preference getWarningPreference(@NonNull final Context context) { | ||||
|         final Preference preference = new Preference(context); | ||||
|         preference.setSummary(R.string.backup_warning); | ||||
|         preference.setEnabled(false); | ||||
|         preference.setIcon(R.drawable.ic_warning); | ||||
|         preference.setIconSpaceReserved(true); | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     private Preference getCreatePreference(@NonNull final Context context) { | ||||
|         final Preference preference = new Preference(context); | ||||
|         preference.setTitle(R.string.create_backup); | ||||
|  | ||||
| @ -134,7 +134,18 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | ||||
|         screen.addPreference(getDivider(context)); | ||||
|         if (isLoggedIn) { | ||||
|             screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { | ||||
|                 NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment); | ||||
|                 final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif"); | ||||
|                 NavHostFragment.findNavController(this).navigate(navDirections); | ||||
|                 return true; | ||||
|             })); | ||||
|             screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> { | ||||
|                 final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml"); | ||||
|                 NavHostFragment.findNavController(this).navigate(navDirections); | ||||
|                 return true; | ||||
|             })); | ||||
|             screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> { | ||||
|                 final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive"); | ||||
|                 NavHostFragment.findNavController(this).navigate(navDirections); | ||||
|                 return true; | ||||
|             })); | ||||
|         } | ||||
|  | ||||
| @ -79,6 +79,7 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment { | ||||
|             screen.addPreference(loggedInUsersPreferenceCategory); | ||||
|             loggedInUsersPreferenceCategory.setIconSpaceReserved(false); | ||||
|             loggedInUsersPreferenceCategory.setTitle(R.string.login_settings); | ||||
|             loggedInUsersPreferenceCategory.addPreference(getStorySortPreference()); | ||||
|             loggedInUsersPreferenceCategory.addPreference(getMarkStoriesSeenPreference()); | ||||
|             loggedInUsersPreferenceCategory.addPreference(getMarkDMSeenPreference()); | ||||
|             loggedInUsersPreferenceCategory.addPreference(getEnableActivityNotificationsPreference()); | ||||
| @ -204,6 +205,25 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment { | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     private Preference getStorySortPreference() { | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return null; | ||||
|         final ListPreference preference = new ListPreference(context); | ||||
|         preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance()); | ||||
|         final int length = getResources().getStringArray(R.array.story_sorts).length; | ||||
|         final String[] values = new String[length]; | ||||
|         for (int i = 0; i < length; i++) { | ||||
|             values[i] = String.valueOf(i); | ||||
|         } | ||||
|         preference.setKey(Constants.STORY_SORT); | ||||
|         preference.setTitle(R.string.story_sort_setting); | ||||
|         preference.setDialogTitle(R.string.story_sort_setting); | ||||
|         preference.setEntries(R.array.story_sorts); | ||||
|         preference.setIconSpaceReserved(false); | ||||
|         preference.setEntryValues(values); | ||||
|         return preference; | ||||
|     } | ||||
| 
 | ||||
|     private Preference getMarkStoriesSeenPreference() { | ||||
|         final Context context = getContext(); | ||||
|         if (context == null) return null; | ||||
|  | ||||
| @ -11,16 +11,12 @@ import awais.instagrabber.models.enums.MediaItemType; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| public abstract class BasePostModel implements Serializable, Selectable { | ||||
|     protected String postId; | ||||
|     protected String displayUrl; | ||||
|     protected String shortCode; | ||||
|     protected String postId, displayUrl, shortCode, captionId; | ||||
|     protected CharSequence postCaption; | ||||
|     protected MediaItemType itemType; | ||||
|     protected boolean isSelected; | ||||
|     protected boolean isDownloaded; | ||||
|     protected boolean isSelected, isDownloaded; | ||||
|     protected long timestamp; | ||||
|     boolean liked; | ||||
|     boolean saved; | ||||
|     boolean liked, saved; | ||||
| 
 | ||||
|     public boolean getLike() { | ||||
|         return liked; | ||||
| @ -46,6 +42,10 @@ public abstract class BasePostModel implements Serializable, Selectable { | ||||
|         return postCaption; | ||||
|     } | ||||
| 
 | ||||
|     public final String getCaptionId() { | ||||
|         return captionId; | ||||
|     } | ||||
| 
 | ||||
|     public final String getShortCode() { | ||||
|         return shortCode; | ||||
|     } | ||||
|  | ||||
| @ -11,11 +11,10 @@ public class CommentModel { | ||||
|     private final ProfileModel profileModel; | ||||
|     private final String id; | ||||
|     private final String text; | ||||
|     private final long likes; | ||||
|     private long likes; | ||||
|     private final long timestamp; | ||||
|     private List<CommentModel> childCommentModels; | ||||
|     private final boolean liked; | ||||
|     private boolean hasNextPage; | ||||
|     private boolean liked, hasNextPage; | ||||
|     private String endCursor; | ||||
| 
 | ||||
|     public CommentModel(final String id, | ||||
| @ -53,6 +52,11 @@ public class CommentModel { | ||||
|         return liked; | ||||
|     } | ||||
| 
 | ||||
|     public void setLiked(boolean liked) { | ||||
|         this.likes = liked ? likes + 1 : likes - 1; | ||||
|         this.liked = liked; | ||||
|     } | ||||
| 
 | ||||
|     public ProfileModel getProfileModel() { | ||||
|         return profileModel; | ||||
|     } | ||||
|  | ||||
| @ -24,7 +24,7 @@ public final class FeedModel extends PostModel { | ||||
|         private String displayUrl; | ||||
|         private String thumbnailUrl; | ||||
|         private String shortCode; | ||||
|         private String postCaption; | ||||
|         private String postCaption, captionId; | ||||
|         private long commentsCount; | ||||
|         private long timestamp; | ||||
|         private boolean liked; | ||||
| @ -76,6 +76,11 @@ public final class FeedModel extends PostModel { | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setCaptionId(final String captionId) { | ||||
|             this.captionId = captionId; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public Builder setCommentsCount(final long commentsCount) { | ||||
|             this.commentsCount = commentsCount; | ||||
|             return this; | ||||
| @ -127,8 +132,8 @@ public final class FeedModel extends PostModel { | ||||
|         } | ||||
| 
 | ||||
|         public FeedModel build() { | ||||
|             return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, commentsCount, | ||||
|                                  timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth); | ||||
|             return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, captionId, | ||||
|                     commentsCount, timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -140,6 +145,7 @@ public final class FeedModel extends PostModel { | ||||
|                       final String thumbnailUrl, | ||||
|                       final String shortCode, | ||||
|                       final String postCaption, | ||||
|                       final String captionId, | ||||
|                       final long commentsCount, | ||||
|                       final long timestamp, | ||||
|                       final boolean liked, | ||||
| @ -150,7 +156,7 @@ public final class FeedModel extends PostModel { | ||||
|                       final List<PostChild> sliderItems, | ||||
|                       final int imageHeight, | ||||
|                       final int imageWidth) { | ||||
|         super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked); | ||||
|         super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, captionId, timestamp, liked, bookmarked); | ||||
|         this.profileModel = profileModel; | ||||
|         this.commentsCount = commentsCount; | ||||
|         this.likesCount = likesCount; | ||||
|  | ||||
| @ -1,36 +1,66 @@ | ||||
| package awais.instagrabber.models; | ||||
| 
 | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
| 
 | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| public final class FeedStoryModel implements Serializable { | ||||
|     private final String storyMediaId; | ||||
|     private final ProfileModel profileModel; | ||||
|     private StoryModel[] storyModels; | ||||
|     private boolean fullyRead; | ||||
|     private final StoryModel firstStoryModel; | ||||
|     private Boolean fullyRead; | ||||
|     private final long timestamp; | ||||
|     private final int mediaCount; | ||||
| 
 | ||||
|     public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, final boolean fullyRead) { | ||||
|     public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, final boolean fullyRead, | ||||
|                           final long timestamp, final StoryModel firstStoryModel, final int mediaCount) { | ||||
|         this.storyMediaId = storyMediaId; | ||||
|         this.profileModel = profileModel; | ||||
|         this.fullyRead = fullyRead; | ||||
|         this.timestamp = timestamp; | ||||
|         this.firstStoryModel = firstStoryModel; | ||||
|         this.mediaCount = mediaCount; | ||||
|     } | ||||
| 
 | ||||
|     public String getStoryMediaId() { | ||||
|         return storyMediaId; | ||||
|     } | ||||
| 
 | ||||
|     public long getTimestamp() { | ||||
|         return timestamp; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public String getDateTime() { | ||||
|         return Utils.datetimeParser.format(new Date(timestamp * 1000L)); | ||||
|     } | ||||
| 
 | ||||
|     public int getMediaCount() { | ||||
|         return mediaCount; | ||||
|     } | ||||
| 
 | ||||
|     public ProfileModel getProfileModel() { | ||||
|         return profileModel; | ||||
|     } | ||||
| 
 | ||||
|     public void setStoryModels(final StoryModel[] storyModels) { | ||||
|         this.storyModels = storyModels; | ||||
| //    public void setFirstStoryModel(final StoryModel firstStoryModel) { | ||||
| //        this.firstStoryModel = firstStoryModel; | ||||
| //    } | ||||
| 
 | ||||
|     public StoryModel getFirstStoryModel() { | ||||
|         return firstStoryModel; | ||||
|     } | ||||
| 
 | ||||
|     public StoryModel[] getStoryModels() { | ||||
|         return storyModels; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getFullyRead() { | ||||
|     public Boolean isFullyRead() { | ||||
|         return fullyRead; | ||||
|     } | ||||
| 
 | ||||
|     public void setFullyRead(final boolean fullyRead) { | ||||
|         this.fullyRead = fullyRead; | ||||
|     } | ||||
| } | ||||
| @ -29,7 +29,7 @@ public final class HashtagModel implements Serializable { | ||||
|         return sdProfilePic; | ||||
|     } | ||||
| 
 | ||||
|     public long getPostCount() { return postCount; } | ||||
|     public Long getPostCount() { return postCount; } | ||||
| 
 | ||||
|     public boolean getFollowing() { return following; } | ||||
| } | ||||
| @ -1,16 +1,28 @@ | ||||
| package awais.instagrabber.models; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import java.util.Date; | ||||
| 
 | ||||
| import awais.instagrabber.utils.Utils; | ||||
| 
 | ||||
| public final class HighlightModel { | ||||
|     private final String title; | ||||
|     private final String id; | ||||
|     private final String thumbnailUrl; | ||||
|     private final long timestamp; | ||||
|     private final int mediaCount; | ||||
| 
 | ||||
|     public HighlightModel(final String title, | ||||
|                           final String id, | ||||
|                           final String thumbnailUrl) { | ||||
|                           final String thumbnailUrl, | ||||
|                           final long timestamp, | ||||
|                           final int mediaCount) { | ||||
|         this.title = title; | ||||
|         this.id = id; | ||||
|         this.thumbnailUrl = thumbnailUrl; | ||||
|         this.timestamp = timestamp; | ||||
|         this.mediaCount = mediaCount; | ||||
|     } | ||||
| 
 | ||||
|     public String getTitle() { | ||||
| @ -24,4 +36,17 @@ public final class HighlightModel { | ||||
|     public String getThumbnailUrl() { | ||||
|         return thumbnailUrl; | ||||
|     } | ||||
| 
 | ||||
|     public long getTimestamp() { | ||||
|         return timestamp; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public String getDateTime() { | ||||
|         return Utils.datetimeParser.format(new Date(timestamp * 1000L)); | ||||
|     } | ||||
| 
 | ||||
|     public int getMediaCount() { | ||||
|         return mediaCount; | ||||
|     } | ||||
| } | ||||
| @ -59,5 +59,5 @@ public final class LocationModel implements Serializable { | ||||
|         return sdProfilePic; | ||||
|     } | ||||
| 
 | ||||
|     public long getPostCount() { return postCount; } | ||||
|     public Long getPostCount() { return postCount; } | ||||
| } | ||||
| @ -13,7 +13,7 @@ public final class NotificationModel { | ||||
|     private final String userId; | ||||
|     private final String username; | ||||
|     private final String profilePicUrl; | ||||
|     private final String shortCode; | ||||
|     private final String postId; | ||||
|     private final String previewUrl; | ||||
|     private final NotificationType type; | ||||
|     private final CharSequence text; | ||||
| @ -25,7 +25,7 @@ public final class NotificationModel { | ||||
|                              final String userId, | ||||
|                              final String username, | ||||
|                              final String profilePicUrl, | ||||
|                              final String shortCode, | ||||
|                              final String postId, | ||||
|                              final String previewUrl, | ||||
|                              final NotificationType type) { | ||||
|         this.id = id; | ||||
| @ -34,7 +34,7 @@ public final class NotificationModel { | ||||
|         this.userId = userId; | ||||
|         this.username = username; | ||||
|         this.profilePicUrl = profilePicUrl; | ||||
|         this.shortCode = shortCode; | ||||
|         this.postId = postId; | ||||
|         this.previewUrl = previewUrl; | ||||
|         this.type = type; | ||||
|     } | ||||
| @ -47,6 +47,10 @@ public final class NotificationModel { | ||||
|         return text; | ||||
|     } | ||||
| 
 | ||||
|     public long getTimestamp() { | ||||
|         return timestamp; | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     public String getDateTime() { | ||||
|         return Utils.datetimeParser.format(new Date(timestamp * 1000L)); | ||||
| @ -64,8 +68,8 @@ public final class NotificationModel { | ||||
|         return profilePicUrl; | ||||
|     } | ||||
| 
 | ||||
|     public String getShortCode() { | ||||
|         return shortCode; | ||||
|     public String getPostId() { | ||||
|         return postId; | ||||
|     } | ||||
| 
 | ||||
|     public String getPreviewPic() { | ||||
|  | ||||
| @ -19,6 +19,7 @@ public class PostModel extends BasePostModel { | ||||
|                      final String thumbnailUrl, | ||||
|                      final String shortCode, | ||||
|                      final CharSequence postCaption, | ||||
|                      final String captionId, | ||||
|                      long timestamp, | ||||
|                      boolean liked, | ||||
|                      boolean bookmarked) { | ||||
| @ -28,6 +29,7 @@ public class PostModel extends BasePostModel { | ||||
|         this.thumbnailUrl = thumbnailUrl; | ||||
|         this.shortCode = shortCode; | ||||
|         this.postCaption = postCaption; | ||||
|         this.captionId = captionId; | ||||
|         this.timestamp = timestamp; | ||||
|         this.liked = liked; | ||||
|         this.saved = bookmarked; | ||||
|  | ||||
| @ -3,15 +3,15 @@ package awais.instagrabber.models; | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| public final class ProfileModel implements Serializable { | ||||
|     private final boolean isPrivate, reallyPrivate, isVerified, following, restricted, blocked, requested; | ||||
|     private final boolean isPrivate, reallyPrivate, isVerified, following, follower, restricted, blocked, requested; | ||||
|     private final long postCount, followersCount, followingCount; | ||||
|     private final String id, username, name, biography, url, sdProfilePic, hdProfilePic; | ||||
| 
 | ||||
|     public ProfileModel(final boolean isPrivate, final boolean reallyPrivate, | ||||
|                         final boolean isVerified, final String id, final String username, final String name, final String biography, | ||||
|                         final String url, final String sdProfilePic, final String hdProfilePic, final long postCount, | ||||
|                         final long followersCount, final long followingCount, final boolean following, final boolean restricted, | ||||
|                         final boolean blocked, final boolean requested) { | ||||
|                         final long followersCount, final long followingCount, final boolean following, final boolean follower, | ||||
|                         final boolean restricted, final boolean blocked, final boolean requested) { | ||||
|         this.isPrivate = isPrivate; | ||||
|         this.reallyPrivate = reallyPrivate; | ||||
|         this.isVerified = isVerified; | ||||
| @ -26,21 +26,22 @@ public final class ProfileModel implements Serializable { | ||||
|         this.followersCount = followersCount; | ||||
|         this.followingCount = followingCount; | ||||
|         this.following = following; | ||||
|         this.follower = follower; | ||||
|         this.restricted = restricted; | ||||
|         this.blocked = blocked; | ||||
|         this.requested = requested; | ||||
|     } | ||||
| 
 | ||||
|     public static ProfileModel getDefaultProfileModel() { | ||||
|         return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false); | ||||
|         return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false, false); | ||||
|     } | ||||
| 
 | ||||
|     public static ProfileModel getDefaultProfileModel(final String userId) { | ||||
|         return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false); | ||||
|         return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false, false); | ||||
|     } | ||||
| 
 | ||||
|     public static ProfileModel getDefaultProfileModel(final String userId, final String username) { | ||||
|         return new ProfileModel(false, false, false, userId, username, null, null, null, null, null, 0, 0, 0, false, false, false, false); | ||||
|         return new ProfileModel(false, false, false, userId, username, null, null, null, null, null, 0, 0, 0, false, false, false, false, false); | ||||
|     } | ||||
| 
 | ||||
|     public boolean isPrivate() { | ||||
| @ -83,31 +84,35 @@ public final class ProfileModel implements Serializable { | ||||
|         return hdProfilePic; | ||||
|     } | ||||
| 
 | ||||
|     public long getPostCount() { | ||||
|     public Long getPostCount() { | ||||
|         return postCount; | ||||
|     } | ||||
| 
 | ||||
|     public long getFollowersCount() { | ||||
|     public Long getFollowersCount() { | ||||
|         return followersCount; | ||||
|     } | ||||
| 
 | ||||
|     public long getFollowingCount() { | ||||
|     public Long getFollowingCount() { | ||||
|         return followingCount; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getFollowing() { | ||||
|     public boolean isFollowing() { | ||||
|         return following; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getRestricted() { | ||||
|     public boolean isFollower() { | ||||
|         return follower; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isRestricted() { | ||||
|         return restricted; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getBlocked() { | ||||
|     public boolean isBlocked() { | ||||
|         return blocked; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getRequested() { | ||||
|     public boolean isRequested() { | ||||
|         return requested; | ||||
|     } | ||||
| } | ||||
| @ -6,11 +6,13 @@ import awais.instagrabber.models.enums.MediaItemType; | ||||
| import awais.instagrabber.models.stickers.PollModel; | ||||
| import awais.instagrabber.models.stickers.QuestionModel; | ||||
| import awais.instagrabber.models.stickers.QuizModel; | ||||
| import awais.instagrabber.models.stickers.SliderModel; | ||||
| import awais.instagrabber.models.stickers.SwipeUpModel; | ||||
| 
 | ||||
| public final class StoryModel implements Serializable { | ||||
|     private final String storyMediaId; | ||||
|     private final String storyUrl; | ||||
|     private String thumbnail; | ||||
|     private final String username; | ||||
|     private final String userId; | ||||
|     private final MediaItemType itemType; | ||||
| @ -21,6 +23,7 @@ public final class StoryModel implements Serializable { | ||||
|     private String spotify; | ||||
|     private PollModel poll; | ||||
|     private QuestionModel question; | ||||
|     private SliderModel slider; | ||||
|     private QuizModel quiz; | ||||
|     private SwipeUpModel swipeUp; | ||||
|     private String[] mentions; | ||||
| @ -28,10 +31,11 @@ public final class StoryModel implements Serializable { | ||||
|     private boolean isCurrentSlide = false; | ||||
|     private final boolean canReply; | ||||
| 
 | ||||
|     public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType, | ||||
|     public StoryModel(final String storyMediaId, final String storyUrl, final String thumbnail, final MediaItemType itemType, | ||||
|                       final long timestamp, final String username, final String userId, final boolean canReply) { | ||||
|         this.storyMediaId = storyMediaId; | ||||
|         this.storyUrl = storyUrl; | ||||
|         this.thumbnail = thumbnail; | ||||
|         this.itemType = itemType; | ||||
|         this.timestamp = timestamp; | ||||
|         this.username = username; | ||||
| @ -43,6 +47,10 @@ public final class StoryModel implements Serializable { | ||||
|         return storyUrl; | ||||
|     } | ||||
| 
 | ||||
|     public String getThumbnail() { | ||||
|         return thumbnail; | ||||
|     } | ||||
| 
 | ||||
|     public String getStoryMediaId() { | ||||
|         return storyMediaId; | ||||
|     } | ||||
| @ -71,6 +79,10 @@ public final class StoryModel implements Serializable { | ||||
|         return question; | ||||
|     } | ||||
| 
 | ||||
|     public SliderModel getSlider() { | ||||
|         return slider; | ||||
|     } | ||||
| 
 | ||||
|     public QuizModel getQuiz() { | ||||
|         return quiz; | ||||
|     } | ||||
| @ -85,6 +97,10 @@ public final class StoryModel implements Serializable { | ||||
|         return position; | ||||
|     } | ||||
| 
 | ||||
|     public void setThumbnail(final String thumbnail) { | ||||
|         this.thumbnail = thumbnail; | ||||
|     } | ||||
| 
 | ||||
|     public void setVideoUrl(final String videoUrl) { | ||||
|         this.videoUrl = videoUrl; | ||||
|     } | ||||
| @ -109,6 +125,10 @@ public final class StoryModel implements Serializable { | ||||
|         this.question = question; | ||||
|     } | ||||
| 
 | ||||
|     public void setSlider(final SliderModel slider) { | ||||
|         this.slider = slider; | ||||
|     } | ||||
| 
 | ||||
|     public void setQuiz(final QuizModel quiz) { | ||||
|         this.quiz = quiz; | ||||
|     } | ||||
|  | ||||
| @ -5,12 +5,20 @@ import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| public enum NotificationType implements Serializable { | ||||
|     // web | ||||
|     LIKE("GraphLikeAggregatedStory"), | ||||
|     FOLLOW("GraphFollowAggregatedStory"), | ||||
|     COMMENT("GraphCommentMediaStory"), | ||||
|     MENTION("GraphMentionStory"), | ||||
|     TAGGED("GraphUserTaggedStory"), | ||||
|     REQUEST("REQUEST"); | ||||
|     // app story_type | ||||
|     COMMENT_LIKE("13"), | ||||
|     TAGGED_COMMENT("14"), | ||||
|     RESPONDED_STORY("213"), | ||||
|     // efr - random value | ||||
|     REQUEST("REQUEST"), | ||||
|     // ayml - random value | ||||
|     AYML("AYML"); | ||||
| 
 | ||||
|     private final String itemType; | ||||
|     private static final Map<String, NotificationType> map = new HashMap<>(); | ||||
|  | ||||
							
								
								
									
										53
									
								
								app/src/main/java/awais/instagrabber/models/stickers/SliderModel.java
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										53
									
								
								app/src/main/java/awais/instagrabber/models/stickers/SliderModel.java
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,53 @@ | ||||
| package awais.instagrabber.models.stickers; | ||||
| 
 | ||||
| import java.io.Serializable; | ||||
| 
 | ||||
| public final class SliderModel implements Serializable { | ||||
|     private final int voteCount; | ||||
|     private final Double average; | ||||
|     private Double myChoice; | ||||
|     private final boolean canVote; | ||||
|     private final String id, question, emoji; | ||||
| 
 | ||||
|     public SliderModel(final String id, final String question, final String emoji, final boolean canVote, | ||||
|                        final Double average, final int voteCount, final Double myChoice) { | ||||
|         this.id = id; | ||||
|         this.question = question; | ||||
|         this.emoji = emoji; | ||||
|         this.canVote = canVote; | ||||
|         this.average = average; | ||||
|         this.voteCount = voteCount; | ||||
|         this.myChoice = myChoice; | ||||
|     } | ||||
| 
 | ||||
|     public String getId() { | ||||
|         return id; | ||||
|     } | ||||
| 
 | ||||
|     public String getQuestion() { | ||||
|         return question; | ||||
|     } | ||||
| 
 | ||||
|     public String getEmoji() { | ||||
|         return emoji; | ||||
|     } | ||||
| 
 | ||||
|     public boolean canVote() { | ||||
|         return canVote; | ||||
|     } | ||||
| 
 | ||||
|     public int getVoteCount() { | ||||
|         return voteCount; | ||||
|     } | ||||
| 
 | ||||
|     public Double getAverage() { | ||||
|         return average; | ||||
|     } | ||||
| 
 | ||||
|     public Double getMyChoice() { return myChoice; } | ||||
| 
 | ||||
|     public Double setMyChoice(final Double choice) { | ||||
|         this.myChoice = choice; | ||||
|         return choice; | ||||
|     } | ||||
| } | ||||
| @ -3,10 +3,12 @@ package awais.instagrabber.repositories; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import retrofit2.Call; | ||||
| import retrofit2.http.GET; | ||||
| import retrofit2.http.QueryMap; | ||||
| import retrofit2.http.FieldMap; | ||||
| import retrofit2.http.FormUrlEncoded; | ||||
| import retrofit2.http.POST; | ||||
| 
 | ||||
| public interface FeedRepository { | ||||
|     @GET("/graphql/query/") | ||||
|     Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams); | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/feed/timeline/") | ||||
|     Call<String> fetch(@FieldMap final Map<String, String> signedForm); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,12 @@ | ||||
| package awais.instagrabber.repositories; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import retrofit2.Call; | ||||
| import retrofit2.http.GET; | ||||
| import retrofit2.http.QueryMap; | ||||
| 
 | ||||
| public interface GraphQLRepository { | ||||
|     @GET("/graphql/query/") | ||||
|     Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams); | ||||
| } | ||||
| @ -12,7 +12,4 @@ public interface LocationRepository { | ||||
|     @GET("/api/v1/feed/location/{location}/") | ||||
|     Call<String> fetchPosts(@Path("location") final String locationId, | ||||
|                             @QueryMap Map<String, String> queryParams); | ||||
| 
 | ||||
|     @GET("/graphql/query/") | ||||
|     Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams); | ||||
| } | ||||
|  | ||||
| @ -5,48 +5,57 @@ import java.util.Map; | ||||
| import retrofit2.Call; | ||||
| import retrofit2.http.FieldMap; | ||||
| import retrofit2.http.FormUrlEncoded; | ||||
| import retrofit2.http.GET; | ||||
| import retrofit2.http.Header; | ||||
| import retrofit2.http.POST; | ||||
| import retrofit2.http.Path; | ||||
| import retrofit2.http.QueryMap; | ||||
| 
 | ||||
| public interface MediaRepository { | ||||
|     @GET("/api/v1/media/{mediaId}/info/") | ||||
|     Call<String> fetch(@Path("mediaId") final String mediaId); | ||||
| 
 | ||||
|     @GET("/api/v1/media/{mediaId}/{action}/") | ||||
|     Call<String> fetchLikes(@Path("mediaId") final String mediaId, | ||||
|                             @Path("action") final String action); // one of "likers" or "comment_likers" | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{mediaId}/{action}/") | ||||
|     Call<String> action(@Header("User-Agent") final String userAgent, | ||||
|                         @Path("action") final String action, | ||||
|     Call<String> action(@Path("action") final String action, | ||||
|                         @Path("mediaId") final String mediaId, | ||||
|                         @FieldMap final Map<String, String> signedForm); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{mediaId}/comment/") | ||||
|     Call<String> comment(@Header("User-Agent") final String userAgent, | ||||
|                          @Path("mediaId") final String mediaId, | ||||
|     Call<String> comment(@Path("mediaId") final String mediaId, | ||||
|                          @FieldMap final Map<String, String> signedForm); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{mediaId}/comment/bulk_delete/") | ||||
|     Call<String> commentsBulkDelete(@Header("User-Agent") final String userAgent, | ||||
|                                     @Path("mediaId") final String mediaId, | ||||
|     Call<String> commentsBulkDelete(@Path("mediaId") final String mediaId, | ||||
|                                     @FieldMap final Map<String, String> signedForm); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{commentId}/comment_like/") | ||||
|     Call<String> commentLike(@Header("User-Agent") final String userAgent, | ||||
|                              @Path("commentId") final String commentId, | ||||
|     Call<String> commentLike(@Path("commentId") final String commentId, | ||||
|                              @FieldMap final Map<String, String> signedForm); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{commentId}/comment_unlike/") | ||||
|     Call<String> commentUnlike(@Header("User-Agent") final String userAgent, | ||||
|                                @Path("commentId") final String commentId, | ||||
|     Call<String> commentUnlike(@Path("commentId") final String commentId, | ||||
|                                @FieldMap final Map<String, String> signedForm); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{mediaId}/edit_media/") | ||||
|     Call<String> editCaption(@Path("mediaId") final String mediaId, | ||||
|                              @FieldMap final Map<String, String> signedForm); | ||||
| 
 | ||||
|     @GET("/api/v1/language/translate/") | ||||
|     Call<String> translate(@QueryMap final Map<String, String> form); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/upload_finish/") | ||||
|     Call<String> uploadFinish(// @Header("User-Agent") final String userAgent, | ||||
|                               @Header("retry_context") final String retryContext, | ||||
|     Call<String> uploadFinish(@Header("retry_context") final String retryContext, | ||||
|                               @QueryMap Map<String, String> queryParams, | ||||
|                               @FieldMap final Map<String, String> signedForm); | ||||
| } | ||||
|  | ||||
| @ -6,16 +6,24 @@ import awais.instagrabber.utils.Constants; | ||||
| import retrofit2.Call; | ||||
| import retrofit2.http.FieldMap; | ||||
| import retrofit2.http.FormUrlEncoded; | ||||
| import retrofit2.http.GET; | ||||
| import retrofit2.http.Header; | ||||
| import retrofit2.http.Headers; | ||||
| import retrofit2.http.POST; | ||||
| import retrofit2.http.Query; | ||||
| 
 | ||||
| public interface NewsRepository { | ||||
| 
 | ||||
|     Call<String> inbox(); | ||||
|     @Headers("User-Agent: " + Constants.USER_AGENT) | ||||
|     @GET("https://www.instagram.com/accounts/activity/?__a=1") | ||||
|     Call<String> webInbox(); | ||||
| 
 | ||||
|     @Headers("User-Agent: " + Constants.I_USER_AGENT) | ||||
|     @GET("/api/v1/news/inbox/") | ||||
|     Call<String> appInbox(@Query(value = "mark_as_seen", encoded = true) boolean markAsSeen); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @Headers("User-Agent: " + Constants.USER_AGENT) | ||||
|     @POST("https://www.instagram.com/web/activity/mark_checked/") | ||||
|     Call<String> markChecked(@Header("x-csrftoken") String csrfToken, @FieldMap Map<String, String> map); | ||||
|     @Headers("User-Agent: " + Constants.I_USER_AGENT) | ||||
|     @POST("/api/v1/discover/ayml/") | ||||
|     Call<String> getAyml(@FieldMap final Map<String, String> form); | ||||
| } | ||||
|  | ||||
| @ -9,11 +9,11 @@ import retrofit2.http.QueryMap; | ||||
| 
 | ||||
| public interface ProfileRepository { | ||||
| 
 | ||||
|     @GET("api/v1/users/{uid}/info/") | ||||
|     @GET("/api/v1/users/{uid}/info/") | ||||
|     Call<String> getUserInfo(@Path("uid") final String uid); | ||||
| 
 | ||||
|     @GET("/graphql/query/") | ||||
|     Call<String> fetch(@QueryMap Map<String, String> queryMap); | ||||
|     @GET("/api/v1/feed/user/{uid}/") | ||||
|     Call<String> fetch(@Path("uid") final String uid, @QueryMap Map<String, String> queryParams); | ||||
| 
 | ||||
|     @GET("/api/v1/feed/saved/") | ||||
|     Call<String> fetchSaved(@QueryMap Map<String, String> queryParams); | ||||
|  | ||||
| @ -2,17 +2,40 @@ package awais.instagrabber.repositories; | ||||
| 
 | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import awais.instagrabber.repositories.responses.StoryStickerResponse; | ||||
| import retrofit2.Call; | ||||
| import retrofit2.http.FieldMap; | ||||
| import retrofit2.http.FormUrlEncoded; | ||||
| import retrofit2.http.GET; | ||||
| import retrofit2.http.Header; | ||||
| import retrofit2.http.Path; | ||||
| import retrofit2.http.POST; | ||||
| import retrofit2.http.QueryMap; | ||||
| import retrofit2.http.Url; | ||||
| 
 | ||||
| public interface StoriesRepository { | ||||
|     @GET("/api/v1/media/{mediaId}/info/") | ||||
|     Call<String> fetch(@Path("mediaId") final String mediaId); | ||||
|     // this one is the same as MediaRepository.fetch BUT you need to make sure it's a story | ||||
| 
 | ||||
|     @GET("graphql/query/") | ||||
|     Call<String> getStories(@QueryMap(encoded = true) Map<String, String> variables); | ||||
|     @GET("/api/v1/feed/reels_tray/") | ||||
|     Call<String> getFeedStories(); | ||||
| 
 | ||||
|     @GET("/api/v1/highlights/{uid}/highlights_tray/") | ||||
|     Call<String> fetchHighlights(@Path("uid") final String uid); | ||||
| 
 | ||||
|     @GET("/api/v1/archive/reel/day_shells/") | ||||
|     Call<String> fetchArchive(@QueryMap Map<String, String> queryParams); | ||||
| 
 | ||||
|     @GET | ||||
|     Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url); | ||||
| 
 | ||||
|     @FormUrlEncoded | ||||
|     @POST("/api/v1/media/{storyId}/{stickerId}/{action}/") | ||||
|     Call<StoryStickerResponse> respondToSticker(@Header("User-Agent") String userAgent, | ||||
|                                                 @Path("storyId") String storyId, | ||||
|                                                 @Path("stickerId") String stickerId, | ||||
|                                                 @Path("action") String action, | ||||
|                                                 // story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer | ||||
|                                                 @FieldMap Map<String, String> form); | ||||
| } | ||||
|  | ||||
| @ -24,7 +24,4 @@ public interface TagsRepository { | ||||
|     @GET("/api/v1/feed/tag/{tag}/") | ||||
|     Call<String> fetchPosts(@Path("tag") final String tag, | ||||
|                             @QueryMap Map<String, String> queryParams); | ||||
| 
 | ||||
|     @GET("/graphql/query/") | ||||
|     Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams); | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,79 @@ | ||||
| package awais.instagrabber.repositories.responses; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import java.util.List; | ||||
| import java.util.Objects; | ||||
| 
 | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| 
 | ||||
| public class GraphQLUserListFetchResponse { | ||||
|     private String nextMaxId; | ||||
|     private String status; | ||||
|     private List<ProfileModel> items; | ||||
| 
 | ||||
|     public GraphQLUserListFetchResponse(final String nextMaxId, | ||||
|                                         final String status, | ||||
|                                         final List<ProfileModel> items) { | ||||
|         this.nextMaxId = nextMaxId; | ||||
|         this.status = status; | ||||
|         this.items = items; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isMoreAvailable() { | ||||
|         return !TextUtils.isEmpty(nextMaxId); | ||||
|     } | ||||
| 
 | ||||
|     public String getNextMaxId() { | ||||
|         return nextMaxId; | ||||
|     } | ||||
| 
 | ||||
|     public GraphQLUserListFetchResponse setNextMaxId(final String nextMaxId) { | ||||
|         this.nextMaxId = nextMaxId; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public String getStatus() { | ||||
|         return status; | ||||
|     } | ||||
| 
 | ||||
|     public GraphQLUserListFetchResponse setStatus(final String status) { | ||||
|         this.status = status; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     public List<ProfileModel> getItems() { | ||||
|         return items; | ||||
|     } | ||||
| 
 | ||||
|     public GraphQLUserListFetchResponse setItems(final List<ProfileModel> items) { | ||||
|         this.items = items; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean equals(final Object o) { | ||||
|         if (this == o) return true; | ||||
|         if (o == null || getClass() != o.getClass()) return false; | ||||
|         final GraphQLUserListFetchResponse that = (GraphQLUserListFetchResponse) o; | ||||
|         return Objects.equals(nextMaxId, that.nextMaxId) && | ||||
|                 Objects.equals(status, that.status) && | ||||
|                 Objects.equals(items, that.items); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int hashCode() { | ||||
|         return Objects.hash(nextMaxId, status, items); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "GraphQLUserListFetchResponse{" + | ||||
|                 "nextMaxId='" + nextMaxId + '\'' + | ||||
|                 ", status='" + status + '\'' + | ||||
|                 ", items=" + items + | ||||
|                 '}'; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| package awais.instagrabber.repositories.responses; | ||||
| 
 | ||||
| public class StoryStickerResponse { | ||||
|     private final String status; | ||||
| 
 | ||||
|     public StoryStickerResponse(final String status) { | ||||
|         this.status = status; | ||||
|     } | ||||
| 
 | ||||
|     public String getStatus() { | ||||
|         return status; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "StoryStickerResponse{" + | ||||
|                 "status='" + status + '\'' + | ||||
|                 '}'; | ||||
|     } | ||||
| } | ||||
| @ -2,15 +2,18 @@ package awais.instagrabber.repositories.responses; | ||||
| 
 | ||||
| public class UserInfo { | ||||
|     private final String pk; | ||||
|     private final String username; | ||||
|     private final String fullName; | ||||
|     private final String profilePicUrl; | ||||
|     private final String username, fullName, profilePicUrl, hdProfilePicUrl; | ||||
| 
 | ||||
|     public UserInfo(final String pk, final String username, final String fullName, final String profilePicUrl) { | ||||
|     public UserInfo(final String pk, | ||||
|                     final String username, | ||||
|                     final String fullName, | ||||
|                     final String profilePicUrl, | ||||
|                     final String hdProfilePicUrl) { | ||||
|         this.pk = pk; | ||||
|         this.username = username; | ||||
|         this.fullName = fullName; | ||||
|         this.profilePicUrl = profilePicUrl; | ||||
|         this.hdProfilePicUrl = hdProfilePicUrl; | ||||
|     } | ||||
| 
 | ||||
|     public String getPk() { | ||||
| @ -29,6 +32,10 @@ public class UserInfo { | ||||
|         return profilePicUrl; | ||||
|     } | ||||
| 
 | ||||
|     public String getHDProfilePicUrl() { | ||||
|         return hdProfilePicUrl; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         return "UserInfo{" + | ||||
| @ -36,6 +43,7 @@ public class UserInfo { | ||||
|                 ", username='" + username + '\'' + | ||||
|                 ", fullName='" + fullName + '\'' + | ||||
|                 ", profilePicUrl='" + profilePicUrl + '\'' + | ||||
|                 ", hdProfilePicUrl='" + hdProfilePicUrl + '\'' + | ||||
|                 '}'; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -99,11 +99,11 @@ public class DirectUser { | ||||
|                 profileModel.getSdProfilePic(), | ||||
|                 null, | ||||
|                 new DirectUserFriendshipStatus( | ||||
|                         profileModel.getFollowing(), | ||||
|                         profileModel.getBlocked(), | ||||
|                         profileModel.isFollowing(), | ||||
|                         profileModel.isBlocked(), | ||||
|                         profileModel.isPrivate(), | ||||
|                         false, | ||||
|                         profileModel.getRequested(), | ||||
|                         profileModel.isRequested(), | ||||
|                         false | ||||
|                 ), | ||||
|                 profileModel.isVerified(), | ||||
|  | ||||
| @ -8,6 +8,7 @@ public final class Constants { | ||||
|     public static final String CUSTOM_DATE_TIME_FORMAT = "date_time_custom_format"; | ||||
|     public static final String APP_THEME = "app_theme_v19"; | ||||
|     public static final String APP_LANGUAGE = "app_language_v19"; | ||||
|     public static final String STORY_SORT = "story_sort"; | ||||
|     // int prefs | ||||
|     public static final String PREV_INSTALL_VERSION = "prevVersion"; | ||||
|     // boolean prefs | ||||
| @ -56,9 +57,9 @@ public final class Constants { | ||||
|     // spoof | ||||
|     public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) " + | ||||
|             "AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 " + | ||||
|             "Instagram 166.1.0.42.245 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 256099205)"; | ||||
|             "Instagram 169.1.0.29.135 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 262886998)"; | ||||
|     public static final String I_USER_AGENT = | ||||
|             "Instagram 166.1.0.42.245 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 256099205)"; | ||||
|             "Instagram 169.1.0.29.135 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 262886998)"; | ||||
|     public static final String A_USER_AGENT = "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me"; | ||||
|     // see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts | ||||
|     public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" + | ||||
|  | ||||
| @ -152,7 +152,7 @@ public final class ExportImportUtils { | ||||
|                 continue; | ||||
|             } | ||||
|             final Favorite favorite = new Favorite( | ||||
|                     -1, | ||||
|                     0, | ||||
|                     query, | ||||
|                     favoriteType, | ||||
|                     favsObject.optString("s"), | ||||
|  | ||||
| @ -73,6 +73,10 @@ public final class LocaleUtils { | ||||
|         if (appLanguageIndex == 14) return "zh_TW"; | ||||
|         if (appLanguageIndex == 15) return "ca"; | ||||
|         if (appLanguageIndex == 16) return "ru"; | ||||
|         if (appLanguageIndex == 17) return "hi"; | ||||
|         if (appLanguageIndex == 18) return "nl"; | ||||
|         if (appLanguageIndex == 19) return "sk"; | ||||
|         if (appLanguageIndex == 20) return "ja"; | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
| @ -20,11 +20,17 @@ import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.PostChild; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.models.enums.MediaItemType; | ||||
| import awais.instagrabber.models.StoryModel; | ||||
| import awais.instagrabber.repositories.responses.directmessages.DirectItem; | ||||
| import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory; | ||||
| import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt; | ||||
| import awais.instagrabber.repositories.responses.directmessages.ImageVersions2; | ||||
| import awais.instagrabber.repositories.responses.directmessages.MediaCandidate; | ||||
| import awais.instagrabber.models.stickers.PollModel; | ||||
| import awais.instagrabber.models.stickers.QuestionModel; | ||||
| import awais.instagrabber.models.stickers.QuizModel; | ||||
| import awais.instagrabber.models.stickers.SliderModel; | ||||
| import awais.instagrabber.models.stickers.SwipeUpModel; | ||||
| import awaisomereport.LogCollector; | ||||
| 
 | ||||
| public final class ResponseBodyUtils { | ||||
| @ -186,8 +192,8 @@ public final class ResponseBodyUtils { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     // public static DirectItemMediaModel getDirectMediaModel(final JSONObject mediaObj) throws Exception { | ||||
|     //     final DirectItemMediaModel mediaModel; | ||||
|     // public static DirectItemModel.DirectItemMediaModel getDirectMediaModel(final JSONObject mediaObj) throws Exception { | ||||
|     //     final DirectItemModel.DirectItemMediaModel mediaModel; | ||||
|     //     if (mediaObj == null) mediaModel = null; | ||||
|     //     else { | ||||
|     //         final JSONObject userObj = mediaObj.optJSONObject("user"); | ||||
| @ -203,7 +209,7 @@ public final class ResponseBodyUtils { | ||||
|     //                     userObj.getString("full_name"), | ||||
|     //                     null, null, | ||||
|     //                     userObj.getString("profile_pic_url"), | ||||
|     //                     null, 0, 0, 0, false, false, false, false); | ||||
|     //                     null, 0, 0, 0, false, false, false, false, false); | ||||
|     //         } | ||||
|     // | ||||
|     //         final MediaItemType mediaType = getMediaItemType(mediaObj.optInt("media_type", -1)); | ||||
| @ -212,7 +218,7 @@ public final class ResponseBodyUtils { | ||||
|     //         if (TextUtils.isEmpty(id)) id = null; | ||||
|     // | ||||
|     //         final ThumbnailDetails thumbnailDetails = getThumbnailUrl(mediaObj, mediaType); | ||||
|     //         mediaModel = new DirectItemMediaModel( | ||||
|     //         mediaModel = new DirectItemModel.DirectItemMediaModel( | ||||
|     //                 mediaType, | ||||
|     //                 mediaObj.optLong("expiring_at"), | ||||
|     //                 mediaObj.optLong("pk"), | ||||
| @ -226,7 +232,7 @@ public final class ResponseBodyUtils { | ||||
|     //     } | ||||
|     //     return mediaModel; | ||||
|     // } | ||||
| 
 | ||||
|     // | ||||
|     // private static DirectItemType getDirectItemType(final String itemType) { | ||||
|     //     if ("placeholder".equals(itemType)) return DirectItemType.PLACEHOLDER; | ||||
|     //     if ("media".equals(itemType)) return DirectItemType.MEDIA; | ||||
| @ -245,81 +251,99 @@ public final class ResponseBodyUtils { | ||||
|     //     if ("felix_share".equals(itemType)) return DirectItemType.FELIX_SHARE; | ||||
|     //     return DirectItemType.TEXT; | ||||
|     // } | ||||
| 
 | ||||
|     // | ||||
|     // @NonNull | ||||
|     // public static InboxThreadModel createInboxThreadModel(@NonNull final JSONObject data, final boolean inThreadView) throws Exception { | ||||
|     //     final InboxReadState readState = data.getInt("read_state") == 0 ? InboxReadState.STATE_READ : InboxReadState.STATE_UNREAD; | ||||
|     //     final String threadType = data.getString("thread_type"); // they're all "private", group is identified by boolean "is_group" | ||||
|     // | ||||
|     //     final JSONArray leftUsersJsonArray = data.optJSONArray("left_users"); | ||||
|     //     final String threadId = data.getString("thread_id"); | ||||
|     //     final String threadV2Id = data.getString("thread_v2_id"); | ||||
|     //     final String threadTitle = data.getString("thread_title"); | ||||
|     // | ||||
|     //     final JSONArray usersJsonArray = data.getJSONArray("users"); | ||||
|     //     final List<ProfileModel> users = new ArrayList<>(); | ||||
|     //     for (int j = 0; j < usersJsonArray.length(); ++j) { | ||||
|     //         final JSONObject userObject = usersJsonArray.getJSONObject(j); | ||||
|     //         users.add(new ProfileModel(userObject.optBoolean("is_private"), | ||||
|     //                                    false, | ||||
|     //                                    userObject.optBoolean("is_verified"), | ||||
|     //                                    String.valueOf(userObject.get("pk")), | ||||
|     //                                    userObject.optString("username"), // optional cuz fb users | ||||
|     //                                    userObject.getString("full_name"), | ||||
|     //                                    null, | ||||
|     //                                    null, | ||||
|     //                                    userObject.getString("profile_pic_url"), | ||||
|     //                                    null, | ||||
|     //                                    0, | ||||
|     //                                    0, | ||||
|     //                                    0, | ||||
|     //                                    false, | ||||
|     //                                    false, | ||||
|     //                                    false, | ||||
|     //                                    false)); | ||||
|     //     } | ||||
|     //     final String threadNewestCursor = data.optString("newest_cursor"); | ||||
|     //     final String threadOldestCursor = data.optString("oldest_cursor"); | ||||
|     //     final String threadNextCursor = data.has("next_cursor") ? data.getString("next_cursor") : null; | ||||
|     //     final String threadPrevCursor = data.has("prev_cursor") ? data.getString("prev_cursor") : null; | ||||
|     // | ||||
|     //     final List<ProfileModel> leftUsers = new ArrayList<>(); | ||||
|     //     for (int j = 0; j < leftUsersJsonArray.length(); ++j) { | ||||
|     //         final JSONObject userObject = leftUsersJsonArray.getJSONObject(j); | ||||
|     //         leftUsers.add(new ProfileModel(userObject.getBoolean("is_private"), | ||||
|     //                                        false, | ||||
|     //                                        userObject.optBoolean("is_verified"), | ||||
|     //                                        String.valueOf(userObject.get("pk")), | ||||
|     //                                        userObject.getString("username"), | ||||
|     //                                        userObject.getString("full_name"), | ||||
|     //                                        null, | ||||
|     //                                        null, | ||||
|     //                                        userObject.getString("profile_pic_url"), | ||||
|     //                                        null, | ||||
|     //                                        0, | ||||
|     //                                        0, | ||||
|     //                                        0, | ||||
|     //                                        false, | ||||
|     //                                        false, | ||||
|     //                                        false, | ||||
|     //                                        false)); | ||||
|     //     } | ||||
|     //     final boolean threadHasOlder = data.getBoolean("has_older"); | ||||
|     //     final long unreadCount = data.optLong("read_state", 0); | ||||
|     // | ||||
|     //     final long lastActivityAt = data.optLong("last_activity_at"); | ||||
|     //     final boolean named = data.optBoolean("named"); | ||||
|     //     final boolean muted = data.optBoolean("muted"); | ||||
|     //     final boolean isPin = data.optBoolean("is_pin"); | ||||
|     //     final boolean isSpam = data.optBoolean("is_spam"); | ||||
|     //     final boolean isGroup = data.optBoolean("is_group"); | ||||
|     //     final boolean pending = data.optBoolean("pending"); | ||||
|     //     final boolean archived = data.optBoolean("archived"); | ||||
|     //     final boolean canonical = data.optBoolean("canonical"); | ||||
|     // | ||||
|     //     final JSONArray users = data.getJSONArray("users"); | ||||
|     //     final int usersLen = users.length(); | ||||
|     //     final JSONArray leftusers = data.optJSONArray("left_users"); | ||||
|     //     final int leftusersLen = leftusers == null ? 0 : leftusers.length(); | ||||
|     //     final JSONArray admins = data.getJSONArray("admin_user_ids"); | ||||
|     //     final List<Long> adminIDs = new ArrayList<>(); | ||||
|     //     for (int j = 0; j < admins.length(); ++j) { | ||||
|     //         adminIDs.add(admins.getLong(j)); | ||||
|     //     final int adminsLen = admins.length(); | ||||
|     // | ||||
|     //     final ProfileModel[] userModels = new ProfileModel[usersLen]; | ||||
|     //     for (int j = 0; j < usersLen; ++j) { | ||||
|     //         final JSONObject userObject = users.getJSONObject(j); | ||||
|     //         userModels[j] = new ProfileModel(userObject.optBoolean("is_private"), | ||||
|     //                                          false, | ||||
|     //                                          userObject.optBoolean("is_verified"), | ||||
|     //                                          String.valueOf(userObject.get("pk")), | ||||
|     //                                          userObject.optString("username"), // optional cuz fb users | ||||
|     //                                          userObject.getString("full_name"), | ||||
|     //                                          null, null, | ||||
|     //                                          userObject.getString("profile_pic_url"), | ||||
|     //                                          null, 0, 0, 0, false, false, false, false, false); | ||||
|     //     } | ||||
|     // | ||||
|     //     final ProfileModel[] leftuserModels = new ProfileModel[leftusersLen]; | ||||
|     //     for (int j = 0; j < leftusersLen; ++j) { | ||||
|     //         final JSONObject userObject = leftusers.getJSONObject(j); | ||||
|     //         leftuserModels[j] = new ProfileModel(userObject.getBoolean("is_private"), | ||||
|     //                                              false, | ||||
|     //                                              userObject.optBoolean("is_verified"), | ||||
|     //                                              String.valueOf(userObject.get("pk")), | ||||
|     //                                              userObject.getString("username"), | ||||
|     //                                              userObject.getString("full_name"), | ||||
|     //                                              null, null, | ||||
|     //                                              userObject.getString("profile_pic_url"), | ||||
|     //                                              null, 0, 0, 0, false, false, false, false, false); | ||||
|     //     } | ||||
|     // | ||||
|     //     final Long[] adminIDs = new Long[adminsLen]; | ||||
|     //     for (int j = 0; j < adminsLen; ++j) { | ||||
|     //         adminIDs[j] = admins.getLong(j); | ||||
|     //     } | ||||
|     // | ||||
|     //     final JSONArray items = data.getJSONArray("items"); | ||||
|     //     final int itemsLen = items.length(); | ||||
|     // | ||||
|     //     final List<DirectItemModel> itemModels = new ArrayList<>(itemsLen); | ||||
|     //     final ArrayList<DirectItemModel> itemModels = new ArrayList<>(itemsLen); | ||||
|     //     for (int i = 0; i < itemsLen; ++i) { | ||||
|     //         final JSONObject itemObject = items.getJSONObject(i); | ||||
|     // | ||||
|     //         CharSequence text = null; | ||||
|     //         ProfileModel profileModel = null; | ||||
|     //         DMItemMediaModel mediaModel = null; | ||||
|     //         DirectItemModel.DirectItemLinkModel linkModel = null; | ||||
|     //         DirectItemModel.DirectItemMediaModel directMedia = null; | ||||
|     //         DirectItemModel.DirectItemReelShareModel reelShareModel = null; | ||||
|     //         DirectItemModel.DirectItemActionLogModel actionLogModel = null; | ||||
|     //         DirectItemModel.DirectItemAnimatedMediaModel animatedMediaModel = null; | ||||
|     //         DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = null; | ||||
|     //         DirectItemModel.DirectItemRavenMediaModel ravenMediaModel = null; | ||||
|     //         DirectItemModel.DirectItemVideoCallEventModel videoCallEventModel = null; | ||||
|     // | ||||
|     //         final DirectItemType itemType = getDirectItemType(itemObject.getString("item_type")); | ||||
|     //         switch (itemType) { | ||||
|     //             case ANIMATED_MEDIA: { | ||||
|     //                 final JSONObject animatedMedia = itemObject.getJSONObject("animated_media"); | ||||
|     //                 final JSONObject stickerImage = animatedMedia.getJSONObject("images").getJSONObject("fixed_height"); | ||||
|     //                 mediaModel = new DirectItemAnimatedMediaModel( | ||||
|     // | ||||
|     //                 animatedMediaModel = new DirectItemModel.DirectItemAnimatedMediaModel( | ||||
|     //                         animatedMedia.getBoolean("is_random"), | ||||
|     //                         animatedMedia.getBoolean("is_sticker"), | ||||
|     //                         animatedMedia.getString("id"), | ||||
| @ -330,9 +354,11 @@ public final class ResponseBodyUtils { | ||||
|     //                         stickerImage.getInt("width")); | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case VOICE_MEDIA: { | ||||
|     //                 final JSONObject voiceMedia = itemObject.getJSONObject("voice_media").getJSONObject("media"); | ||||
|     //                 final JSONObject audio = voiceMedia.getJSONObject("audio"); | ||||
|     // | ||||
|     //                 int[] waveformData = null; | ||||
|     //                 final JSONArray waveformDataArray = audio.optJSONArray("waveform_data"); | ||||
|     //                 if (waveformDataArray != null) { | ||||
| @ -343,35 +369,40 @@ public final class ResponseBodyUtils { | ||||
|     //                         waveformData[j] = (int) (waveformDataArray.optDouble(j) * 10); | ||||
|     //                     } | ||||
|     //                 } | ||||
|     //                 mediaModel = new DirectItemVoiceMediaModel( | ||||
|     // | ||||
|     //                 voiceMediaModel = new DirectItemModel.DirectItemVoiceMediaModel( | ||||
|     //                         voiceMedia.getString("id"), | ||||
|     //                         audio.getString("audio_src"), | ||||
|     //                         audio.getLong("duration"), | ||||
|     //                         waveformData); | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case LINK: { | ||||
|     //                 final JSONObject linkObj = itemObject.getJSONObject("link"); | ||||
|     //                 DirectItemLinkContext itemLinkContext = null; | ||||
|     // | ||||
|     //                 DirectItemModel.DirectItemLinkContext itemLinkContext = null; | ||||
|     //                 final JSONObject linkContext = linkObj.optJSONObject("link_context"); | ||||
|     //                 if (linkContext != null) { | ||||
|     //                     itemLinkContext = new DirectItemLinkContext( | ||||
|     //                     itemLinkContext = new DirectItemModel.DirectItemLinkContext( | ||||
|     //                             linkContext.getString("link_url"), | ||||
|     //                             linkContext.optString("link_title"), | ||||
|     //                             linkContext.optString("link_summary"), | ||||
|     //                             linkContext.optString("link_image_url") | ||||
|     //                     ); | ||||
|     //                 } | ||||
|     //                 mediaModel = new DirectItemLinkModel( | ||||
|     // | ||||
|     //                 linkModel = new DirectItemModel.DirectItemLinkModel( | ||||
|     //                         linkObj.getString("text"), | ||||
|     //                         linkObj.getString("client_context"), | ||||
|     //                         linkObj.optString("mutation_token"), | ||||
|     //                         itemLinkContext); | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case REEL_SHARE: { | ||||
|     //                 final JSONObject reelShare = itemObject.getJSONObject("reel_share"); | ||||
|     //                 mediaModel = new DirectItemReelShareModel( | ||||
|     //                 reelShareModel = new DirectItemModel.DirectItemReelShareModel( | ||||
|     //                         reelShare.optBoolean("is_reel_persisted"), | ||||
|     //                         reelShare.getLong("reel_owner_id"), | ||||
|     //                         reelShare.getJSONObject("media").getJSONObject("user").getString("username"), | ||||
| @ -383,34 +414,39 @@ public final class ResponseBodyUtils { | ||||
|     //                         getDirectMediaModel(reelShare.optJSONObject("media"))); | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case RAVEN_MEDIA: { | ||||
|     //                 final JSONObject visualMedia = itemObject.getJSONObject("visual_media"); | ||||
|     // | ||||
|     //                 final JSONArray seenUserIdsArray = visualMedia.getJSONArray("seen_user_ids"); | ||||
|     //                 final int seenUsersLen = seenUserIdsArray.length(); | ||||
|     //                 final String[] seenUserIds = new String[seenUsersLen]; | ||||
|     //                 for (int j = 0; j < seenUsersLen; j++) | ||||
|     //                     seenUserIds[j] = seenUserIdsArray.getString(j); | ||||
|     //                 RavenExpiringMediaActionSummary expiringSummaryModel = null; | ||||
|     // | ||||
|     //                 DirectItemModel.RavenExpiringMediaActionSummaryModel expiringSummaryModel = null; | ||||
|     //                 final JSONObject actionSummary = visualMedia.optJSONObject("expiring_media_action_summary"); | ||||
|     //                 if (actionSummary != null) | ||||
|     //                     expiringSummaryModel = new RavenExpiringMediaActionSummary( | ||||
|     //                     expiringSummaryModel = new DirectItemModel.RavenExpiringMediaActionSummaryModel( | ||||
|     //                             actionSummary.getLong("timestamp"), actionSummary.getInt("count"), | ||||
|     //                             getExpiringMediaType(actionSummary.getString("type"))); | ||||
|     //                 final RavenMediaViewMode viewType; | ||||
|     // | ||||
|     //                 final RavenMediaViewType viewType; | ||||
|     //                 final String viewMode = visualMedia.getString("view_mode"); | ||||
|     //                 switch (viewMode) { | ||||
|     //                     case "replayable": | ||||
|     //                         viewType = RavenMediaViewMode.REPLAYABLE; | ||||
|     //                         viewType = RavenMediaViewType.REPLAYABLE; | ||||
|     //                         break; | ||||
|     //                     case "permanent": | ||||
|     //                         viewType = RavenMediaViewMode.PERMANENT; | ||||
|     //                         viewType = RavenMediaViewType.PERMANENT; | ||||
|     //                         break; | ||||
|     //                     case "once": | ||||
|     //                     default: | ||||
|     //                         viewType = RavenMediaViewMode.ONCE; | ||||
|     //                         viewType = RavenMediaViewType.ONCE; | ||||
|     //                 } | ||||
|     //                 mediaModel = new DirectItemRavenMediaModel( | ||||
|     //                         visualMedia.optLong(viewType == RavenMediaViewMode.PERMANENT ? "url_expire_at_secs" : "replay_expiring_at_us"), | ||||
|     // | ||||
|     //                 ravenMediaModel = new DirectItemModel.DirectItemRavenMediaModel( | ||||
|     //                         visualMedia.optLong(viewType == RavenMediaViewType.PERMANENT ? "url_expire_at_secs" : "replay_expiring_at_us"), | ||||
|     //                         visualMedia.optInt("playback_duration_secs"), | ||||
|     //                         visualMedia.getInt("seen_count"), | ||||
|     //                         seenUserIds, | ||||
| @ -420,14 +456,16 @@ public final class ResponseBodyUtils { | ||||
|     // | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case VIDEO_CALL_EVENT: { | ||||
|     //                 final JSONObject videoCallEvent = itemObject.getJSONObject("video_call_event"); | ||||
|     //                 mediaModel = new DirectItemVideoCallEventModel(videoCallEvent.getLong("vc_id"), | ||||
|     //                                                                videoCallEvent.optBoolean("thread_has_audio_only_call"), | ||||
|     //                                                                videoCallEvent.getString("action"), | ||||
|     //                                                                videoCallEvent.getString("description")); | ||||
|     //                 videoCallEventModel = new DirectItemModel.DirectItemVideoCallEventModel(videoCallEvent.optLong("vc_id"), | ||||
|     //                                                                                         videoCallEvent.optBoolean("thread_has_audio_only_call"), | ||||
|     //                                                                                         videoCallEvent.getString("action"), | ||||
|     //                                                                                         videoCallEvent.getString("description")); | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case PROFILE: { | ||||
|     //                 final JSONObject profile = itemObject.getJSONObject("profile"); | ||||
|     //                 profileModel = new ProfileModel(profile.getBoolean("is_private"), | ||||
| @ -438,13 +476,15 @@ public final class ResponseBodyUtils { | ||||
|     //                                                 profile.getString("full_name"), | ||||
|     //                                                 null, null, | ||||
|     //                                                 profile.getString("profile_pic_url"), | ||||
|     //                                                 null, 0, 0, 0, false, false, false, false); | ||||
|     //                                                 null, 0, 0, 0, false, false, false, false, false); | ||||
|     //             } | ||||
|     //             break; | ||||
|     // | ||||
|     //             case PLACEHOLDER: | ||||
|     //                 final JSONObject placeholder = itemObject.getJSONObject("placeholder"); | ||||
|     //                 text = placeholder.getString("title") + "<br><small>" + placeholder.getString("message") + "</small>"; | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case ACTION_LOG: | ||||
|     //                 if (inThreadView && itemObject.optInt("hide_in_thread", 0) != 0) | ||||
|     //                     continue; | ||||
| @ -457,29 +497,35 @@ public final class ResponseBodyUtils { | ||||
|     //                             + desc.substring(boldItem.getInt("start") + q * 7, boldItem.getInt("end") + q * 7) | ||||
|     //                             + "</b>" + desc.substring(boldItem.getInt("end") + q * 7); | ||||
|     //                 } | ||||
|     //                 mediaModel = new DirectItemActionLogModel(desc); | ||||
|     //                 actionLogModel = new DirectItemModel.DirectItemActionLogModel(desc); | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case MEDIA_SHARE: | ||||
|     //                 mediaModel = getDirectMediaModel(itemObject.getJSONObject("media_share")); | ||||
|     //                 directMedia = getDirectMediaModel(itemObject.getJSONObject("media_share")); | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case CLIP: | ||||
|     //                 mediaModel = getDirectMediaModel(itemObject.getJSONObject("clip").getJSONObject("clip")); | ||||
|     //                 directMedia = getDirectMediaModel(itemObject.getJSONObject("clip").getJSONObject("clip")); | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case FELIX_SHARE: | ||||
|     //                 mediaModel = getDirectMediaModel(itemObject.getJSONObject("felix_share").getJSONObject("video")); | ||||
|     //                 directMedia = getDirectMediaModel(itemObject.getJSONObject("felix_share").getJSONObject("video")); | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case MEDIA: | ||||
|     //                 mediaModel = getDirectMediaModel(itemObject.optJSONObject("media")); | ||||
|     //                 directMedia = getDirectMediaModel(itemObject.optJSONObject("media")); | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case LIKE: | ||||
|     //                 text = itemObject.getString("like"); | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case STORY_SHARE: | ||||
|     //                 final JSONObject storyShare = itemObject.getJSONObject("story_share"); | ||||
|     //                 if (!storyShare.has("media")) | ||||
|     //                     text = "<small>" + storyShare.optString("message") + "</small>"; | ||||
|     //                 else { | ||||
|     //                     mediaModel = new DirectItemReelShareModel( | ||||
|     //                     reelShareModel = new DirectItemModel.DirectItemReelShareModel( | ||||
|     //                             storyShare.optBoolean("is_reel_persisted"), | ||||
|     //                             storyShare.getJSONObject("media").getJSONObject("user").getLong("pk"), | ||||
|     //                             storyShare.getJSONObject("media").getJSONObject("user").getString("username"), | ||||
| @ -491,6 +537,7 @@ public final class ResponseBodyUtils { | ||||
|     //                             getDirectMediaModel(storyShare.optJSONObject("media"))); | ||||
|     //                 } | ||||
|     //                 break; | ||||
|     // | ||||
|     //             case TEXT: | ||||
|     //                 if (!itemObject.has("text")) | ||||
|     //                     Log.d("AWAISKING_APP", "itemObject: " + itemObject); // todo | ||||
| @ -514,49 +561,40 @@ public final class ResponseBodyUtils { | ||||
|     //                 liked, | ||||
|     //                 itemType, | ||||
|     //                 text, | ||||
|     //                 linkModel, | ||||
|     //                 profileModel, | ||||
|     //                 mediaModel)); | ||||
|     //                 reelShareModel, | ||||
|     //                 directMedia, | ||||
|     //                 actionLogModel, | ||||
|     //                 voiceMediaModel, | ||||
|     //                 ravenMediaModel, | ||||
|     //                 videoCallEventModel, | ||||
|     //                 animatedMediaModel)); | ||||
|     //     } | ||||
|     // | ||||
|     //     return new InboxThreadModel(readState, | ||||
|     //                                 data.getString("thread_id"), | ||||
|     //                                 data.getString("thread_v2_id"), | ||||
|     //                                 data.getString("thread_type"), | ||||
|     //                                 data.getString("thread_title"), | ||||
|     //                                 data.optString("newest_cursor"), | ||||
|     //                                 data.optString("oldest_cursor"), | ||||
|     //                                 data.has("next_cursor") ? data.getString("next_cursor") : null, | ||||
|     //                                 data.has("prev_cursor") ? data.getString("prev_cursor") : null, | ||||
|     //     itemModels.trimToSize(); | ||||
|     // | ||||
|     //     return new InboxThreadModel(readState, threadId, threadV2Id, threadType, threadTitle, | ||||
|     //                                 threadNewestCursor, threadOldestCursor, threadNextCursor, threadPrevCursor, | ||||
|     //                                 null, // todo | ||||
|     //                                 users, | ||||
|     //                                 leftUsers, | ||||
|     //                                 adminIDs, | ||||
|     //                                 itemModels, | ||||
|     //                                 data.optBoolean("muted"), | ||||
|     //                                 data.optBoolean("is_pin"), | ||||
|     //                                 data.optBoolean("named"), | ||||
|     //                                 data.optBoolean("canonical"), | ||||
|     //                                 data.optBoolean("pending"), | ||||
|     //                                 data.getBoolean("has_older"), | ||||
|     //                                 data.optLong("read_state", 0), | ||||
|     //                                 data.optBoolean("is_spam"), | ||||
|     //                                 data.optBoolean("is_group"), | ||||
|     //                                 data.optBoolean("archived"), | ||||
|     //                                 data.optLong("last_activity_at")); | ||||
|     //                                 userModels, leftuserModels, adminIDs, | ||||
|     //                                 itemModels.toArray(new DirectItemModel[0]), | ||||
|     //                                 muted, isPin, named, canonical, | ||||
|     //                                 pending, threadHasOlder, unreadCount, isSpam, isGroup, archived, lastActivityAt); | ||||
|     // } | ||||
| 
 | ||||
|     // private static ActionType getExpiringMediaType(final String type) { | ||||
|     //     if ("raven_sent".equals(type)) return ActionType.SENT; | ||||
|     //     if ("raven_opened".equals(type)) return ActionType.OPENED; | ||||
|     //     if ("raven_blocked".equals(type)) return ActionType.BLOCKED; | ||||
|     //     if ("raven_sending".equals(type)) return ActionType.SENDING; | ||||
|     //     if ("raven_replayed".equals(type)) return ActionType.REPLAYED; | ||||
|     //     if ("raven_delivered".equals(type)) return ActionType.DELIVERED; | ||||
|     //     if ("raven_suggested".equals(type)) return ActionType.SUGGESTED; | ||||
|     //     if ("raven_screenshot".equals(type)) return ActionType.SCREENSHOT; | ||||
|     //     if ("raven_cannot_deliver".equals(type)) return ActionType.CANNOT_DELIVER; | ||||
|     // | ||||
|     // private static RavenExpiringMediaType getExpiringMediaType(final String type) { | ||||
|     //     if ("raven_sent".equals(type)) return RavenExpiringMediaType.RAVEN_SENT; | ||||
|     //     if ("raven_opened".equals(type)) return RavenExpiringMediaType.RAVEN_OPENED; | ||||
|     //     if ("raven_blocked".equals(type)) return RavenExpiringMediaType.RAVEN_BLOCKED; | ||||
|     //     if ("raven_sending".equals(type)) return RavenExpiringMediaType.RAVEN_SENDING; | ||||
|     //     if ("raven_replayed".equals(type)) return RavenExpiringMediaType.RAVEN_REPLAYED; | ||||
|     //     if ("raven_delivered".equals(type)) return RavenExpiringMediaType.RAVEN_DELIVERED; | ||||
|     //     if ("raven_suggested".equals(type)) return RavenExpiringMediaType.RAVEN_SUGGESTED; | ||||
|     //     if ("raven_screenshot".equals(type)) return RavenExpiringMediaType.RAVEN_SCREENSHOT; | ||||
|     //     if ("raven_cannot_deliver".equals(type)) return RavenExpiringMediaType.RAVEN_CANNOT_DELIVER; | ||||
|     //     //if ("raven_unknown".equals(type)) [default?] | ||||
|     //     return ActionType.UNKNOWN; | ||||
|     //     return RavenExpiringMediaType.RAVEN_UNKNOWN; | ||||
|     // } | ||||
| 
 | ||||
|     public static FeedModel parseItem(final JSONObject itemJson) throws JSONException { | ||||
| @ -590,6 +628,7 @@ public final class ResponseBodyUtils { | ||||
|                     0, | ||||
|                     0, | ||||
|                     following, | ||||
|                     false, | ||||
|                     restricted, | ||||
|                     false, | ||||
|                     requested); | ||||
| @ -606,7 +645,8 @@ public final class ResponseBodyUtils { | ||||
|                 .setPostId(itemJson.getString(Constants.EXTRAS_ID)) | ||||
|                 .setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(itemJson) : null) | ||||
|                 .setShortCode(itemJson.getString("code")) | ||||
|                 .setPostCaption(captionJson != null ? captionJson.optString("text") : null) | ||||
|                 .setPostCaption(captionJson != null ? captionJson.optString("text") : "") | ||||
|                 .setCaptionId(captionJson != null ? captionJson.optString("pk") : null) | ||||
|                 .setCommentsCount(itemJson.optInt("comment_count")) | ||||
|                 .setTimestamp(itemJson.optLong("taken_at", -1)) | ||||
|                 .setLiked(itemJson.optBoolean("has_liked")) | ||||
| @ -627,7 +667,9 @@ public final class ResponseBodyUtils { | ||||
|                 break; | ||||
|             case MEDIA_TYPE_SLIDER: | ||||
|                 final List<PostChild> childPosts = getChildPosts(itemJson); | ||||
|                 feedModelBuilder.setSliderItems(childPosts); | ||||
|                 feedModelBuilder.setSliderItems(childPosts) | ||||
|                                 .setImageHeight(childPosts.get(0).getHeight()) | ||||
|                                 .setImageWidth(childPosts.get(0).getWidth()); | ||||
|                 break; | ||||
|         } | ||||
|         return feedModelBuilder.build(); | ||||
| @ -673,6 +715,7 @@ public final class ResponseBodyUtils { | ||||
|                     false, | ||||
|                     false, | ||||
|                     false, | ||||
|                     false, | ||||
|                     false); | ||||
|         } | ||||
|         JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); | ||||
| @ -743,7 +786,9 @@ public final class ResponseBodyUtils { | ||||
|                 final JSONArray children = sidecar.optJSONArray("edges"); | ||||
|                 if (children != null) { | ||||
|                     final List<PostChild> sliderItems = getSliderItems(children); | ||||
|                     feedModelBuilder.setSliderItems(sliderItems); | ||||
|                     feedModelBuilder.setSliderItems(sliderItems) | ||||
|                             .setImageHeight(sliderItems.get(0).getHeight()) | ||||
|                             .setImageWidth(sliderItems.get(0).getWidth()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @ -832,6 +877,134 @@ public final class ResponseBodyUtils { | ||||
|         return sliderItems; | ||||
|     } | ||||
| 
 | ||||
|     public static StoryModel parseStoryItem(final JSONObject data, | ||||
|                                             final boolean isLoc, | ||||
|                                             final boolean isHashtag, | ||||
|                                             final String localUsername) throws JSONException { | ||||
|         final boolean isVideo = data.has("video_duration"); | ||||
|         final StoryModel model = new StoryModel(data.getString("id"), | ||||
|                 data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0) | ||||
|                         .getString("url"), null, | ||||
|                 isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, | ||||
|                 data.optLong("taken_at", 0), | ||||
|                 (isLoc || isHashtag) | ||||
|                         ? data.getJSONObject("user").getString("username") | ||||
|                         : localUsername, | ||||
|                 data.getJSONObject("user").getString("pk"), | ||||
|                 data.optBoolean("can_reply")); | ||||
| 
 | ||||
|         if (data.getJSONObject("image_versions2").getJSONArray("candidates").length() > 1) { | ||||
|             model.setThumbnail(data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1) | ||||
|                     .getString("url")); | ||||
|         } | ||||
| 
 | ||||
|         final JSONArray videoResources = data.optJSONArray("video_versions"); | ||||
|         if (isVideo && videoResources != null) | ||||
|             model.setVideoUrl(ResponseBodyUtils.getHighQualityPost(videoResources, true, true, false)); | ||||
| 
 | ||||
|         if (data.has("story_feed_media")) { | ||||
|             model.setTappableShortCode(data.getJSONArray("story_feed_media").getJSONObject(0).optString("media_code")); | ||||
|         } | ||||
| 
 | ||||
|         // TODO: this may not be limited to spotify | ||||
|         if (!data.isNull("story_app_attribution")) | ||||
|             model.setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]); | ||||
| 
 | ||||
|         if (data.has("story_polls")) { | ||||
|             final JSONArray storyPolls = data.optJSONArray("story_polls"); | ||||
|             JSONObject tappableObject = null; | ||||
|             if (storyPolls != null) { | ||||
|                 tappableObject = storyPolls.getJSONObject(0).optJSONObject("poll_sticker"); | ||||
|             } | ||||
|             if (tappableObject != null) model.setPoll(new PollModel( | ||||
|                     String.valueOf(tappableObject.getLong("poll_id")), | ||||
|                     tappableObject.getString("question"), | ||||
|                     tappableObject.getJSONArray("tallies").getJSONObject(0).getString("text"), | ||||
|                     tappableObject.getJSONArray("tallies").getJSONObject(0).getInt("count"), | ||||
|                     tappableObject.getJSONArray("tallies").getJSONObject(1).getString("text"), | ||||
|                     tappableObject.getJSONArray("tallies").getJSONObject(1).getInt("count"), | ||||
|                     tappableObject.optInt("viewer_vote", -1) | ||||
|             )); | ||||
|         } | ||||
|         if (data.has("story_questions")) { | ||||
|             final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0) | ||||
|                     .optJSONObject("question_sticker"); | ||||
|             if (tappableObject != null && !tappableObject.getString("question_type").equals("music")) | ||||
|                 model.setQuestion(new QuestionModel( | ||||
|                         String.valueOf(tappableObject.getLong("question_id")), | ||||
|                         tappableObject.getString("question") | ||||
|                 )); | ||||
|         } | ||||
|         if (data.has("story_quizs")) { | ||||
|             JSONObject tappableObject = data.getJSONArray("story_quizs").getJSONObject(0).optJSONObject("quiz_sticker"); | ||||
|             if (tappableObject != null) { | ||||
|                 String[] choices = new String[tappableObject.getJSONArray("tallies").length()]; | ||||
|                 Long[] counts = new Long[choices.length]; | ||||
|                 for (int q = 0; q < choices.length; ++q) { | ||||
|                     JSONObject tempchoice = tappableObject.getJSONArray("tallies").getJSONObject(q); | ||||
|                     choices[q] = (q == tappableObject.getInt("correct_answer") ? "*** " : "") | ||||
|                             + tempchoice.getString("text"); | ||||
|                     counts[q] = tempchoice.getLong("count"); | ||||
|                 } | ||||
|                 model.setQuiz(new QuizModel( | ||||
|                         String.valueOf(tappableObject.getLong("quiz_id")), | ||||
|                         tappableObject.getString("question"), | ||||
|                         choices, | ||||
|                         counts, | ||||
|                         tappableObject.optInt("viewer_answer", -1) | ||||
|                 )); | ||||
|             } | ||||
|         } | ||||
|         if (data.has("story_cta") && data.has("link_text")) { | ||||
|             JSONObject tappableObject = data.getJSONArray("story_cta").getJSONObject(0).getJSONArray("links").getJSONObject(0); | ||||
|             String swipeUpUrl = tappableObject.getString("webUri"); | ||||
|             if (swipeUpUrl.startsWith("http")) { | ||||
|                 model.setSwipeUp(new SwipeUpModel(swipeUpUrl, data.getString("link_text"))); | ||||
|             } | ||||
|         } | ||||
|         if (data.has("story_sliders")) { | ||||
|             final JSONObject tappableObject = data.getJSONArray("story_sliders").getJSONObject(0) | ||||
|                     .optJSONObject("slider_sticker"); | ||||
|             if (tappableObject != null) | ||||
|                 model.setSlider(new SliderModel( | ||||
|                         String.valueOf(tappableObject.getLong("slider_id")), | ||||
|                         tappableObject.getString("question"), | ||||
|                         tappableObject.getString("emoji"), | ||||
|                         tappableObject.getBoolean("viewer_can_vote"), | ||||
|                         tappableObject.getDouble("slider_vote_average"), | ||||
|                         tappableObject.getInt("slider_vote_count"), | ||||
|                         tappableObject.optDouble("viewer_vote") | ||||
|                 )); | ||||
|         } | ||||
|         JSONArray hashtags = data.optJSONArray("story_hashtags"); | ||||
|         JSONArray locations = data.optJSONArray("story_locations"); | ||||
|         JSONArray atmarks = data.optJSONArray("reel_mentions"); | ||||
|         String[] mentions = new String[(hashtags == null ? 0 : hashtags.length()) | ||||
|                 + (atmarks == null ? 0 : atmarks.length()) | ||||
|                 + (locations == null ? 0 : locations.length())]; | ||||
|         if (hashtags != null) { | ||||
|             for (int h = 0; h < hashtags.length(); ++h) { | ||||
|                 mentions[h] = "#" + hashtags.getJSONObject(h).getJSONObject("hashtag").getString("name"); | ||||
|             } | ||||
|         } | ||||
|         if (atmarks != null) { | ||||
|             for (int h = 0; h < atmarks.length(); ++h) { | ||||
|                 mentions[h + (hashtags == null ? 0 : hashtags.length())] = | ||||
|                         "@" + atmarks.getJSONObject(h).getJSONObject("user").getString("username"); | ||||
|             } | ||||
|         } | ||||
|         if (locations != null) { | ||||
|             for (int h = 0; h < locations.length(); ++h) { | ||||
|                 mentions[h + (hashtags == null ? 0 : hashtags.length()) + (atmarks == null ? 0 : atmarks.length())] = | ||||
|                         locations.getJSONObject(h).getJSONObject("location").getString("short_name") | ||||
|                                 + " (" + locations.getJSONObject(h).getJSONObject("location").getLong("pk") + ")"; | ||||
|             } | ||||
|         } | ||||
|         if (mentions.length != 0) model.setMentions(mentions); | ||||
| 
 | ||||
|         return model; | ||||
|     } | ||||
| 
 | ||||
|     public static String getThumbUrl(final ImageVersions2 imageVersions2) { | ||||
|         if (imageVersions2 == null) return null; | ||||
|         final List<MediaCandidate> candidates = imageVersions2.getCandidates(); | ||||
|  | ||||
| @ -41,6 +41,7 @@ import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT; | ||||
| import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION; | ||||
| import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG; | ||||
| import static awais.instagrabber.utils.Constants.SKIPPED_VERSION; | ||||
| import static awais.instagrabber.utils.Constants.STORY_SORT; | ||||
| import static awais.instagrabber.utils.Constants.SWAP_DATE_TIME_FORMAT_ENABLED; | ||||
| 
 | ||||
| public final class SettingsHelper { | ||||
| @ -123,7 +124,7 @@ public final class SettingsHelper { | ||||
|             {APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT, | ||||
|                     DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, | ||||
|                     PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_POSTS_LAYOUT, | ||||
|                     PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, PREF_EMOJI_VARIANTS}) | ||||
|                     PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, STORY_SORT, PREF_EMOJI_VARIANTS}) | ||||
|     public @interface StringSettings {} | ||||
| 
 | ||||
|     @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, | ||||
|  | ||||
| @ -94,12 +94,9 @@ public final class Utils { | ||||
|         if (signed == null) { | ||||
|             return null; | ||||
|         } | ||||
|         final String[] parts = signed.split("&"); | ||||
|         final Map<String, String> map = new HashMap<>(); | ||||
|         for (final String part : parts) { | ||||
|             final String[] partSplit = part.split("="); | ||||
|             map.put(partSplit[0], partSplit[1]); | ||||
|         } | ||||
|         map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION); | ||||
|         map.put("signed_body", signed.split("&signed_body=")[1]); | ||||
|         return map; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,19 @@ | ||||
| package awais.instagrabber.viewmodels; | ||||
| 
 | ||||
| import androidx.lifecycle.MutableLiveData; | ||||
| import androidx.lifecycle.ViewModel; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.models.HighlightModel; | ||||
| 
 | ||||
| public class ArchivesViewModel extends ViewModel { | ||||
|     private MutableLiveData<List<HighlightModel>> list; | ||||
| 
 | ||||
|     public MutableLiveData<List<HighlightModel>> getList() { | ||||
|         if (list == null) { | ||||
|             list = new MutableLiveData<>(); | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
| } | ||||
| @ -16,4 +16,12 @@ public class BasePostViewModel<T extends BasePostModel> extends ViewModel { | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
| 
 | ||||
|     public void edit(int index, T post) { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public void delete(int index) { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
| @ -3,6 +3,7 @@ package awais.instagrabber.viewmodels; | ||||
| import androidx.lifecycle.MutableLiveData; | ||||
| import androidx.lifecycle.ViewModel; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.models.FeedStoryModel; | ||||
|  | ||||
| @ -0,0 +1,19 @@ | ||||
| package awais.instagrabber.viewmodels; | ||||
| 
 | ||||
| import androidx.lifecycle.MutableLiveData; | ||||
| import androidx.lifecycle.ViewModel; | ||||
| 
 | ||||
| import java.util.List; | ||||
| 
 | ||||
| import awais.instagrabber.models.FollowModel; | ||||
| 
 | ||||
| public class FollowViewModel extends ViewModel { | ||||
|     private MutableLiveData<List<FollowModel>> list; | ||||
| 
 | ||||
|     public MutableLiveData<List<FollowModel>> getList() { | ||||
|         if (list == null) { | ||||
|             list = new MutableLiveData<>(); | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
| } | ||||
| @ -6,16 +6,19 @@ import com.google.common.collect.ImmutableMap; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| 
 | ||||
| import java.io.UnsupportedEncodingException; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import awais.instagrabber.repositories.DirectMessagesRepository; | ||||
| import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions; | ||||
| import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds; | ||||
| import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions; | ||||
| import awais.instagrabber.repositories.requests.directmessages.PhotoBroadcastOptions; | ||||
| import awais.instagrabber.repositories.requests.directmessages.StoryReplyBroadcastOptions; | ||||
| import awais.instagrabber.repositories.requests.directmessages.TextBroadcastOptions; | ||||
| import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOptions; | ||||
| import awais.instagrabber.repositories.requests.directmessages.VoiceBroadcastOptions; | ||||
| @ -146,6 +149,12 @@ public class DirectMessagesService extends BaseService { | ||||
|         return broadcast(new VoiceBroadcastOptions(clientContext, threadIdOrUserIds, uploadId, waveform, samplingFreq)); | ||||
|     } | ||||
| 
 | ||||
|     public Call<DirectThreadBroadcastResponse> broadcastStoryReply(final ThreadIdOrUserIds threadIdOrUserIds, | ||||
|                                                                    final String text, | ||||
|                                                                    final String mediaId, final String reelId) throws UnsupportedEncodingException { | ||||
|         return broadcast(new StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdOrUserIds, text, mediaId, reelId)); | ||||
|     } | ||||
| 
 | ||||
|     private Call<DirectThreadBroadcastResponse> broadcast(@NonNull final BroadcastOptions broadcastOptions) { | ||||
|         if (TextUtils.isEmpty(broadcastOptions.getClientContext())) { | ||||
|             throw new IllegalArgumentException("Broadcast requires a valid client context value"); | ||||
|  | ||||
| @ -165,6 +165,7 @@ public class DiscoverService extends BaseService { | ||||
|                     false, | ||||
|                     false, | ||||
|                     false, | ||||
|                     false, | ||||
|                     false); | ||||
|         } | ||||
|         final String resourceUrl = ResponseBodyUtils.getHighQualityImage(coverMediaJson); | ||||
|  | ||||
| @ -19,6 +19,8 @@ import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.TimeZone; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.PostChild; | ||||
| @ -36,7 +38,6 @@ import retrofit2.Retrofit; | ||||
| 
 | ||||
| public class FeedService extends BaseService { | ||||
|     private static final String TAG = "FeedService"; | ||||
|     private static final boolean loadFromMock = false; | ||||
| 
 | ||||
|     private final FeedRepository repository; | ||||
| 
 | ||||
| @ -44,7 +45,7 @@ public class FeedService extends BaseService { | ||||
| 
 | ||||
|     private FeedService() { | ||||
|         final Retrofit retrofit = getRetrofitBuilder() | ||||
|                 .baseUrl("https://www.instagram.com") | ||||
|                 .baseUrl("https://i.instagram.com") | ||||
|                 .build(); | ||||
|         repository = retrofit.create(FeedRepository.class); | ||||
|     } | ||||
| @ -56,41 +57,26 @@ public class FeedService extends BaseService { | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     public void fetch(final int maxItemsToLoad, | ||||
|     public void fetch(final String csrfToken, | ||||
|                       final String cursor, | ||||
|                       final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         if (loadFromMock) { | ||||
|             final Handler handler = new Handler(); | ||||
|             handler.postDelayed(() -> { | ||||
|                 final ClassLoader classLoader = getClass().getClassLoader(); | ||||
|                 if (classLoader == null) { | ||||
|                     Log.e(TAG, "fetch: classLoader is null!"); | ||||
|                     return; | ||||
|                 } | ||||
|                 try (InputStream resourceAsStream = classLoader.getResourceAsStream("feed_response.json"); | ||||
|                      Reader in = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)) { | ||||
|                     final int bufferSize = 1024; | ||||
|                     final char[] buffer = new char[bufferSize]; | ||||
|                     final StringBuilder out = new StringBuilder(); | ||||
|                     int charsRead; | ||||
|                     while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) { | ||||
|                         out.append(buffer, 0, charsRead); | ||||
|                     } | ||||
|                     callback.onSuccess(parseResponseBody(out.toString())); | ||||
|                 } catch (IOException | JSONException e) { | ||||
|                     Log.e(TAG, "fetch: ", e); | ||||
|                 } | ||||
|             }, 1000); | ||||
|             return; | ||||
|         final Map<String, String> form = new HashMap<>(); | ||||
|         form.put("_uuid", UUID.randomUUID().toString()); | ||||
|         form.put("_csrftoken", csrfToken); | ||||
|         form.put("phone_id", UUID.randomUUID().toString()); | ||||
|         form.put("device_id", UUID.randomUUID().toString()); | ||||
|         form.put("client_session_id", UUID.randomUUID().toString()); | ||||
|         form.put("is_prefetch", "0"); | ||||
|         form.put("timezone_offset", String.valueOf(TimeZone.getDefault().getRawOffset() / 1000)); | ||||
|         if (!TextUtils.isEmpty(cursor)) { | ||||
|             form.put("max_id", cursor); | ||||
|             form.put("reason", "pagination"); | ||||
|         } | ||||
|         final Map<String, String> queryMap = new HashMap<>(); | ||||
|         queryMap.put("query_hash", "c699b185975935ae2a457f24075de8c7"); | ||||
|         queryMap.put("variables", "{" + | ||||
|                 "\"fetch_media_item_count\":" + maxItemsToLoad + "," + | ||||
|                 "\"fetch_like\":3,\"has_stories\":false,\"has_stories\":false,\"has_threaded_comments\":true," + | ||||
|                 "\"fetch_media_item_cursor\":\"" + (cursor == null ? "" : cursor) + "\"" + | ||||
|                 "}"); | ||||
|         final Call<String> request = repository.fetch(queryMap); | ||||
|         else { | ||||
|             form.put("is_pull_to_refresh", "1"); | ||||
|             form.put("reason", "pull_to_refresh"); | ||||
|         } | ||||
|         final Call<String> request = repository.fetch(form); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
| @ -130,35 +116,45 @@ public class FeedService extends BaseService { | ||||
|     @NonNull | ||||
|     private PostsFetchResponse parseResponseBody(@NonNull final String body) | ||||
|             throws JSONException { | ||||
|         final JSONObject root = new JSONObject(body); | ||||
|         final boolean moreAvailable = root.optBoolean("more_available"); | ||||
|         String nextMaxId = root.optString("next_max_id"); | ||||
|         final boolean needNewMaxId = nextMaxId.equals("feed_recs_head_load"); | ||||
|         final JSONArray feedItems = root.optJSONArray("items"); | ||||
|         final List<FeedModel> feedModels = new ArrayList<>(); | ||||
|         final JSONObject timelineFeed = new JSONObject(body) | ||||
|                 .getJSONObject("data") | ||||
|                 .getJSONObject(Constants.EXTRAS_USER) | ||||
|                 .getJSONObject("edge_web_feed_timeline"); | ||||
|         final String endCursor; | ||||
|         final boolean hasNextPage; | ||||
| 
 | ||||
|         final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); | ||||
|         if (pageInfo.has("has_next_page")) { | ||||
|             hasNextPage = pageInfo.getBoolean("has_next_page"); | ||||
|             endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; | ||||
|         } else { | ||||
|             hasNextPage = false; | ||||
|             endCursor = null; | ||||
|         } | ||||
| 
 | ||||
|         final JSONArray feedItems = timelineFeed.getJSONArray("edges"); | ||||
| 
 | ||||
|         for (int i = 0; i < feedItems.length(); ++i) { | ||||
|             final JSONObject itemJson = feedItems.optJSONObject(i); | ||||
|             if (itemJson == null) { | ||||
|             if (itemJson == null || itemJson.has("injected")) { | ||||
|                 continue; | ||||
|             } | ||||
|             final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson); | ||||
|             if (feedModel != null) { | ||||
|                 feedModels.add(feedModel); | ||||
|             else if (itemJson.has("end_of_feed_demarcator") && needNewMaxId) { | ||||
|                 final JSONArray groups = itemJson.getJSONObject("end_of_feed_demarcator").getJSONObject("group_set").getJSONArray("groups"); | ||||
|                 for (int j = 0; j < groups.length(); ++j) { | ||||
|                     final JSONObject groupJson = groups.optJSONObject(j); | ||||
|                     if (groupJson.getString("id").equals("past_posts")) { | ||||
|                         nextMaxId = groupJson.optString("next_max_id"); | ||||
|                         final JSONArray miniFeedItems = groupJson.optJSONArray("feed_items"); | ||||
|                         for (int k = 0; k < miniFeedItems.length(); ++k) { | ||||
|                             final JSONObject miniItemJson = miniFeedItems.optJSONObject(k); | ||||
|                             if (miniItemJson == null || miniItemJson.has("injected")) { | ||||
|                                 continue; | ||||
|                             } | ||||
|                             final FeedModel feedModel = ResponseBodyUtils.parseItem(miniItemJson); | ||||
|                             if (feedModel != null) { | ||||
|                                 feedModels.add(feedModel); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                     else continue; | ||||
|                 } | ||||
|             } | ||||
|             else { | ||||
|                 final FeedModel feedModel = ResponseBodyUtils.parseItem(itemJson); | ||||
|                 if (feedModel != null) { | ||||
|                     feedModels.add(feedModel); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return new PostsFetchResponse(feedModels, hasNextPage, endCursor); | ||||
|         return new PostsFetchResponse(feedModels, moreAvailable, nextMaxId); | ||||
|     } | ||||
| } | ||||
| } | ||||
| @ -153,13 +153,12 @@ public class FriendshipService extends BaseService { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     // log in required | ||||
|     public void getList(final boolean follower, | ||||
|                         final String targetUserId, | ||||
|                         final String maxId, | ||||
|                         final ServiceCallback<FriendshipRepoListFetchResponse> callback) { | ||||
|         final Map<String, String> queryMap = new HashMap<>(); | ||||
|         queryMap.put("max_id", maxId == null ? "" : maxId); | ||||
|         if (maxId != null) queryMap.put("max_id", maxId); | ||||
|         final Call<String> request = repository.getList(Constants.I_USER_AGENT, | ||||
|                                                         targetUserId, | ||||
|                                                         follower ? "followers" : "following", | ||||
| @ -173,7 +172,6 @@ public class FriendshipService extends BaseService { | ||||
|                     } | ||||
|                     final String body = response.body(); | ||||
|                     if (TextUtils.isEmpty(body)) { | ||||
| 
 | ||||
|                         callback.onSuccess(null); | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
| @ -0,0 +1,243 @@ | ||||
| package awais.instagrabber.webservices; | ||||
| 
 | ||||
| import android.os.Handler; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| import java.io.InputStream; | ||||
| import java.io.InputStreamReader; | ||||
| import java.io.Reader; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| 
 | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.repositories.GraphQLRepository; | ||||
| import awais.instagrabber.repositories.responses.GraphQLUserListFetchResponse; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.ResponseBodyUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import retrofit2.Call; | ||||
| import retrofit2.Callback; | ||||
| import retrofit2.Response; | ||||
| import retrofit2.Retrofit; | ||||
| 
 | ||||
| public class GraphQLService extends BaseService { | ||||
|     private static final String TAG = "GraphQLService"; | ||||
|     private static final boolean loadFromMock = false; | ||||
| 
 | ||||
|     private final GraphQLRepository repository; | ||||
| 
 | ||||
|     private static GraphQLService instance; | ||||
| 
 | ||||
|     private GraphQLService() { | ||||
|         final Retrofit retrofit = getRetrofitBuilder() | ||||
|                 .baseUrl("https://www.instagram.com") | ||||
|                 .build(); | ||||
|         repository = retrofit.create(GraphQLRepository.class); | ||||
|     } | ||||
| 
 | ||||
|     public static GraphQLService getInstance() { | ||||
|         if (instance == null) { | ||||
|             instance = new GraphQLService(); | ||||
|         } | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     private void fetch(final String queryHash, | ||||
|                        final String variables, | ||||
|                        final String arg1, | ||||
|                        final String arg2, | ||||
|                        final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         final Map<String, String> queryMap = new HashMap<>(); | ||||
|         queryMap.put("query_hash", queryHash); | ||||
|         queryMap.put("variables", variables); | ||||
|         final Call<String> request = repository.fetch(queryMap); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
|                 try { | ||||
|                     // Log.d(TAG, "onResponse: body: " + response.body()); | ||||
|                     final PostsFetchResponse postsFetchResponse = parsePostResponse(response, arg1, arg2); | ||||
|                     if (callback != null) { | ||||
|                         callback.onSuccess(postsFetchResponse); | ||||
|                     } | ||||
|                 } catch (JSONException e) { | ||||
|                     Log.e(TAG, "onResponse", e); | ||||
|                     if (callback != null) { | ||||
|                         callback.onFailure(e); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { | ||||
|                 if (callback != null) { | ||||
|                     callback.onFailure(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void fetchLocationPosts(@NonNull final String locationId, | ||||
|                                    final String maxId, | ||||
|                                    final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         fetch("36bd0f2bf5911908de389b8ceaa3be6d", | ||||
|                 "{\"id\":\"" + locationId + "\"," + | ||||
|                         "\"first\":25," + | ||||
|                         "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", | ||||
|                 Constants.EXTRAS_LOCATION, | ||||
|                 "edge_location_to_media", | ||||
|                 callback); | ||||
|     } | ||||
| 
 | ||||
|     public void fetchHashtagPosts(@NonNull final String tag, | ||||
|                                   final String maxId, | ||||
|                                   final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         fetch("9b498c08113f1e09617a1703c22b2f32", | ||||
|                 "{\"tag_name\":\"" + tag + "\"," + | ||||
|                         "\"first\":25," + | ||||
|                         "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", | ||||
|                 Constants.EXTRAS_HASHTAG, | ||||
|                 "edge_hashtag_to_media", | ||||
|                 callback); | ||||
|     } | ||||
| 
 | ||||
|     public void fetchProfilePosts(@NonNull final String profileId, | ||||
|                                   final int postsPerPage, | ||||
|                                   final String maxId, | ||||
|                                   final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         fetch("18a7b935ab438c4514b1f742d8fa07a7", | ||||
|                 "{\"id\":\"" + profileId + "\"," + | ||||
|                         "\"first\":" + postsPerPage + "," + | ||||
|                         "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", | ||||
|                 Constants.EXTRAS_USER, | ||||
|                 "edge_owner_to_timeline_media", | ||||
|                 callback); | ||||
|     } | ||||
| 
 | ||||
|     public void fetchTaggedPosts(@NonNull final String profileId, | ||||
|                                  final int postsPerPage, | ||||
|                                  final String maxId, | ||||
|                                  final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         fetch("31fe64d9463cbbe58319dced405c6206", | ||||
|                 "{\"id\":\"" + profileId + "\"," + | ||||
|                         "\"first\":" + postsPerPage + "," + | ||||
|                         "\"after\":\"" + (maxId == null ? "" : maxId) + "\"}", | ||||
|                 Constants.EXTRAS_USER, | ||||
|                 "edge_user_to_photos_of_you", | ||||
|                 callback); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private PostsFetchResponse parsePostResponse(@NonNull final Response<String> response, @NonNull final String arg1, @NonNull final String arg2) throws JSONException { | ||||
|         if (TextUtils.isEmpty(response.body())) { | ||||
|             Log.e(TAG, "parseResponse: feed response body is empty with status code: " + response.code()); | ||||
|             return new PostsFetchResponse(Collections.emptyList(), false, null); | ||||
|         } | ||||
|         return parseResponseBody(response.body(), arg1, arg2); | ||||
|     } | ||||
| 
 | ||||
|     @NonNull | ||||
|     private PostsFetchResponse parseResponseBody(@NonNull final String body, @NonNull final String arg1, @NonNull final String arg2) | ||||
|             throws JSONException { | ||||
|         final List<FeedModel> feedModels = new ArrayList<>(); | ||||
|         final JSONObject timelineFeed = new JSONObject(body) | ||||
|                 .getJSONObject("data") | ||||
|                 .getJSONObject(arg1) | ||||
|                 .getJSONObject(arg2); | ||||
|         final String endCursor; | ||||
|         final boolean hasNextPage; | ||||
| 
 | ||||
|         final JSONObject pageInfo = timelineFeed.getJSONObject("page_info"); | ||||
|         if (pageInfo.has("has_next_page")) { | ||||
|             hasNextPage = pageInfo.getBoolean("has_next_page"); | ||||
|             endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null; | ||||
|         } else { | ||||
|             hasNextPage = false; | ||||
|             endCursor = null; | ||||
|         } | ||||
| 
 | ||||
|         final JSONArray feedItems = timelineFeed.getJSONArray("edges"); | ||||
| 
 | ||||
|         for (int i = 0; i < feedItems.length(); ++i) { | ||||
|             final JSONObject itemJson = feedItems.optJSONObject(i); | ||||
|             if (itemJson == null) { | ||||
|                 continue; | ||||
|             } | ||||
|             final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson); | ||||
|             if (feedModel != null) { | ||||
|                 feedModels.add(feedModel); | ||||
|             } | ||||
|         } | ||||
|         return new PostsFetchResponse(feedModels, hasNextPage, endCursor); | ||||
|     } | ||||
| 
 | ||||
|     public void fetchCommentLikers(final String commentId, | ||||
|                                     final String endCursor, | ||||
|                                     final ServiceCallback<GraphQLUserListFetchResponse> callback) { | ||||
|         final Map<String, String> queryMap = new HashMap<>(); | ||||
|         queryMap.put("query_hash", "5f0b1f6281e72053cbc07909c8d154ae"); | ||||
|         queryMap.put("variables", "{\"comment_id\":\"" + commentId + "\"," + | ||||
|                 "\"first\":30," + | ||||
|                 "\"after\":\"" + (endCursor == null ? "" : endCursor) + "\"}"); | ||||
|         final Call<String> request = repository.fetch(queryMap); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
|                 final String rawBody = response.body(); | ||||
|                 if (rawBody == null) { | ||||
|                     Log.e(TAG, "Error occurred while fetching gql comment likes of "+commentId); | ||||
|                     callback.onSuccess(null); | ||||
|                     return; | ||||
|                 } | ||||
|                 try { | ||||
|                     final JSONObject body = new JSONObject(rawBody); | ||||
|                     final String status = body.getString("status"); | ||||
|                     final JSONObject data = body.getJSONObject("data").getJSONObject("comment").getJSONObject("edge_liked_by"); | ||||
|                     final JSONObject pageInfo = data.getJSONObject("page_info"); | ||||
|                     final String endCursor = pageInfo.getBoolean("has_next_page") ? pageInfo.getString("end_cursor") : null; | ||||
|                     final JSONArray users = data.getJSONArray("edges"); | ||||
|                     final int usersLen = users.length(); | ||||
|                     final List<ProfileModel> userModels = new ArrayList<>(); | ||||
|                     for (int j = 0; j < usersLen; ++j) { | ||||
|                         final JSONObject userObject = users.getJSONObject(j).getJSONObject("node"); | ||||
|                         userModels.add(new ProfileModel(userObject.optBoolean("is_private"), | ||||
|                                 false, | ||||
|                                 userObject.optBoolean("is_verified"), | ||||
|                                 userObject.getString("id"), | ||||
|                                 userObject.getString("username"), | ||||
|                                 userObject.optString("full_name"), | ||||
|                                 null, null, | ||||
|                                 userObject.getString("profile_pic_url"), | ||||
|                                 null, 0, 0, 0, false, false, false, false, false)); | ||||
|                     } | ||||
|                     callback.onSuccess(new GraphQLUserListFetchResponse(endCursor, status, userModels)); | ||||
|                 } catch (JSONException e) { | ||||
|                     Log.e(TAG, "onResponse", e); | ||||
|                     if (callback != null) { | ||||
|                         callback.onFailure(e); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { | ||||
|                 if (callback != null) { | ||||
|                     callback.onFailure(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @ -19,6 +19,7 @@ import java.util.Objects; | ||||
| 
 | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.repositories.LocationRepository; | ||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||
| import awais.instagrabber.utils.ResponseBodyUtils; | ||||
| import awais.instagrabber.utils.TextUtils; | ||||
| import retrofit2.Call; | ||||
| @ -29,7 +30,7 @@ import retrofit2.Retrofit; | ||||
| public class LocationService extends BaseService { | ||||
|     private static final String TAG = "LocationService"; | ||||
| 
 | ||||
|     private final LocationRepository repository, webRepository; | ||||
|     private final LocationRepository repository; | ||||
| 
 | ||||
|     private static LocationService instance; | ||||
| 
 | ||||
| @ -38,10 +39,6 @@ public class LocationService extends BaseService { | ||||
|                 .baseUrl("https://i.instagram.com") | ||||
|                 .build(); | ||||
|         repository = retrofit.create(LocationRepository.class); | ||||
|         final Retrofit webRetrofit = getRetrofitBuilder() | ||||
|                 .baseUrl("https://www.instagram.com") | ||||
|                 .build(); | ||||
|         webRepository = webRetrofit.create(LocationRepository.class); | ||||
|     } | ||||
| 
 | ||||
|     public static LocationService getInstance() { | ||||
| @ -53,7 +50,7 @@ public class LocationService extends BaseService { | ||||
| 
 | ||||
|     public void fetchPosts(@NonNull final String locationId, | ||||
|                            final String maxId, | ||||
|                            final ServiceCallback<LocationPostsFetchResponse> callback) { | ||||
|                            final ServiceCallback<PostsFetchResponse> callback) { | ||||
|         final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); | ||||
|         if (!TextUtils.isEmpty(maxId)) { | ||||
|             builder.put("max_id", maxId); | ||||
| @ -71,7 +68,7 @@ public class LocationService extends BaseService { | ||||
|                         callback.onSuccess(null); | ||||
|                         return; | ||||
|                     } | ||||
|                     final LocationPostsFetchResponse tagPostsFetchResponse = parseResponse(body); | ||||
|                     final PostsFetchResponse tagPostsFetchResponse = parseResponse(body); | ||||
|                     callback.onSuccess(tagPostsFetchResponse); | ||||
|                 } catch (JSONException e) { | ||||
|                     Log.e(TAG, "onResponse", e); | ||||
| @ -88,20 +85,16 @@ public class LocationService extends BaseService { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private LocationPostsFetchResponse parseResponse(@NonNull final String body) throws JSONException { | ||||
|     private PostsFetchResponse parseResponse(@NonNull final String body) throws JSONException { | ||||
|         final JSONObject root = new JSONObject(body); | ||||
|         final boolean moreAvailable = root.optBoolean("more_available"); | ||||
|         final String nextMaxId = root.optString("next_max_id"); | ||||
|         final int numResults = root.optInt("num_results"); | ||||
|         final String status = root.optString("status"); | ||||
|         final JSONArray itemsJson = root.optJSONArray("items"); | ||||
|         final List<FeedModel> items = parseItems(itemsJson); | ||||
|         return new LocationPostsFetchResponse( | ||||
|         return new PostsFetchResponse( | ||||
|                 items, | ||||
|                 moreAvailable, | ||||
|                 nextMaxId, | ||||
|                 numResults, | ||||
|                 status, | ||||
|                 items | ||||
|                 nextMaxId | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -122,174 +115,4 @@ public class LocationService extends BaseService { | ||||
|         } | ||||
|         return feedModels; | ||||
|     } | ||||
| 
 | ||||
|     public void fetchGraphQLPosts(@NonNull final String locationId, | ||||
|                                   final String maxId, | ||||
|                                   final ServiceCallback<LocationPostsFetchResponse> callback) { | ||||
|         final Map<String, String> queryMap = new HashMap<>(); | ||||
|         queryMap.put("query_hash", "36bd0f2bf5911908de389b8ceaa3be6d"); | ||||
|         queryMap.put("variables", "{" + | ||||
|                 "\"id\":\"" + locationId + "\"," + | ||||
|                 "\"first\":25," + | ||||
|                 "\"after\":\"" + (maxId == null ? "" : maxId) + "\"" + | ||||
|                 "}"); | ||||
|         final Call<String> request = webRepository.fetchGraphQLPosts(queryMap); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
|                 try { | ||||
|                     if (callback == null) { | ||||
|                         return; | ||||
|                     } | ||||
|                     final String body = response.body(); | ||||
|                     if (TextUtils.isEmpty(body)) { | ||||
|                         callback.onSuccess(null); | ||||
|                         return; | ||||
|                     } | ||||
|                     final LocationPostsFetchResponse tagPostsFetchResponse = parseGraphQLResponse(body); | ||||
|                     callback.onSuccess(tagPostsFetchResponse); | ||||
|                 } catch (JSONException e) { | ||||
|                     Log.e(TAG, "onResponse", e); | ||||
|                     callback.onFailure(e); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { | ||||
|                 if (callback != null) { | ||||
|                     callback.onFailure(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private LocationPostsFetchResponse parseGraphQLResponse(@NonNull final String body) throws JSONException { | ||||
|         final JSONObject rootroot = new JSONObject(body); | ||||
|         final JSONObject root = rootroot.getJSONObject("data").getJSONObject("location").getJSONObject("edge_location_to_media"); | ||||
|         final boolean moreAvailable = root.getJSONObject("page_info").optBoolean("has_next_page"); | ||||
|         final String nextMaxId = root.getJSONObject("page_info").optString("end_cursor"); | ||||
|         final int numResults = root.optInt("count"); | ||||
|         final String status = rootroot.optString("status"); | ||||
|         final JSONArray itemsJson = root.optJSONArray("edges"); | ||||
|         final List<FeedModel> items = parseGraphQLItems(itemsJson); | ||||
|         return new LocationPostsFetchResponse( | ||||
|                 moreAvailable, | ||||
|                 nextMaxId, | ||||
|                 numResults, | ||||
|                 status, | ||||
|                 items | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private List<FeedModel> parseGraphQLItems(final JSONArray items) throws JSONException { | ||||
|         if (items == null) { | ||||
|             return Collections.emptyList(); | ||||
|         } | ||||
|         final List<FeedModel> feedModels = new ArrayList<>(); | ||||
|         for (int i = 0; i < items.length(); i++) { | ||||
|             final JSONObject itemJson = items.optJSONObject(i); | ||||
|             if (itemJson == null) { | ||||
|                 continue; | ||||
|             } | ||||
|             final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson); | ||||
|             if (feedModel != null) { | ||||
|                 feedModels.add(feedModel); | ||||
|             } | ||||
|         } | ||||
|         return feedModels; | ||||
|     } | ||||
| 
 | ||||
|     public static class LocationPostsFetchResponse { | ||||
|         private boolean moreAvailable; | ||||
|         private String nextMaxId; | ||||
|         private int numResults; | ||||
|         private String status; | ||||
|         private List<FeedModel> items; | ||||
| 
 | ||||
|         public LocationPostsFetchResponse(final boolean moreAvailable, | ||||
|                                           final String nextMaxId, | ||||
|                                           final int numResults, | ||||
|                                           final String status, | ||||
|                                           final List<FeedModel> items) { | ||||
|             this.moreAvailable = moreAvailable; | ||||
|             this.nextMaxId = nextMaxId; | ||||
|             this.numResults = numResults; | ||||
|             this.status = status; | ||||
|             this.items = items; | ||||
|         } | ||||
| 
 | ||||
|         public boolean isMoreAvailable() { | ||||
|             return moreAvailable; | ||||
|         } | ||||
| 
 | ||||
|         public LocationPostsFetchResponse setMoreAvailable(final boolean moreAvailable) { | ||||
|             this.moreAvailable = moreAvailable; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public String getNextMaxId() { | ||||
|             return nextMaxId; | ||||
|         } | ||||
| 
 | ||||
|         public LocationPostsFetchResponse setNextMaxId(final String nextMaxId) { | ||||
|             this.nextMaxId = nextMaxId; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public int getNumResults() { | ||||
|             return numResults; | ||||
|         } | ||||
| 
 | ||||
|         public LocationPostsFetchResponse setNumResults(final int numResults) { | ||||
|             this.numResults = numResults; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public String getStatus() { | ||||
|             return status; | ||||
|         } | ||||
| 
 | ||||
|         public LocationPostsFetchResponse setStatus(final String status) { | ||||
|             this.status = status; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         public List<FeedModel> getItems() { | ||||
|             return items; | ||||
|         } | ||||
| 
 | ||||
|         public LocationPostsFetchResponse setItems(final List<FeedModel> items) { | ||||
|             this.items = items; | ||||
|             return this; | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public boolean equals(final Object o) { | ||||
|             if (this == o) return true; | ||||
|             if (o == null || getClass() != o.getClass()) return false; | ||||
|             final LocationPostsFetchResponse that = (LocationPostsFetchResponse) o; | ||||
|             return moreAvailable == that.moreAvailable && | ||||
|                     numResults == that.numResults && | ||||
|                     Objects.equals(nextMaxId, that.nextMaxId) && | ||||
|                     Objects.equals(status, that.status) && | ||||
|                     Objects.equals(items, that.items); | ||||
|         } | ||||
| 
 | ||||
|         @Override | ||||
|         public int hashCode() { | ||||
|             return Objects.hash(moreAvailable, nextMaxId, numResults, status, items); | ||||
|         } | ||||
| 
 | ||||
|         @NonNull | ||||
|         @Override | ||||
|         public String toString() { | ||||
|             return "LocationPostsFetchResponse{" + | ||||
|                     "moreAvailable=" + moreAvailable + | ||||
|                     ", nextMaxId='" + nextMaxId + '\'' + | ||||
|                     ", numResults=" + numResults + | ||||
|                     ", status='" + status + '\'' + | ||||
|                     ", items=" + items + | ||||
|                     '}'; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -7,20 +7,25 @@ import androidx.annotation.NonNull; | ||||
| 
 | ||||
| import com.google.common.collect.ImmutableMap; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.UUID; | ||||
| 
 | ||||
| import awais.instagrabber.models.FeedModel; | ||||
| import awais.instagrabber.models.ProfileModel; | ||||
| import awais.instagrabber.repositories.MediaRepository; | ||||
| import awais.instagrabber.repositories.requests.UploadFinishOptions; | ||||
| import awais.instagrabber.utils.Constants; | ||||
| import awais.instagrabber.utils.DateUtils; | ||||
| import awais.instagrabber.utils.MediaUploadHelper; | ||||
| import awais.instagrabber.utils.ResponseBodyUtils; | ||||
| import awais.instagrabber.utils.Utils; | ||||
| import retrofit2.Call; | ||||
| import retrofit2.Callback; | ||||
| @ -48,6 +53,37 @@ public class MediaService extends BaseService { | ||||
|         return instance; | ||||
|     } | ||||
| 
 | ||||
|     public void fetch(final String mediaId, | ||||
|                       final ServiceCallback<FeedModel> callback) { | ||||
|         final Call<String> request = repository.fetch(mediaId); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, | ||||
|                                    @NonNull final Response<String> response) { | ||||
|                 if (callback == null) return; | ||||
|                 final String body = response.body(); | ||||
|                 if (body == null) { | ||||
|                     callback.onSuccess(null); | ||||
|                     return; | ||||
|                 } | ||||
|                 try { | ||||
|                     final JSONObject itemJson = new JSONObject(body).getJSONArray("items").getJSONObject(0); | ||||
|                     callback.onSuccess(ResponseBodyUtils.parseItem(itemJson)); | ||||
|                 } catch (JSONException e) { | ||||
|                     callback.onFailure(e); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, | ||||
|                                   @NonNull final Throwable t) { | ||||
|                 if (callback != null) { | ||||
|                     callback.onFailure(t); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void like(final String mediaId, | ||||
|                      final String userId, | ||||
|                      final String csrfToken, | ||||
| @ -88,7 +124,7 @@ public class MediaService extends BaseService { | ||||
|         form.put("_uuid", UUID.randomUUID().toString()); | ||||
|         // form.put("radio_type", "wifi-none"); | ||||
|         final Map<String, String> signedForm = Utils.sign(form); | ||||
|         final Call<String> request = repository.action(Constants.I_USER_AGENT, action, mediaId, signedForm); | ||||
|         final Call<String> request = repository.action(action, mediaId, signedForm); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, | ||||
| @ -137,7 +173,7 @@ public class MediaService extends BaseService { | ||||
|             form.put("replied_to_comment_id", replyToCommentId); | ||||
|         } | ||||
|         final Map<String, String> signedForm = Utils.sign(form); | ||||
|         final Call<String> commentRequest = repository.comment(Constants.I_USER_AGENT, mediaId, signedForm); | ||||
|         final Call<String> commentRequest = repository.comment(mediaId, signedForm); | ||||
|         commentRequest.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
| @ -183,7 +219,7 @@ public class MediaService extends BaseService { | ||||
|         form.put("_uid", userId); | ||||
|         form.put("_uuid", UUID.randomUUID().toString()); | ||||
|         final Map<String, String> signedForm = Utils.sign(form); | ||||
|         final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(Constants.USER_AGENT, mediaId, signedForm); | ||||
|         final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(mediaId, signedForm); | ||||
|         bulkDeleteRequest.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
| @ -219,7 +255,7 @@ public class MediaService extends BaseService { | ||||
|         // form.put("_uid", userId); | ||||
|         // form.put("_uuid", UUID.randomUUID().toString()); | ||||
|         final Map<String, String> signedForm = Utils.sign(form); | ||||
|         final Call<String> commentLikeRequest = repository.commentLike(Constants.USER_AGENT, commentId, signedForm); | ||||
|         final Call<String> commentLikeRequest = repository.commentLike(commentId, signedForm); | ||||
|         commentLikeRequest.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
| @ -255,7 +291,7 @@ public class MediaService extends BaseService { | ||||
|         // form.put("_uid", userId); | ||||
|         // form.put("_uuid", UUID.randomUUID().toString()); | ||||
|         final Map<String, String> signedForm = Utils.sign(form); | ||||
|         final Call<String> commentUnlikeRequest = repository.commentUnlike(Constants.USER_AGENT, commentId, signedForm); | ||||
|         final Call<String> commentUnlikeRequest = repository.commentUnlike(commentId, signedForm); | ||||
|         commentUnlikeRequest.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
| @ -283,6 +319,126 @@ public class MediaService extends BaseService { | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void editCaption(final String postId, | ||||
|                             final String userId, | ||||
|                             final String newCaption, | ||||
|                             @NonNull final String csrfToken, | ||||
|                             @NonNull final ServiceCallback<Boolean> callback) { | ||||
|         final Map<String, Object> form = new HashMap<>(); | ||||
|         form.put("_csrftoken", csrfToken); | ||||
|         form.put("_uid", userId); | ||||
|         form.put("_uuid", UUID.randomUUID().toString()); | ||||
|         form.put("igtv_feed_preview", "false"); | ||||
|         form.put("media_id", postId); | ||||
|         form.put("caption_text", newCaption); | ||||
|         final Map<String, String> signedForm = Utils.sign(form); | ||||
|         final Call<String> request = repository.editCaption(postId, signedForm); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
|                 final String body = response.body(); | ||||
|                 if (body == null) { | ||||
|                     Log.e(TAG, "Error occurred while editing caption"); | ||||
|                     callback.onSuccess(false); | ||||
|                     return; | ||||
|                 } | ||||
|                 try { | ||||
|                     final JSONObject jsonObject = new JSONObject(body); | ||||
|                     final String status = jsonObject.optString("status"); | ||||
|                     callback.onSuccess(status.equals("ok")); | ||||
|                 } catch (JSONException e) { | ||||
|                     // Log.e(TAG, "Error parsing body", e); | ||||
|                     callback.onFailure(e); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { | ||||
|                 Log.e(TAG, "Error editing caption", t); | ||||
|                 callback.onFailure(t); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void fetchLikes(final String mediaId, | ||||
|                            final boolean isComment, | ||||
|                            @NonNull final ServiceCallback<List<ProfileModel>> callback) { | ||||
|         final Call<String> likesRequest = repository.fetchLikes(mediaId, isComment ? "comment_likers" : "likers"); | ||||
|         likesRequest.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
|                 final String body = response.body(); | ||||
|                 if (body == null) { | ||||
|                     Log.e(TAG, "Error occurred while fetching likes of "+mediaId); | ||||
|                     callback.onSuccess(null); | ||||
|                     return; | ||||
|                 } | ||||
|                 try { | ||||
|                     final JSONObject data = new JSONObject(body); | ||||
|                     final JSONArray users = data.getJSONArray("users"); | ||||
|                     final int usersLen = users.length(); | ||||
|                     final List<ProfileModel> userModels = new ArrayList<>(); | ||||
|                     for (int j = 0; j < usersLen; ++j) { | ||||
|                         final JSONObject userObject = users.getJSONObject(j); | ||||
|                         userModels.add(new ProfileModel(userObject.optBoolean("is_private"), | ||||
|                                 false, | ||||
|                                 userObject.optBoolean("is_verified"), | ||||
|                                 String.valueOf(userObject.get("pk")), | ||||
|                                 userObject.getString("username"), | ||||
|                                 userObject.optString("full_name"), | ||||
|                                 null, null, | ||||
|                                 userObject.getString("profile_pic_url"), | ||||
|                                 null, 0, 0, 0, false, false, false, false, false)); | ||||
|                     } | ||||
|                     callback.onSuccess(userModels); | ||||
|                 } catch (JSONException e) { | ||||
|                     // Log.e(TAG, "Error parsing body", e); | ||||
|                     callback.onFailure(e); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { | ||||
|                 Log.e(TAG, "Error getting likes", t); | ||||
|                 callback.onFailure(t); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public void translate(final String id, | ||||
|                           final String type, // 1 caption 2 comment 3 bio | ||||
|                           @NonNull final ServiceCallback<String> callback) { | ||||
|         final Map<String, String> form = new HashMap<>(); | ||||
|         form.put("id", id); | ||||
|         form.put("type", type); | ||||
|         final Call<String> request = repository.translate(form); | ||||
|         request.enqueue(new Callback<String>() { | ||||
|             @Override | ||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||
|                 final String body = response.body(); | ||||
|                 if (body == null) { | ||||
|                     Log.e(TAG, "Error occurred while translating"); | ||||
|                     callback.onSuccess(null); | ||||
|                     return; | ||||
|                 } | ||||
|                 try { | ||||
|                     final JSONObject jsonObject = new JSONObject(body); | ||||
|                     final String translation = jsonObject.optString("translation"); | ||||
|                     callback.onSuccess(translation); | ||||
|                 } catch (JSONException e) { | ||||
|                     // Log.e(TAG, "Error parsing body", e); | ||||
|                     callback.onFailure(e); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             @Override | ||||
|             public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) { | ||||
|                 Log.e(TAG, "Error translating", t); | ||||
|                 callback.onFailure(t); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     public Call<String> uploadFinish(final long userId, | ||||
|                                      @NonNull final String csrfToken, | ||||
|                                      @NonNull final UploadFinishOptions options) { | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user