mirror of
				https://github.com/KokaKiwi/BarInsta
				synced 2025-10-31 19:45:35 +00:00 
			
		
		
		
	Merge branch 'master' into dm-notifications-enhancements
This commit is contained in:
		
						commit
						d42d4dd3ea
					
				| @ -61,6 +61,15 @@ | |||||||
|         "translation" |         "translation" | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       "login": "CrazyMarvin", | ||||||
|  |       "name": "CrazyMarvin", | ||||||
|  |       "avatar_url": "https://avatars3.githubusercontent.com/u/15004217?v=4", | ||||||
|  |       "profile": "https://github.com/CrazyMarvin", | ||||||
|  |       "contributions": [ | ||||||
|  |         "financial" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "login": "KevinNThomas", |       "login": "KevinNThomas", | ||||||
|       "name": "Kevin Thomas", |       "name": "Kevin Thomas", | ||||||
| @ -82,16 +91,6 @@ | |||||||
|         "question" |         "question" | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     { |  | ||||||
|       "login": "e-edgren", |  | ||||||
|       "name": "Airikr", |  | ||||||
|       "avatar_url": "https://avatars0.githubusercontent.com/u/53869451", |  | ||||||
|       "profile": "https://airikr.me/", |  | ||||||
|       "contributions": [ |  | ||||||
|         "ideas", |  | ||||||
|         "question" |  | ||||||
|       ] |  | ||||||
|     }, |  | ||||||
|     { |     { | ||||||
|       "login": "alin-1", |       "login": "alin-1", | ||||||
|       "name": "ALIN", |       "name": "ALIN", | ||||||
| @ -102,6 +101,26 @@ | |||||||
|         "ideas" |         "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", |       "login": "Akrai", | ||||||
|       "name": "Akrai", |       "name": "Akrai", | ||||||
| @ -112,6 +131,15 @@ | |||||||
|         "translation" |         "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", |       "login": "farzadx", | ||||||
|       "name": "farzadx", |       "name": "farzadx", | ||||||
| @ -149,19 +177,28 @@ | |||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "login": "kernoeb", |       "login": "initdebugs", | ||||||
|       "name": "kernoeb", |       "name": "Initdebugs", | ||||||
|       "avatar_url": "https://avatars3.githubusercontent.com/u/24623168", |       "avatar_url": "https://avatars0.githubusercontent.com/u/75781464?v=4", | ||||||
|       "profile": "https://becauseofprog.fr/", |       "profile": "https://github.com/initdebugs", | ||||||
|       "contributions": [ |       "contributions": [ | ||||||
|         "translation" |         "translation" | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       "login": "Lego8486", |       "login": "CrafterSvK", | ||||||
|       "name": "Ten_Lego", |       "name": "Jakub Janek", | ||||||
|       "avatar_url": "https://avatars1.githubusercontent.com/u/47414485", |       "avatar_url": "https://avatars3.githubusercontent.com/u/8365659?v=4", | ||||||
|       "profile": "https://github.com/Lego8486", |       "profile": "https://janek.xyz/", | ||||||
|  |       "contributions": [ | ||||||
|  |         "translation" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "login": "kernoeb", | ||||||
|  |       "name": "kernoeb", | ||||||
|  |       "avatar_url": "https://avatars3.githubusercontent.com/u/24623168", | ||||||
|  |       "profile": "https://becauseofprog.fr/", | ||||||
|       "contributions": [ |       "contributions": [ | ||||||
|         "translation" |         "translation" | ||||||
|       ] |       ] | ||||||
| @ -229,16 +266,6 @@ | |||||||
|         "translation" |         "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", |       "login": "rikishi0071", | ||||||
|       "name": "rikishi0071", |       "name": "rikishi0071", | ||||||
| @ -257,6 +284,15 @@ | |||||||
|         "translation" |         "translation" | ||||||
|       ] |       ] | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       "login": "Lego8486", | ||||||
|  |       "name": "Ten_Lego", | ||||||
|  |       "avatar_url": "https://avatars1.githubusercontent.com/u/47414485", | ||||||
|  |       "profile": "https://github.com/Lego8486", | ||||||
|  |       "contributions": [ | ||||||
|  |         "translation" | ||||||
|  |       ] | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "login": "wagnim", |       "login": "wagnim", | ||||||
|       "name": "wagnim", |       "name": "wagnim", | ||||||
| @ -265,6 +301,15 @@ | |||||||
|       "contributions": [ |       "contributions": [ | ||||||
|         "translation" |         "translation" | ||||||
|       ] |       ] | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       "login": "ysakamoto", | ||||||
|  |       "name": "ysakamoto", | ||||||
|  |       "avatar_url": "https://avatars3.githubusercontent.com/u/1331642?v=4", | ||||||
|  |       "profile": "https://github.com/ysakamoto", | ||||||
|  |       "contributions": [ | ||||||
|  |         "translation" | ||||||
|  |       ] | ||||||
|     } |     } | ||||||
|   ], |   ], | ||||||
|   "contributorsPerLine": 6, |   "contributorsPerLine": 6, | ||||||
|  | |||||||
							
								
								
									
										44
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								README.md
									
									
									
									
									
								
							| @ -9,7 +9,7 @@ | |||||||
| [](http://makeapullrequest.com) | [](http://makeapullrequest.com) | ||||||
| [](./LICENSE) | [](./LICENSE) | ||||||
| [](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> | [](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> | ||||||
| [](#contributors) | [](#contributors) | ||||||
| <!-- ALL-CONTRIBUTORS-BADGE:END --> | <!-- ALL-CONTRIBUTORS-BADGE:END --> | ||||||
| 
 | 
 | ||||||
| We're previously known as InstaGrabber. | We're previously known as InstaGrabber. | ||||||
| @ -32,11 +32,15 @@ Version status: . | ||||||
|  | 
 | ||||||
| ## Contact us | ## Contact us | ||||||
| 
 | 
 | ||||||
| * Use [GitHub issues](https://github.com/austinhuang0131/instagrabber/issues) when possible. | * 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) | * 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) | * 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 | ## 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="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="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="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> | ||||||
|   <tr> |   <tr> | ||||||
|  |     <td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td> | ||||||
|     <td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td> |     <td align="center"><a href="https://github.com/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://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> |     <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/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> |     <td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|   </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/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> |     <td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://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/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://github.com/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> |  | ||||||
|   </tr> |   </tr> | ||||||
|   <tr> |   <tr> | ||||||
|  |     <td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|  |     <td align="center"><a href="https://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/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> |     <td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|  |   </tr> | ||||||
|  |   <tr> | ||||||
|     <td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> |     <td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> |     <td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> |     <td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td> | ||||||
|     <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://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://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/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> |   </tr> | ||||||
| </table> | </table> | ||||||
| 
 | 
 | ||||||
| @ -92,11 +103,10 @@ Prominent contributors are listed here in the [all-contributors](https://allcont | |||||||
| 
 | 
 | ||||||
| ## License | ## 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) |     Barinsta | ||||||
|     Copyright (C) 2019  AWAiS        <chapter50000@hotmail.com> |     Copyright (C) 2020-2021  Austin Huang <im@austinhuang.me> | ||||||
|     Copyright (C) 2020  Austin Huang <im@austinhuang.me> |  | ||||||
|                              Ammar Githam <ammargitham786@gmail.com> |                              Ammar Githam <ammargitham786@gmail.com> | ||||||
| 
 | 
 | ||||||
|     This program is free software: you can redistribute it and/or modify |     This program is free software: you can redistribute it and/or modify | ||||||
|  | |||||||
| @ -10,8 +10,8 @@ android { | |||||||
|         minSdkVersion 21 |         minSdkVersion 21 | ||||||
|         targetSdkVersion 29 |         targetSdkVersion 29 | ||||||
| 
 | 
 | ||||||
|         versionCode 54 |         versionCode 56 | ||||||
|         versionName '19.0.2' |         versionName '19.0.4' | ||||||
| 
 | 
 | ||||||
|         multiDexEnabled true |         multiDexEnabled true | ||||||
| 
 | 
 | ||||||
| @ -98,6 +98,9 @@ dependencies { | |||||||
|     implementation "androidx.emoji:emoji:$emoji_compat_version" |     implementation "androidx.emoji:emoji:$emoji_compat_version" | ||||||
|     implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version" |     implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version" | ||||||
| 
 | 
 | ||||||
|  |     // implementation 'com.github.hendrawd:StorageUtil:1.1.0' | ||||||
|  |     implementation 'me.austinhuang:AutoLinkTextViewV2:-SNAPSHOT' | ||||||
|  | 
 | ||||||
|     implementation 'org.jsoup:jsoup:1.13.1' |     implementation 'org.jsoup:jsoup:1.13.1' | ||||||
|     implementation 'com.facebook.fresco:fresco:2.3.0' |     implementation 'com.facebook.fresco:fresco:2.3.0' | ||||||
|     implementation 'com.facebook.fresco:animated-webp:2.3.0' |     implementation 'com.facebook.fresco:animated-webp:2.3.0' | ||||||
| @ -109,7 +112,6 @@ dependencies { | |||||||
| 
 | 
 | ||||||
|     implementation 'org.apache.commons:commons-imaging:1.0-alpha2' |     implementation 'org.apache.commons:commons-imaging:1.0-alpha2' | ||||||
|     implementation 'com.ibm.icu:icu4j:68.1' |     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:uCrop:2.3-native-beta-2' | ||||||
|     implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4' |     implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4' | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -83,8 +83,8 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie | |||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|     private final CommentCallback commentCallback; |     private final CommentCallback commentCallback; | ||||||
|     private CommentModel selected; |     private CommentModel selected, toChangeLike; | ||||||
|     private int selectedIndex; |     private int selectedIndex, likedIndex; | ||||||
| 
 | 
 | ||||||
|     public CommentsAdapter(final CommentCallback commentCallback) { |     public CommentsAdapter(final CommentCallback commentCallback) { | ||||||
|         super(DIFF_CALLBACK); |         super(DIFF_CALLBACK); | ||||||
| @ -106,10 +106,12 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { |     public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { | ||||||
|         final CommentModel commentModel = getItem(position); |         CommentModel commentModel = getItem(position); | ||||||
|         if (commentModel == null) return; |         if (commentModel == null) return; | ||||||
|         final int type = getItemViewType(position); |         final int type = getItemViewType(position); | ||||||
|         final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId()); |         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) { |         if (type == TYPE_PARENT) { | ||||||
|             final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder; |             final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder; | ||||||
|             viewHolder.bind(commentModel, selected, commentCallback); |             viewHolder.bind(commentModel, selected, commentCallback); | ||||||
| @ -174,6 +176,14 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie | |||||||
|         notifyItemChanged(selectedIndex); |         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() { |     public CommentModel getSelected() { | ||||||
|         return selected; |         return selected; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -46,7 +46,7 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie | |||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { |         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() { |     private final AdapterSelectionCallback adapterSelectionCallback = new AdapterSelectionCallback() { | ||||||
|  | |||||||
| @ -4,12 +4,19 @@ import android.view.LayoutInflater; | |||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  | import androidx.annotation.Nullable; | ||||||
| import androidx.recyclerview.widget.DiffUtil; | import androidx.recyclerview.widget.DiffUtil; | ||||||
| import androidx.recyclerview.widget.ListAdapter; | import androidx.recyclerview.widget.ListAdapter; | ||||||
| 
 | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
| import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder; | import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder; | ||||||
| import awais.instagrabber.databinding.ItemHighlightBinding; | import awais.instagrabber.databinding.ItemHighlightBinding; | ||||||
| import awais.instagrabber.models.FeedStoryModel; | import awais.instagrabber.models.FeedStoryModel; | ||||||
|  | import awais.instagrabber.utils.Constants; | ||||||
|  | import awais.instagrabber.utils.Utils; | ||||||
| 
 | 
 | ||||||
| public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> { | public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> { | ||||||
|     private final OnFeedStoryClickListener listener; |     private final OnFeedStoryClickListener listener; | ||||||
| @ -22,7 +29,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt | |||||||
| 
 | 
 | ||||||
|         @Override |         @Override | ||||||
|         public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) { |         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 { |     public interface OnFeedStoryClickListener { | ||||||
|         void onFeedStoryClick(FeedStoryModel model, int position); |         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) { |     private List<NotificationModel> sort(final List<NotificationModel> list) { | ||||||
|         final List<NotificationModel> listCopy = new ArrayList<>(list); |         final List<NotificationModel> listCopy = new ArrayList<>(list); | ||||||
|         Collections.sort(listCopy, (o1, o2) -> { |         Collections.sort(listCopy, (o1, o2) -> { | ||||||
|             if (o1.getType() == o2.getType()) return 0; |  | ||||||
|             // keep requests at top |             // keep requests at top | ||||||
|             if (o1.getType() == NotificationType.REQUEST) return -1; |             if (o1.getType() == o2.getType() | ||||||
|             if (o2.getType() == NotificationType.REQUEST) return 1; |                     && o1.getType() == NotificationType.REQUEST | ||||||
|             return 0; |                     && 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; |         return listCopy; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public interface OnNotificationClickListener { |     public interface OnNotificationClickListener { | ||||||
|         void onNotificationClick(final NotificationModel model); |         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; |             if (listener == null) return; | ||||||
|             listener.onFeedStoryClick(model, position); |             listener.onFeedStoryClick(model, position); | ||||||
|         }); |         }); | ||||||
|  |         binding.getRoot().setOnLongClickListener(v -> { | ||||||
|  |             if (listener != null) listener.onFeedStoryLongClick(model, position); | ||||||
|  |             return true; | ||||||
|  |         }); | ||||||
|         final ProfileModel profileModel = model.getProfileModel(); |         final ProfileModel profileModel = model.getProfileModel(); | ||||||
|         binding.title.setText(profileModel.getUsername()); |         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.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; | package awais.instagrabber.adapters.viewholder; | ||||||
| 
 | 
 | ||||||
|  | import android.util.Log; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| 
 | 
 | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
|  | |||||||
| @ -24,10 +24,6 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder { | |||||||
|     public void bind(final NotificationModel model, |     public void bind(final NotificationModel model, | ||||||
|                      final OnNotificationClickListener notificationClickListener) { |                      final OnNotificationClickListener notificationClickListener) { | ||||||
|         if (model == null) return; |         if (model == null) return; | ||||||
|         itemView.setOnClickListener(v -> { |  | ||||||
|             if (notificationClickListener == null) return; |  | ||||||
|             notificationClickListener.onNotificationClick(model); |  | ||||||
|         }); |  | ||||||
|         int text = -1; |         int text = -1; | ||||||
|         CharSequence subtext = null; |         CharSequence subtext = null; | ||||||
|         switch (model.getType()) { |         switch (model.getType()) { | ||||||
| @ -52,20 +48,54 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder { | |||||||
|                 text = R.string.request_notif; |                 text = R.string.request_notif; | ||||||
|                 subtext = model.getText(); |                 subtext = model.getText(); | ||||||
|                 break; |                 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.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.tvComment.setText(text); | ||||||
|         binding.tvSubComment.setText(subtext, subtext instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL); |             binding.tvSubComment.setVisibility(subtext == null ? View.GONE : View.VISIBLE); | ||||||
|         // binding.tvSubComment.setMentionClickListener(mentionClickListener); |         } | ||||||
|         if (model.getType() != NotificationType.REQUEST) { | 
 | ||||||
|  |         if (model.getType() != NotificationType.REQUEST && model.getType() != NotificationType.AYML) { | ||||||
|             binding.tvDate.setText(model.getDateTime()); |             binding.tvDate.setText(model.getDateTime()); | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         binding.tvUsername.setText(model.getUsername()); | ||||||
|         binding.ivProfilePic.setImageURI(model.getProfilePic()); |         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); |             binding.ivPreviewPic.setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|  |         else if (TextUtils.isEmpty(model.getPreviewPic())) { | ||||||
|  |             binding.ivPreviewPic.setVisibility(View.INVISIBLE); | ||||||
|         } else { |         } else { | ||||||
|             binding.ivPreviewPic.setVisibility(View.VISIBLE); |             binding.ivPreviewPic.setVisibility(View.VISIBLE); | ||||||
|             binding.ivPreviewPic.setImageURI(model.getPreviewPic()); |             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) { |     public final void setLiked(final boolean liked) { | ||||||
|         if (liked) { |         if (liked) { | ||||||
|             // container.setBackgroundColor(0x40FF69B4); |             itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked)); | ||||||
|             return; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -89,8 +89,7 @@ public final class ParentCommentViewHolder extends RecyclerView.ViewHolder { | |||||||
| 
 | 
 | ||||||
|     public final void setLiked(final boolean liked) { |     public final void setLiked(final boolean liked) { | ||||||
|         if (liked) { |         if (liked) { | ||||||
|             // container.setBackgroundColor(0x40FF69B4); |             itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked)); | ||||||
|             return; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -28,11 +28,12 @@ import static awais.instagrabber.utils.Utils.logCollector; | |||||||
| public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> { | public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> { | ||||||
|     private static final String TAG = "CommentsFetcher"; |     private static final String TAG = "CommentsFetcher"; | ||||||
| 
 | 
 | ||||||
|     private final String shortCode; |     private final String shortCode, endCursor; | ||||||
|     private final FetchListener<List<CommentModel>> fetchListener; |     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.shortCode = shortCode; | ||||||
|  |         this.endCursor = endCursor; | ||||||
|         this.fetchListener = fetchListener; |         this.fetchListener = fetchListener; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -48,6 +49,7 @@ 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":""} |         https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""} | ||||||
|          */ |          */ | ||||||
|         final List<CommentModel> commentModels = getParentComments(); |         final List<CommentModel> commentModels = getParentComments(); | ||||||
|  |         if (commentModels != null) { | ||||||
|             for (final CommentModel commentModel : commentModels) { |             for (final CommentModel commentModel : commentModels) { | ||||||
|                 final List<CommentModel> childCommentModels = commentModel.getChildCommentModels(); |                 final List<CommentModel> childCommentModels = commentModel.getChildCommentModels(); | ||||||
|                 if (childCommentModels != null) { |                 if (childCommentModels != null) { | ||||||
| @ -60,6 +62,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |         } | ||||||
|         return commentModels; |         return commentModels; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -76,11 +79,10 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|     @NonNull |     @NonNull | ||||||
|     private synchronized List<CommentModel> getChildComments(final String commentId) { |     private synchronized List<CommentModel> getChildComments(final String commentId) { | ||||||
|         final List<CommentModel> commentModels = new ArrayList<>(); |         final List<CommentModel> commentModels = new ArrayList<>(); | ||||||
|         String endCursor = ""; |         String childEndCursor = ""; | ||||||
|         while (endCursor != null) { |         while (childEndCursor != null) { | ||||||
|             final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" + |             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 { |             try { | ||||||
|                 final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |                 final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||||
|                 conn.setUseCaches(false); |                 conn.setUseCaches(false); | ||||||
| @ -93,8 +95,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                                                                                                  .getJSONObject("edge_threaded_comments"); |                                                                                                  .getJSONObject("edge_threaded_comments"); | ||||||
| 
 | 
 | ||||||
|                     final JSONObject pageInfo = data.getJSONObject("page_info"); |                     final JSONObject pageInfo = data.getJSONObject("page_info"); | ||||||
|                     endCursor = pageInfo.getString("end_cursor"); |                     childEndCursor = pageInfo.getString("end_cursor"); | ||||||
|                     if (TextUtils.isEmpty(endCursor)) endCursor = null; |                     if (TextUtils.isEmpty(childEndCursor)) childEndCursor = null; | ||||||
| 
 | 
 | ||||||
|                     final JSONArray childComments = data.optJSONArray("edges"); |                     final JSONArray childComments = data.optJSONArray("edges"); | ||||||
|                     if (childComments != null) { |                     if (childComments != null) { | ||||||
| @ -120,6 +122,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                                                                                    false, |                                                                                    false, | ||||||
|                                                                                    false, |                                                                                    false, | ||||||
|                                                                                    false, |                                                                                    false, | ||||||
|  |                                                                                    false, | ||||||
|                                                                                    false); |                                                                                    false); | ||||||
| 
 | 
 | ||||||
|                                 final JSONObject likedBy = childComment.optJSONObject("edge_liked_by"); |                                 final JSONObject likedBy = childComment.optJSONObject("edge_liked_by"); | ||||||
| @ -142,6 +145,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                                                  "getChildComments", |                                                  "getChildComments", | ||||||
|                                                  new Pair<>("commentModels.size", commentModels.size())); |                                                  new Pair<>("commentModels.size", commentModels.size())); | ||||||
|                 if (BuildConfig.DEBUG) Log.e(TAG, "", e); |                 if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||||
|  |                 if (fetchListener != null) fetchListener.onFailure(e); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @ -152,17 +156,14 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|     @NonNull |     @NonNull | ||||||
|     private synchronized List<CommentModel> getParentComments() { |     private synchronized List<CommentModel> getParentComments() { | ||||||
|         final List<CommentModel> commentModels = new ArrayList<>(); |         final List<CommentModel> commentModels = new ArrayList<>(); | ||||||
|         String endCursor = ""; |  | ||||||
|         while (endCursor != null) { |  | ||||||
|             final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" + |             final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" + | ||||||
|                     "{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}"; |                     "{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}"; | ||||||
| 
 |  | ||||||
|         try { |         try { | ||||||
|                 final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); |                 final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); | ||||||
|                 conn.setUseCaches(false); |                 conn.setUseCaches(false); | ||||||
|                 conn.connect(); |                 conn.connect(); | ||||||
| 
 | 
 | ||||||
|                 if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break; |                 if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null; | ||||||
|                 else { |                 else { | ||||||
|                     final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") |                     final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") | ||||||
|                                                                                                            .getJSONObject("shortcode_media") |                                                                                                            .getJSONObject("shortcode_media") | ||||||
| @ -170,8 +171,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                                                                                                                    "edge_media_to_parent_comment"); |                                                                                                                    "edge_media_to_parent_comment"); | ||||||
| 
 | 
 | ||||||
|                     final JSONObject pageInfo = parentComments.getJSONObject("page_info"); |                     final JSONObject pageInfo = parentComments.getJSONObject("page_info"); | ||||||
|                     endCursor = pageInfo.optString("end_cursor"); |                     final String foundEndCursor = pageInfo.optString("end_cursor"); | ||||||
|                     if (TextUtils.isEmpty(endCursor)) endCursor = null; |                     final boolean hasNextPage = pageInfo.optBoolean("has_next_page", !TextUtils.isEmpty(foundEndCursor)); | ||||||
| 
 | 
 | ||||||
|                     // final boolean containsToken = endCursor.contains("bifilter_token"); |                     // final boolean containsToken = endCursor.contains("bifilter_token"); | ||||||
|                     // if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) { |                     // 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, |                                                                            false, | ||||||
|  |                                                                            false, | ||||||
|                                                                            false); |                                                                            false); | ||||||
| 
 | 
 | ||||||
|                         final JSONObject likedBy = comment.optJSONObject("edge_liked_by"); |                         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, |                                                                            likedBy != null ? likedBy.optLong("count", 0) : 0, | ||||||
|                                                                            comment.getBoolean("viewer_has_liked"), |                                                                            comment.getBoolean("viewer_has_liked"), | ||||||
|                                                                            profileModel); |                                                                            profileModel); | ||||||
|  |                         if (i == 0 && !foundEndCursor.contains("tao_cursor")) | ||||||
|  |                             commentModel.setPageCursor(hasNextPage, TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor); | ||||||
|                         JSONObject tempJsonObject; |                         JSONObject tempJsonObject; | ||||||
|                         final JSONArray childCommentsArray; |                         final JSONArray childCommentsArray; | ||||||
|                         final int childCommentsLen; |                         final int childCommentsLen; | ||||||
| @ -227,13 +231,13 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                                 && (childCommentsLen = childCommentsArray.length()) > 0) { |                                 && (childCommentsLen = childCommentsArray.length()) > 0) { | ||||||
| 
 | 
 | ||||||
|                             final String childEndCursor; |                             final String childEndCursor; | ||||||
|                             final boolean hasNextPage; |                             final boolean childHasNextPage; | ||||||
|                             if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) { |                             if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) { | ||||||
|                                 childEndCursor = tempJsonObject.optString("end_cursor"); |                                 childEndCursor = tempJsonObject.optString("end_cursor"); | ||||||
|                                 hasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor)); |                                 childHasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor)); | ||||||
|                             } else { |                             } else { | ||||||
|                                 childEndCursor = null; |                                 childEndCursor = null; | ||||||
|                                 hasNextPage = false; |                                 childHasNextPage = false; | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                             final List<CommentModel> childCommentModels = new ArrayList<>(); |                             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, |                                                                                         false, | ||||||
|  |                                                                                         false, | ||||||
|                                                                                         false); |                                                                                         false); | ||||||
| 
 | 
 | ||||||
|                                 tempJsonObject = childComment.optJSONObject("edge_liked_by"); |                                 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"), |                                                                         childComment.getBoolean("viewer_has_liked"), | ||||||
|                                                                         childProfileModel)); |                                                                         childProfileModel)); | ||||||
|                             } |                             } | ||||||
|                             childCommentModels.get(childCommentsLen - 1).setPageCursor(hasNextPage, childEndCursor); |                             childCommentModels.get(childCommentsLen - 1).setPageCursor(childHasNextPage, childEndCursor); | ||||||
|                             commentModel.setChildCommentModels(childCommentModels); |                             commentModel.setChildCommentModels(childCommentModels); | ||||||
|                         } |                         } | ||||||
|                         commentModels.add(commentModel); |                         commentModels.add(commentModel); | ||||||
| @ -280,8 +285,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod | |||||||
|                     logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments", |                     logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments", | ||||||
|                                                  new Pair<>("commentModelsList.size", commentModels.size())); |                                                  new Pair<>("commentModelsList.size", commentModels.size())); | ||||||
|                 if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); |                 if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); | ||||||
|                 break; |                 if (fetchListener != null) fetchListener.onFailure(e); | ||||||
|             } |                 return null; | ||||||
|             } |             } | ||||||
|         return commentModels; |         return commentModels; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -1,34 +1,61 @@ | |||||||
| package awais.instagrabber.asyncs; | package awais.instagrabber.asyncs; | ||||||
| 
 | 
 | ||||||
|  | //import android.os.Handler; | ||||||
|  | //import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import awais.instagrabber.customviews.helpers.PostFetcher; | import awais.instagrabber.customviews.helpers.PostFetcher; | ||||||
| import awais.instagrabber.interfaces.FetchListener; | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | 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.FeedService; | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| 
 | 
 | ||||||
|  | import static awais.instagrabber.utils.Utils.settingsHelper; | ||||||
|  | 
 | ||||||
| public class FeedPostFetchService implements PostFetcher.PostFetchService { | public class FeedPostFetchService implements PostFetcher.PostFetchService { | ||||||
|     private static final String TAG = "FeedPostFetchService"; |     private static final String TAG = "FeedPostFetchService"; | ||||||
|     private final FeedService feedService; |     private final FeedService feedService; | ||||||
|     private String nextCursor; |     private String nextCursor; | ||||||
|  | //    private final Handler handler; | ||||||
|     private boolean hasNextPage; |     private boolean hasNextPage; | ||||||
|  | //    private static final int DELAY_MILLIS = 500; | ||||||
| 
 | 
 | ||||||
|     public FeedPostFetchService() { |     public FeedPostFetchService() { | ||||||
|         feedService = FeedService.getInstance(); |         feedService = FeedService.getInstance(); | ||||||
|  | //        handler = new Handler(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { |     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 |             @Override | ||||||
|             public void onSuccess(final PostsFetchResponse result) { |             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(); |                 nextCursor = result.getNextCursor(); | ||||||
|                 hasNextPage = result.hasNextPage(); |                 hasNextPage = result.hasNextPage(); | ||||||
|  |                 feedModels.addAll(result.getFeedModels()); | ||||||
|                 if (fetchListener != null) { |                 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> { | public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsyncTask.NotificationCounts> { | ||||||
|     private static final String TAG = "GetActivityAsyncTask"; |     private static final String TAG = "GetActivityAsyncTask"; | ||||||
| 
 | 
 | ||||||
|     private OnTaskCompleteListener onTaskCompleteListener; |     private final OnTaskCompleteListener onTaskCompleteListener; | ||||||
| 
 | 
 | ||||||
|     public GetActivityAsyncTask(final OnTaskCompleteListener onTaskCompleteListener) { |     public GetActivityAsyncTask(final OnTaskCompleteListener onTaskCompleteListener) { | ||||||
|         this.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) { |     protected NotificationCounts doInBackground(final String... cookiesArray) { | ||||||
|         if (cookiesArray == null) return null; |         if (cookiesArray == null) return null; | ||||||
|         final String cookie = cookiesArray[0]; |         final String cookie = cookiesArray[0]; | ||||||
| @ -70,11 +79,11 @@ public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsy | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static class NotificationCounts { |     public static class NotificationCounts { | ||||||
|         private int relationshipsCount; |         private final int relationshipsCount; | ||||||
|         private int userTagsCount; |         private final int userTagsCount; | ||||||
|         private int commentsCount; |         private final int commentsCount; | ||||||
|         private int commentLikesCount; |         private final int commentLikesCount; | ||||||
|         private int likesCount; |         private final int likesCount; | ||||||
| 
 | 
 | ||||||
|         public NotificationCounts(final int relationshipsCount, |         public NotificationCounts(final int relationshipsCount, | ||||||
|                                   final int userTagsCount, |                                   final int userTagsCount, | ||||||
|  | |||||||
| @ -89,6 +89,7 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> { | |||||||
|             if (logCollector != null) |             if (logCollector != null) | ||||||
|                 logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); |                 logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); | ||||||
|             if (BuildConfig.DEBUG) Log.e(TAG, "", e); |             if (BuildConfig.DEBUG) Log.e(TAG, "", e); | ||||||
|  |             if (fetchListener != null) fetchListener.onFailure(e); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return result; |         return result; | ||||||
|  | |||||||
| @ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher; | |||||||
| import awais.instagrabber.interfaces.FetchListener; | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.HashtagModel; | 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.ServiceCallback; | ||||||
| import awais.instagrabber.webservices.TagsService; | import awais.instagrabber.webservices.TagsService; | ||||||
| import awais.instagrabber.webservices.TagsService.TagPostsFetchResponse; |  | ||||||
| 
 | 
 | ||||||
| public class HashtagPostFetchService implements PostFetcher.PostFetchService { | public class HashtagPostFetchService implements PostFetcher.PostFetchService { | ||||||
|     private final TagsService tagsService; |     private final TagsService tagsService; | ||||||
|  |     private final GraphQLService graphQLService; | ||||||
|     private final HashtagModel hashtagModel; |     private final HashtagModel hashtagModel; | ||||||
|     private String nextMaxId; |     private String nextMaxId; | ||||||
|     private boolean moreAvailable; |     private boolean moreAvailable; | ||||||
| @ -20,19 +22,20 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService { | |||||||
|     public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) { |     public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) { | ||||||
|         this.hashtagModel = hashtagModel; |         this.hashtagModel = hashtagModel; | ||||||
|         this.isLoggedIn = isLoggedIn; |         this.isLoggedIn = isLoggedIn; | ||||||
|         tagsService = TagsService.getInstance(); |         tagsService = isLoggedIn ? TagsService.getInstance() : null; | ||||||
|  |         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { |     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||||
|         final ServiceCallback cb = new ServiceCallback<TagPostsFetchResponse>() { |         final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final TagPostsFetchResponse result) { |             public void onSuccess(final PostsFetchResponse result) { | ||||||
|                 if (result == null) return; |                 if (result == null) return; | ||||||
|                 nextMaxId = result.getNextMaxId(); |                 nextMaxId = result.getNextCursor(); | ||||||
|                 moreAvailable = result.isMoreAvailable(); |                 moreAvailable = result.hasNextPage(); | ||||||
|                 if (fetchListener != null) { |                 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); |         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 |     @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.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.LocationModel; | 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; | ||||||
| import awais.instagrabber.webservices.LocationService.LocationPostsFetchResponse; |  | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| 
 | 
 | ||||||
| public class LocationPostFetchService implements PostFetcher.PostFetchService { | public class LocationPostFetchService implements PostFetcher.PostFetchService { | ||||||
|     private final LocationService locationService; |     private final LocationService locationService; | ||||||
|  |     private final GraphQLService graphQLService; | ||||||
|     private final LocationModel locationModel; |     private final LocationModel locationModel; | ||||||
|     private String nextMaxId; |     private String nextMaxId; | ||||||
|     private boolean moreAvailable; |     private boolean moreAvailable; | ||||||
| @ -20,19 +22,20 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService { | |||||||
|     public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) { |     public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) { | ||||||
|         this.locationModel = locationModel; |         this.locationModel = locationModel; | ||||||
|         this.isLoggedIn = isLoggedIn; |         this.isLoggedIn = isLoggedIn; | ||||||
|         locationService = LocationService.getInstance(); |         locationService = isLoggedIn ? LocationService.getInstance() : null; | ||||||
|  |         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { |     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||||
|         final ServiceCallback cb = new ServiceCallback<LocationPostsFetchResponse>() { |         final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final LocationPostsFetchResponse result) { |             public void onSuccess(final PostsFetchResponse result) { | ||||||
|                 if (result == null) return; |                 if (result == null) return; | ||||||
|                 nextMaxId = result.getNextMaxId(); |                 nextMaxId = result.getNextCursor(); | ||||||
|                 moreAvailable = result.isMoreAvailable(); |                 moreAvailable = result.hasNextPage(); | ||||||
|                 if (fetchListener != null) { |                 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); |         if (isLoggedIn) locationService.fetchPosts(locationModel.getId(), nextMaxId, cb); | ||||||
|         else locationService.fetchGraphQLPosts(locationModel.getId(), nextMaxId, cb); |         else graphQLService.fetchLocationPosts(locationModel.getId(), nextMaxId, cb); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|  | |||||||
| @ -3,21 +3,14 @@ package awais.instagrabber.asyncs; | |||||||
| import android.os.AsyncTask; | import android.os.AsyncTask; | ||||||
| import android.util.Log; | 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.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import awais.instagrabber.BuildConfig; | import awais.instagrabber.BuildConfig; | ||||||
| import awais.instagrabber.interfaces.FetchListener; | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.NotificationModel; | import awais.instagrabber.models.NotificationModel; | ||||||
| import awais.instagrabber.models.enums.NotificationType; | import awais.instagrabber.webservices.NewsService; | ||||||
| import awais.instagrabber.utils.Constants; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| import awais.instagrabber.utils.LocaleUtils; |  | ||||||
| import awais.instagrabber.utils.NetworkUtils; |  | ||||||
| import awaisomereport.LogCollector; | import awaisomereport.LogCollector; | ||||||
| 
 | 
 | ||||||
| import static awais.instagrabber.utils.Utils.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 static final String TAG = "NotificationsFetcher"; | ||||||
| 
 | 
 | ||||||
|     private final FetchListener<List<NotificationModel>> fetchListener; |     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; |         this.fetchListener = fetchListener; | ||||||
|  |         newsService = NewsService.getInstance(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     protected List<NotificationModel> doInBackground(final Void... voids) { |     protected List<NotificationModel> doInBackground(final Void... voids) { | ||||||
|         List<NotificationModel> result = new ArrayList<>(); |         List<NotificationModel> notificationModels = 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(); |  | ||||||
| 
 | 
 | ||||||
|             if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { |         newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<NotificationModel>>() { | ||||||
|                 final JSONObject page = new JSONObject(NetworkUtils.readFromConnection(conn)) |             @Override | ||||||
|                         .getJSONObject("graphql") |             public void onSuccess(final List<NotificationModel> result) { | ||||||
|                         .getJSONObject("user"); |                 if (result == null) return; | ||||||
|                 final JSONObject ewaf = page.getJSONObject("activity_feed") |                 notificationModels.addAll(result); | ||||||
|                                             .optJSONObject("edge_web_activity_feed"); |                 if (fetchedWeb) { | ||||||
|                 final JSONObject efr = page.optJSONObject("edge_follow_requests"); |                     fetchListener.onResult(notificationModels); | ||||||
|                 JSONObject data; |                 } | ||||||
|                 JSONArray media; |                 else { | ||||||
|                 if (ewaf != null |                     fetchedWeb = true; | ||||||
|                         && (media = ewaf.optJSONArray("edges")) != null |                     newsService.fetchWebInbox(markAsSeen, this); | ||||||
|                         && 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)); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|                 if (efr != null |             @Override | ||||||
|                         && (media = efr.optJSONArray("edges")) != null |             public void onFailure(final Throwable t) { | ||||||
|                         && media.length() > 0 |                 // Log.e(TAG, "onFailure: ", t); | ||||||
|                         && media.optJSONObject(0).optJSONObject("node") != null) { |                 if (fetchListener != null) { | ||||||
|                     for (int i = 0; i < media.length(); ++i) { |                     fetchListener.onFailure(t); | ||||||
|                         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)); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             } |         }); | ||||||
|             conn.disconnect(); |         return notificationModels; | ||||||
|         } 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 |     @Override | ||||||
|     protected void onPreExecute() { |     protected void onPreExecute() { | ||||||
|         if (fetchListener != null) fetchListener.doBefore(); |         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"), |                             owner.optInt("edge_followed_by"), | ||||||
|                             -1, |                             -1, | ||||||
|                             owner.optBoolean("followed_by_viewer"), |                             owner.optBoolean("followed_by_viewer"), | ||||||
|  |                             false, | ||||||
|                             owner.optBoolean("restricted_by_viewer"), |                             owner.optBoolean("restricted_by_viewer"), | ||||||
|                             owner.optBoolean("blocked_by_viewer"), |                             owner.optBoolean("blocked_by_viewer"), | ||||||
|                             owner.optBoolean("requested_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_followed_by").getLong("count"), | ||||||
|                         user.getJSONObject("edge_follow").getLong("count"), |                         user.getJSONObject("edge_follow").getLong("count"), | ||||||
|                         user.optBoolean("followed_by_viewer"), |                         user.optBoolean("followed_by_viewer"), | ||||||
|  |                         user.optBoolean("follows_viewer"), | ||||||
|                         user.optBoolean("restricted_by_viewer"), |                         user.optBoolean("restricted_by_viewer"), | ||||||
|                         user.optBoolean("blocked_by_viewer"), |                         user.optBoolean("blocked_by_viewer"), | ||||||
|                         user.optBoolean("requested_by_viewer")); |                         user.optBoolean("requested_by_viewer")); | ||||||
|  | |||||||
| @ -40,23 +40,7 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> { | |||||||
|     protected String doInBackground(final Void... voids) { |     protected String doInBackground(final Void... voids) { | ||||||
|         String out = null; |         String out = null; | ||||||
|         if (isHashtag) out = picUrl; |         if (isHashtag) out = picUrl; | ||||||
|         else try { |         else if (Utils.settingsHelper.getBoolean(Constants.INSTADP)) 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); |  | ||||||
| 
 |  | ||||||
|             final String result = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(conn) : null; |  | ||||||
|             conn.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(out) && Utils.settingsHelper.getBoolean(Constants.INSTADP)) { |  | ||||||
|             final HttpURLConnection backup = |             final HttpURLConnection backup = | ||||||
|                     (HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection(); |                     (HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection(); | ||||||
|             backup.setUseCaches(false); |             backup.setUseCaches(false); | ||||||
| @ -87,7 +71,6 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             } |  | ||||||
|             if (TextUtils.isEmpty(out)) out = picUrl; |             if (TextUtils.isEmpty(out)) out = picUrl; | ||||||
|         } catch (final Exception e) { |         } catch (final Exception e) { | ||||||
|             if (logCollector != null) |             if (logCollector != null) | ||||||
|  | |||||||
| @ -7,29 +7,34 @@ import awais.instagrabber.interfaces.FetchListener; | |||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.ProfileModel; | import awais.instagrabber.models.ProfileModel; | ||||||
| import awais.instagrabber.repositories.responses.PostsFetchResponse; | import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||||
|  | import awais.instagrabber.webservices.GraphQLService; | ||||||
| import awais.instagrabber.webservices.ProfileService; | import awais.instagrabber.webservices.ProfileService; | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| 
 | 
 | ||||||
| public class ProfilePostFetchService implements PostFetcher.PostFetchService { | public class ProfilePostFetchService implements PostFetcher.PostFetchService { | ||||||
|     private static final String TAG = "ProfilePostFetchService"; |     private static final String TAG = "ProfilePostFetchService"; | ||||||
|     private final ProfileService profileService; |     private final ProfileService profileService; | ||||||
|  |     private final GraphQLService graphQLService; | ||||||
|     private final ProfileModel profileModel; |     private final ProfileModel profileModel; | ||||||
|     private String nextCursor; |     private final boolean isLoggedIn; | ||||||
|     private boolean hasNextPage; |     private String nextMaxId; | ||||||
|  |     private boolean moreAvailable; | ||||||
| 
 | 
 | ||||||
|     public ProfilePostFetchService(final ProfileModel profileModel) { |     public ProfilePostFetchService(final ProfileModel profileModel, final boolean isLoggedIn) { | ||||||
|         this.profileModel = profileModel; |         this.profileModel = profileModel; | ||||||
|         profileService = ProfileService.getInstance(); |         this.isLoggedIn = isLoggedIn; | ||||||
|  |         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||||
|  |         profileService = isLoggedIn ? ProfileService.getInstance() : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { |     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||||
|         profileService.fetchPosts(profileModel, 30, nextCursor, new ServiceCallback<PostsFetchResponse>() { |         final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final PostsFetchResponse result) { |             public void onSuccess(final PostsFetchResponse result) { | ||||||
|                 if (result == null) return; |                 if (result == null) return; | ||||||
|                 nextCursor = result.getNextCursor(); |                 nextMaxId = result.getNextCursor(); | ||||||
|                 hasNextPage = result.hasNextPage(); |                 moreAvailable = result.hasNextPage(); | ||||||
|                 if (fetchListener != null) { |                 if (fetchListener != null) { | ||||||
|                     fetchListener.onResult(result.getFeedModels()); |                     fetchListener.onResult(result.getFeedModels()); | ||||||
|                 } |                 } | ||||||
| @ -42,16 +47,18 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService { | |||||||
|                     fetchListener.onFailure(t); |                     fetchListener.onFailure(t); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         }); |         }; | ||||||
|  |         if (isLoggedIn) profileService.fetchPosts(profileModel.getId(), nextMaxId, cb); | ||||||
|  |         else graphQLService.fetchProfilePosts(profileModel.getId(), 30, nextMaxId, cb); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void reset() { |     public void reset() { | ||||||
|         nextCursor = null; |         nextMaxId = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean hasNextPage() { |     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.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.enums.PostItemType; | 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; | ||||||
| import awais.instagrabber.webservices.ProfileService.SavedPostsFetchResponse; |  | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| 
 | 
 | ||||||
| public class SavedPostFetchService implements PostFetcher.PostFetchService { | public class SavedPostFetchService implements PostFetcher.PostFetchService { | ||||||
|     private final ProfileService profileService; |     private final ProfileService profileService; | ||||||
|  |     private final GraphQLService graphQLService; | ||||||
|     private final String profileId; |     private final String profileId; | ||||||
|     private final PostItemType type; |     private final PostItemType type; | ||||||
|  |     private final boolean isLoggedIn; | ||||||
| 
 | 
 | ||||||
|     private String nextMaxId; |     private String nextMaxId; | ||||||
|     private boolean moreAvailable; |     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.profileId = profileId; | ||||||
|         this.type = type; |         this.type = type; | ||||||
|         profileService = ProfileService.getInstance(); |         this.isLoggedIn = isLoggedIn; | ||||||
|  |         graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); | ||||||
|  |         profileService = isLoggedIn ? ProfileService.getInstance() : null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { |     public void fetch(final FetchListener<List<FeedModel>> fetchListener) { | ||||||
|         final ServiceCallback<SavedPostsFetchResponse> callback = new ServiceCallback<SavedPostsFetchResponse>() { |         final ServiceCallback<PostsFetchResponse> callback = new ServiceCallback<PostsFetchResponse>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final SavedPostsFetchResponse result) { |             public void onSuccess(final PostsFetchResponse result) { | ||||||
|                 if (result == null) return; |                 if (result == null) return; | ||||||
|                 nextMaxId = result.getNextMaxId(); |                 nextMaxId = result.getNextCursor(); | ||||||
|                 moreAvailable = result.isMoreAvailable(); |                 moreAvailable = result.hasNextPage(); | ||||||
|                 if (fetchListener != null) { |                 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); |                 profileService.fetchLiked(nextMaxId, callback); | ||||||
|                 break; |                 break; | ||||||
|             case TAGGED: |             case TAGGED: | ||||||
|                 profileService.fetchTagged(profileId, nextMaxId, callback); |                 if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback); | ||||||
|  |                 else graphQLService.fetchTaggedPosts(profileId, 30, nextMaxId, callback); | ||||||
|                 break; |                 break; | ||||||
|             case SAVED: |             case SAVED: | ||||||
|             default: |             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 { | //     public interface OnBroadcastCompleteListener { | ||||||
| //         void onTaskComplete(DirectThreadBroadcastResponse response); | //         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() { |     private void initSelf() { | ||||||
|         feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class); |         feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class); | ||||||
|         feedViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> { |         feedViewModel.getList().observe(lifeCycleOwner, list -> { | ||||||
|  |             if (list.size() > 0) feedAdapter.submitList(list, () -> { | ||||||
|                 if (!shouldScrollToTop) return; |                 if (!shouldScrollToTop) return; | ||||||
|                 smoothScrollToPosition(0); |                 smoothScrollToPosition(0); | ||||||
|                 shouldScrollToTop = false; |                 shouldScrollToTop = false; | ||||||
|         })); |             }); | ||||||
|  |         }); | ||||||
|         postFetcher = new PostFetcher(postFetchService, fetchListener); |         postFetcher = new PostFetcher(postFetchService, fetchListener); | ||||||
|         if (layoutPreferences.getHasGap()) { |         if (layoutPreferences.getHasGap()) { | ||||||
|             addItemDecoration(gridSpacingItemDecoration); |             addItemDecoration(gridSpacingItemDecoration); | ||||||
|  | |||||||
| @ -161,7 +161,7 @@ public abstract class AppDatabase extends RoomDatabase { | |||||||
|                         final FavoriteType type = favoriteTypeQueryPair.first; |                         final FavoriteType type = favoriteTypeQueryPair.first; | ||||||
|                         final String query = favoriteTypeQueryPair.second; |                         final String query = favoriteTypeQueryPair.second; | ||||||
|                         oldModels.add(new Favorite( |                         oldModels.add(new Favorite( | ||||||
|                                 -1, |                                 0, | ||||||
|                                 query, |                                 query, | ||||||
|                                 type, |                                 type, | ||||||
|                                 queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) |                                 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") |     @Query("SELECT * FROM favorites WHERE query_text = :query and type = :type") | ||||||
|     Favorite findFavoriteByQueryAndType(String query, FavoriteType type); |     Favorite findFavoriteByQueryAndType(String query, FavoriteType type); | ||||||
| 
 | 
 | ||||||
|     @Insert(onConflict = OnConflictStrategy.REPLACE) |     @Insert | ||||||
|     List<Long> insertFavorites(Favorite... favorites); |     List<Long> insertFavorites(Favorite... favorites); | ||||||
| 
 | 
 | ||||||
|     @Update |     @Update | ||||||
|  | |||||||
| @ -9,6 +9,7 @@ import android.graphics.drawable.ColorDrawable; | |||||||
| import android.os.AsyncTask; | import android.os.AsyncTask; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Environment; | import android.os.Environment; | ||||||
|  | import android.util.Log; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| @ -19,6 +20,7 @@ import androidx.annotation.NonNull; | |||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
| import androidx.core.content.ContextCompat; | import androidx.core.content.ContextCompat; | ||||||
| import androidx.fragment.app.DialogFragment; | import androidx.fragment.app.DialogFragment; | ||||||
|  | import androidx.fragment.app.FragmentActivity; | ||||||
| 
 | 
 | ||||||
| import com.facebook.drawee.backends.pipeline.Fresco; | import com.facebook.drawee.backends.pipeline.Fresco; | ||||||
| import com.facebook.drawee.controller.BaseControllerListener; | import com.facebook.drawee.controller.BaseControllerListener; | ||||||
| @ -30,9 +32,19 @@ import java.io.File; | |||||||
| import awais.instagrabber.R; | import awais.instagrabber.R; | ||||||
| import awais.instagrabber.asyncs.ProfilePictureFetcher; | import awais.instagrabber.asyncs.ProfilePictureFetcher; | ||||||
| import awais.instagrabber.databinding.DialogProfilepicBinding; | 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.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.DownloadUtils; | ||||||
| import awais.instagrabber.utils.TextUtils; | 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 { | public class ProfilePicDialogFragment extends DialogFragment { | ||||||
|     private static final String TAG = "ProfilePicDlgFragment"; |     private static final String TAG = "ProfilePicDlgFragment"; | ||||||
| @ -41,9 +53,15 @@ public class ProfilePicDialogFragment extends DialogFragment { | |||||||
|     private final String name; |     private final String name; | ||||||
|     private final String fallbackUrl; |     private final String fallbackUrl; | ||||||
| 
 | 
 | ||||||
|  |     private boolean isLoggedIn; | ||||||
|     private DialogProfilepicBinding binding; |     private DialogProfilepicBinding binding; | ||||||
|     private String url; |     private String url; | ||||||
| 
 | 
 | ||||||
|  |     private final FetchListener<String> fetchListener = profileUrl -> { | ||||||
|  |         url = profileUrl; | ||||||
|  |         setupPhoto(); | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|     public ProfilePicDialogFragment(final String id, final String name, final String fallbackUrl) { |     public ProfilePicDialogFragment(final String id, final String name, final String fallbackUrl) { | ||||||
|         this.id = id; |         this.id = id; | ||||||
|         this.name = name; |         this.name = name; | ||||||
| @ -55,6 +73,8 @@ public class ProfilePicDialogFragment extends DialogFragment { | |||||||
|                              final ViewGroup container, |                              final ViewGroup container, | ||||||
|                              final Bundle savedInstanceState) { |                              final Bundle savedInstanceState) { | ||||||
|         binding = DialogProfilepicBinding.inflate(inflater, container, false); |         binding = DialogProfilepicBinding.inflate(inflater, container, false); | ||||||
|  |         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||||
|  |         isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; | ||||||
|         return binding.getRoot(); |         return binding.getRoot(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -83,7 +103,7 @@ public class ProfilePicDialogFragment extends DialogFragment { | |||||||
|     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { |     public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { | ||||||
|         super.onViewCreated(view, savedInstanceState); |         super.onViewCreated(view, savedInstanceState); | ||||||
|         init(); |         init(); | ||||||
|         fetchPhoto(); |         fetchAvatar(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void init() { |     private void init() { | ||||||
| @ -106,9 +126,29 @@ public class ProfilePicDialogFragment extends DialogFragment { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void fetchPhoto() { |     private void fetchAvatar() { | ||||||
|         final FetchListener<String> fetchListener = profileUrl -> { |         if (isLoggedIn) { | ||||||
|             url = profileUrl; |             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 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)) { |         if (TextUtils.isEmpty(url)) { | ||||||
|             url = fallbackUrl; |             url = fallbackUrl; | ||||||
|         } |         } | ||||||
| @ -135,8 +175,6 @@ public class ProfilePicDialogFragment extends DialogFragment { | |||||||
|                 }) |                 }) | ||||||
|                 .build(); |                 .build(); | ||||||
|         binding.imageViewer.setController(controller); |         binding.imageViewer.setController(controller); | ||||||
|         }; |  | ||||||
|         new ProfilePictureFetcher(name, id, fetchListener, url, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void downloadProfilePicture() { |     private void downloadProfilePicture() { | ||||||
|  | |||||||
| @ -25,6 +25,7 @@ import androidx.appcompat.widget.LinearLayoutCompat; | |||||||
| import androidx.fragment.app.FragmentManager; | import androidx.fragment.app.FragmentManager; | ||||||
| import androidx.fragment.app.FragmentTransaction; | import androidx.fragment.app.FragmentTransaction; | ||||||
| import androidx.lifecycle.ViewModelProvider; | import androidx.lifecycle.ViewModelProvider; | ||||||
|  | import androidx.navigation.NavController; | ||||||
| import androidx.navigation.NavDirections; | import androidx.navigation.NavDirections; | ||||||
| import androidx.navigation.fragment.NavHostFragment; | import androidx.navigation.fragment.NavHostFragment; | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| @ -32,11 +33,18 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | |||||||
| 
 | 
 | ||||||
| import com.google.android.material.bottomsheet.BottomSheetDialogFragment; | 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.R; | ||||||
| import awais.instagrabber.adapters.CommentsAdapter; | import awais.instagrabber.adapters.CommentsAdapter; | ||||||
| import awais.instagrabber.asyncs.CommentsFetcher; | import awais.instagrabber.asyncs.CommentsFetcher; | ||||||
|  | import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||||
| import awais.instagrabber.databinding.FragmentCommentsBinding; | import awais.instagrabber.databinding.FragmentCommentsBinding; | ||||||
| import awais.instagrabber.dialogs.ProfilePicDialogFragment; | import awais.instagrabber.dialogs.ProfilePicDialogFragment; | ||||||
|  | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.CommentModel; | import awais.instagrabber.models.CommentModel; | ||||||
| import awais.instagrabber.models.ProfileModel; | import awais.instagrabber.models.ProfileModel; | ||||||
| import awais.instagrabber.utils.Constants; | import awais.instagrabber.utils.Constants; | ||||||
| @ -56,17 +64,48 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
| 
 | 
 | ||||||
|     private CommentsAdapter commentsAdapter; |     private CommentsAdapter commentsAdapter; | ||||||
|     private FragmentCommentsBinding binding; |     private FragmentCommentsBinding binding; | ||||||
|     private String shortCode; |     private LinearLayoutManager layoutManager; | ||||||
|     private String userId; |     private RecyclerLazyLoader lazyLoader; | ||||||
|  |     private String shortCode, userId, endCursor = null; | ||||||
|     private Resources resources; |     private Resources resources; | ||||||
|     private InputMethodManager imm; |     private InputMethodManager imm; | ||||||
|     private AppCompatActivity fragmentActivity; |     private AppCompatActivity fragmentActivity; | ||||||
|     private LinearLayoutCompat root; |     private LinearLayoutCompat root; | ||||||
|     private boolean shouldRefresh = true; |     private boolean shouldRefresh = true, hasNextPage = false; | ||||||
|     private MediaService mediaService; |     private MediaService mediaService; | ||||||
|     private String postId; |     private String postId; | ||||||
|  |     private AsyncTask<Void, Void, List<CommentModel>> currentlyRunning; | ||||||
|     private CommentsViewModel commentsViewModel; |     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() { |     private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() { | ||||||
|         @Override |         @Override | ||||||
|         public void onClick(final CommentModel comment) { |         public void onClick(final CommentModel comment) { | ||||||
| @ -181,11 +220,11 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onRefresh() { |     public void onRefresh() { | ||||||
|         binding.swipeRefreshLayout.setRefreshing(true); |         endCursor = null; | ||||||
|         new CommentsFetcher(shortCode, commentModels -> { |         lazyLoader.resetState(); | ||||||
|             commentsViewModel.getList().postValue(commentModels); |         commentsViewModel.getList().postValue(Collections.emptyList()); | ||||||
|             binding.swipeRefreshLayout.setRefreshing(false); |         stopCurrentExecutor(); | ||||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |         currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void init() { |     private void init() { | ||||||
| @ -198,7 +237,8 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); |         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||||
|         binding.swipeRefreshLayout.setRefreshing(true); |         binding.swipeRefreshLayout.setRefreshing(true); | ||||||
|         commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class); |         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); |         commentsAdapter = new CommentsAdapter(commentCallback); | ||||||
|         binding.rvComments.setAdapter(commentsAdapter); |         binding.rvComments.setAdapter(commentsAdapter); | ||||||
|         commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList); |         commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList); | ||||||
| @ -226,6 +266,13 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|             }); |             }); | ||||||
|             binding.commentField.setEndIconOnClickListener(newCommentListener); |             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(); |         onRefresh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -249,30 +296,29 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|                 && (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) { |                 && (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) { | ||||||
|             commentDialogList = new String[]{ |             commentDialogList = new String[]{ | ||||||
|                     resources.getString(R.string.open_profile), |                     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), | ||||||
|                     resources.getString(R.string.comment_viewer_reply_comment), |                     resources.getString(R.string.comment_viewer_reply_comment), | ||||||
|                     commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_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_like_comment), | ||||||
|  |                     resources.getString(R.string.comment_viewer_translate_comment), | ||||||
|                     resources.getString(R.string.comment_viewer_delete_comment) |                     resources.getString(R.string.comment_viewer_delete_comment) | ||||||
|             }; |             }; | ||||||
|         } else if (!TextUtils.isEmpty(cookie)) { |         } else if (!TextUtils.isEmpty(cookie)) { | ||||||
|             commentDialogList = new String[]{ |             commentDialogList = new String[]{ | ||||||
|                     resources.getString(R.string.open_profile), |                     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), | ||||||
|                     resources.getString(R.string.comment_viewer_reply_comment), |                     resources.getString(R.string.comment_viewer_reply_comment), | ||||||
|                     commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_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_like_comment), | ||||||
|  |                     resources.getString(R.string.comment_viewer_translate_comment) | ||||||
|             }; |             }; | ||||||
|         } else { |         } else { | ||||||
|             commentDialogList = new String[]{ |             commentDialogList = new String[]{ | ||||||
|                     resources.getString(R.string.open_profile), |                     resources.getString(R.string.open_profile), | ||||||
|                     resources.getString(R.string.view_pfp), |                     resources.getString(R.string.comment_viewer_copy_comment), | ||||||
| //                    resources.getString(R.string.comment_viewer_copy_user), |                     resources.getString(R.string.comment_viewer_see_likers) | ||||||
|                     resources.getString(R.string.comment_viewer_copy_comment) |  | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
| @ -284,25 +330,20 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|                 case 0: // open profile |                 case 0: // open profile | ||||||
|                     openProfile("@" + profileModel.getUsername()); |                     openProfile("@" + profileModel.getUsername()); | ||||||
|                     break; |                     break; | ||||||
|                 case 1: // view profile pic |                 case 1: // copy comment | ||||||
|                     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 |  | ||||||
|                     Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText()); |                     Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText()); | ||||||
|                     break; |                     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 |                 case 3: // reply to comment | ||||||
|                     // final View focus = binding.rvComments.findViewWithTag(commentModel); |  | ||||||
|                     // focus.setBackgroundColor(0x80888888); |  | ||||||
|                     commentsAdapter.setSelected(commentModel); |                     commentsAdapter.setSelected(commentModel); | ||||||
|                     String mention = "@" + profileModel.getUsername() + " "; |                     String mention = "@" + profileModel.getUsername() + " "; | ||||||
|                     binding.commentText.setText(mention); |                     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(); |                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|                                     return; |                                     return; | ||||||
|                                 } |                                 } | ||||||
|                                 onRefresh(); |                                 commentsAdapter.setLiked(commentModel, true); | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                             @Override |                             @Override | ||||||
| @ -344,7 +385,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); |                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|                                 return; |                                 return; | ||||||
|                             } |                             } | ||||||
|                             onRefresh(); |                             commentsAdapter.setLiked(commentModel, false); | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         @Override |                         @Override | ||||||
| @ -354,7 +395,29 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|                         } |                         } | ||||||
|                     }); |                     }); | ||||||
|                     break; |                     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); |                     final String userId = CookieUtils.getUserIdFromCookie(cookie); | ||||||
|                     if (userId == null) return; |                     if (userId == null) return; | ||||||
|                     mediaService.deleteComment( |                     mediaService.deleteComment( | ||||||
| @ -389,4 +452,25 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl | |||||||
|         final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username); |         final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username); | ||||||
|         NavHostFragment.findNavController(this).navigate(action); |         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 |             @Override | ||||||
|             public void onSuccess(final List<Favorite> favorites) { |             public void onSuccess(final List<Favorite> favorites) { | ||||||
|                 favoritesViewModel.getList().postValue(favorites); |                 favoritesViewModel.getList().postValue(favorites); | ||||||
|                 fetchMissingInfo(favorites); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
| @ -156,109 +155,4 @@ public class FavoritesFragment extends Fragment { | |||||||
|         binding.favoriteList.setAdapter(adapter); |         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.appcompat.widget.SearchView; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import androidx.navigation.fragment.NavHostFragment; | import androidx.navigation.fragment.NavHostFragment; | ||||||
|  | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Arrays; |  | ||||||
| 
 | 
 | ||||||
| import awais.instagrabber.BuildConfig; |  | ||||||
| import awais.instagrabber.R; | import awais.instagrabber.R; | ||||||
| import awais.instagrabber.adapters.FollowAdapter; | import awais.instagrabber.adapters.FollowAdapter; | ||||||
|  | import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; | ||||||
| import awais.instagrabber.databinding.FragmentFollowersViewerBinding; | import awais.instagrabber.databinding.FragmentFollowersViewerBinding; | ||||||
| import awais.instagrabber.models.FollowModel; | import awais.instagrabber.models.FollowModel; | ||||||
| import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; |  | ||||||
| import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse; | import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse; | ||||||
| import awais.instagrabber.utils.Constants; |  | ||||||
| import awais.instagrabber.utils.CookieUtils; |  | ||||||
| import awais.instagrabber.utils.TextUtils; | import awais.instagrabber.utils.TextUtils; | ||||||
| import awais.instagrabber.webservices.FriendshipService; | import awais.instagrabber.webservices.FriendshipService; | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| import awaisomereport.LogCollector; |  | ||||||
| import thoughtbot.expandableadapter.ExpandableGroup; | 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 { | public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||||
|     private static final String TAG = "FollowViewerFragment"; |     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> followersModels = new ArrayList<>(); | ||||||
|     private final ArrayList<FollowModel> allFollowing = new ArrayList<>(); |     private final ArrayList<FollowModel> allFollowing = new ArrayList<>(); | ||||||
| 
 | 
 | ||||||
|     private boolean isFollowersList, isCompare = false, loading = false; |     private boolean moreAvailable = true, isFollowersList, isCompare = false, loading = false, shouldRefresh = true; | ||||||
|     private String profileId, username, namePost, type; |     private String profileId, username, namePost, type, endCursor; | ||||||
|     private Resources resources; |     private Resources resources; | ||||||
|  |     private LinearLayoutManager layoutManager; | ||||||
|  |     private RecyclerLazyLoader lazyLoader; | ||||||
|     private FollowModel model; |     private FollowModel model; | ||||||
|     private FollowAdapter adapter; |     private FollowAdapter adapter; | ||||||
|     private View.OnClickListener clickListener; |     private View.OnClickListener clickListener; | ||||||
| @ -61,9 +56,61 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | |||||||
|     private AsyncTask<Void, Void, FollowModel[]> currentlyExecuting; |     private AsyncTask<Void, Void, FollowModel[]> currentlyExecuting; | ||||||
|     private SwipeRefreshLayout root; |     private SwipeRefreshLayout root; | ||||||
|     private FriendshipService friendshipService; |     private FriendshipService friendshipService; | ||||||
|     private boolean shouldRefresh = true; |  | ||||||
|     private AppCompatActivity fragmentActivity; |     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 |     @Override | ||||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { |     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
| @ -141,13 +188,13 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | |||||||
|     public void onRefresh() { |     public void onRefresh() { | ||||||
|         if (isCompare) listCompare(); |         if (isCompare) listCompare(); | ||||||
|         else listFollows(); |         else listFollows(); | ||||||
|  |         endCursor = null; | ||||||
|  |         lazyLoader.resetState(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void listFollows() { |     private void listFollows() { | ||||||
|         loading = true; |  | ||||||
|         type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following); |         type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following); | ||||||
|         setSubtitle(type); |         setSubtitle(type); | ||||||
|         followModels.clear(); |  | ||||||
|         final ServiceCallback<FriendshipRepoListFetchResponse> cb = new ServiceCallback<FriendshipRepoListFetchResponse>() { |         final ServiceCallback<FriendshipRepoListFetchResponse> cb = new ServiceCallback<FriendshipRepoListFetchResponse>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final FriendshipRepoListFetchResponse result) { |             public void onSuccess(final FriendshipRepoListFetchResponse result) { | ||||||
| @ -156,82 +203,70 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | |||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|                 else { |                 else { | ||||||
|  |                     int oldSize = followModels.size() == 0 ? 0 : followModels.size() - 1; | ||||||
|                     followModels.addAll(result.getItems()); |                     followModels.addAll(result.getItems()); | ||||||
|                     if (result.isMoreAvailable()) { |                     if (result.isMoreAvailable()) { | ||||||
|                         friendshipService.getList(isFollowersList, profileId, result.getNextMaxId(), this); |                         moreAvailable = true; | ||||||
|  |                         endCursor = result.getNextMaxId(); | ||||||
|                     } |                     } | ||||||
|                     else { |                     else moreAvailable = false; | ||||||
|                     binding.swipeRefreshLayout.setRefreshing(false); |                     binding.swipeRefreshLayout.setRefreshing(false); | ||||||
|                         if (isFollowersList) followersModels.addAll(followModels); |                     if (isFollowersList) followersModels.addAll(result.getItems()); | ||||||
|                         else followingModels.addAll(followModels); |                     else followingModels.addAll(result.getItems()); | ||||||
|                     refreshAdapter(followModels, null, null, null); |                     refreshAdapter(followModels, null, null, null); | ||||||
|                     } |                     layoutManager.scrollToPosition(oldSize); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onFailure(final Throwable t) { |             public void onFailure(final Throwable t) { | ||||||
|                 binding.swipeRefreshLayout.setRefreshing(false); |                 binding.swipeRefreshLayout.setRefreshing(false); | ||||||
|  |                 Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||||
|                 Log.e(TAG, "Error fetching list (single)", t); |                 Log.e(TAG, "Error fetching list (single)", t); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
|  |         layoutManager = new LinearLayoutManager(getContext()); | ||||||
|  |         lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { | ||||||
|  |             if (!TextUtils.isEmpty(endCursor)) { | ||||||
|                 binding.swipeRefreshLayout.setRefreshing(true); |                 binding.swipeRefreshLayout.setRefreshing(true); | ||||||
|         friendshipService.getList(isFollowersList, profileId, null, cb); |                 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() { |     private void listCompare() { | ||||||
|  |         layoutManager.setStackFromEnd(false); | ||||||
|  |         binding.rvFollow.clearOnScrollListeners(); | ||||||
|         loading = true; |         loading = true; | ||||||
|         setSubtitle(R.string.followers_compare); |         setSubtitle(R.string.followers_compare); | ||||||
|         allFollowing.clear(); |         allFollowing.clear(); | ||||||
|         followersModels.clear(); |         if (moreAvailable) { | ||||||
|         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); |             binding.swipeRefreshLayout.setRefreshing(true); | ||||||
|         if (followersModels.size() == 0) { |             Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show(); | ||||||
|             friendshipService.getList(true, profileId, null, followersFetchCb); |             friendshipService.getList(isFollowersList, | ||||||
|  |                     profileId, | ||||||
|  |                     endCursor, | ||||||
|  |                     isFollowersList ? followersFetchCb : followingFetchCb); | ||||||
|         } |         } | ||||||
|         else if (followingModels.size() == 0) { |         else if (followersModels.size() == 0 || followingModels.size() == 0) { | ||||||
|             friendshipService.getList(false, profileId, null, followingFetchCb); |             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(); |         else showCompare(); | ||||||
|     } |     } | ||||||
| @ -346,12 +381,12 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | |||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         if (loading) Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_LONG).show(); |         if (loading) Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_LONG).show(); | ||||||
|         else if (isCompare) { |         else if (isCompare) { | ||||||
|             listFollows(); |  | ||||||
|             isCompare = !isCompare; |             isCompare = !isCompare; | ||||||
|  |             listFollows(); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             listCompare(); |  | ||||||
|             isCompare = !isCompare; |             isCompare = !isCompare; | ||||||
|  |             listCompare(); | ||||||
|         } |         } | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| @ -371,8 +406,7 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh | |||||||
|             if (allFollowing != null && allFollowing.size() > 0) |             if (allFollowing != null && allFollowing.size() > 0) | ||||||
|                 groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing)); |                 groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing)); | ||||||
|         } else { |         } else { | ||||||
|             final ExpandableGroup group = new ExpandableGroup(type, followModels); |             groups.add(new ExpandableGroup(type, followModels)); | ||||||
|             groups.add(group); |  | ||||||
|         } |         } | ||||||
|         adapter = new FollowAdapter(clickListener, groups); |         adapter = new FollowAdapter(clickListener, groups); | ||||||
|         adapter.toggleGroup(0); |         adapter.toggleGroup(0); | ||||||
|  | |||||||
| @ -54,6 +54,7 @@ import awais.instagrabber.db.entities.Favorite; | |||||||
| import awais.instagrabber.db.repositories.FavoriteRepository; | import awais.instagrabber.db.repositories.FavoriteRepository; | ||||||
| import awais.instagrabber.db.repositories.RepositoryCallback; | import awais.instagrabber.db.repositories.RepositoryCallback; | ||||||
| import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; | import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; | ||||||
|  | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.HashtagModel; | import awais.instagrabber.models.HashtagModel; | ||||||
| import awais.instagrabber.models.PostsLayoutPreferences; | import awais.instagrabber.models.PostsLayoutPreferences; | ||||||
| @ -363,7 +364,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|     private void fetchHashtagModel() { |     private void fetchHashtagModel() { | ||||||
|         stopCurrentExecutor(); |         stopCurrentExecutor(); | ||||||
|         binding.swipeRefreshLayout.setRefreshing(true); |         binding.swipeRefreshLayout.setRefreshing(true); | ||||||
|         currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> { |         currentlyExecuting = new HashtagFetcher(hashtag.substring(1), new FetchListener<HashtagModel>() { | ||||||
|  |             @Override | ||||||
|  |             public void onResult(final HashtagModel result) { | ||||||
|                 hashtagModel = result; |                 hashtagModel = result; | ||||||
|                 binding.swipeRefreshLayout.setRefreshing(false); |                 binding.swipeRefreshLayout.setRefreshing(false); | ||||||
|                 final Context context = getContext(); |                 final Context context = getContext(); | ||||||
| @ -376,6 +379,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                 setHashtagDetails(); |                 setHashtagDetails(); | ||||||
|                 setupPosts(); |                 setupPosts(); | ||||||
|                 fetchStories(); |                 fetchStories(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             @Override | ||||||
|  |             public void onFailure(Throwable t) { | ||||||
|  |                 Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||||
|  |             } | ||||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -410,9 +419,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                                 hashtagDetailsBinding.btnFollowTag.setClickable(true); |                                 hashtagDetailsBinding.btnFollowTag.setClickable(true); | ||||||
|                                 if (!result) { |                                 if (!result) { | ||||||
|                                     Log.e(TAG, "onSuccess: result is false"); |                                     Log.e(TAG, "onSuccess: result is false"); | ||||||
|  |                                     Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG) | ||||||
|  |                                             .show(); | ||||||
|                                     return; |                                     return; | ||||||
|                                 } |                                 } | ||||||
|                                 onRefresh(); |                                 hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow); | ||||||
|  |                                 hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24); | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                             @Override |                             @Override | ||||||
| @ -435,9 +447,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                             hashtagDetailsBinding.btnFollowTag.setClickable(true); |                             hashtagDetailsBinding.btnFollowTag.setClickable(true); | ||||||
|                             if (!result) { |                             if (!result) { | ||||||
|                                 Log.e(TAG, "onSuccess: result is false"); |                                 Log.e(TAG, "onSuccess: result is false"); | ||||||
|  |                                 Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG) | ||||||
|  |                                         .show(); | ||||||
|                                 return; |                                 return; | ||||||
|                             } |                             } | ||||||
|                             onRefresh(); |                             hashtagDetailsBinding.btnFollowTag.setText(R.string.follow); | ||||||
|  |                             hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_24); | ||||||
|                         } |                         } | ||||||
| 
 | 
 | ||||||
|                         @Override |                         @Override | ||||||
| @ -462,10 +477,25 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|         favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() { |         favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final Favorite result) { |             public void onSuccess(final Favorite result) { | ||||||
|  |                 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.setChipIconResource(R.drawable.ic_star_check_24); | ||||||
|                         hashtagDetailsBinding.favChip.setText(R.string.favorite_short); |                         hashtagDetailsBinding.favChip.setText(R.string.favorite_short); | ||||||
|                     } |                     } | ||||||
| 
 | 
 | ||||||
|  |                     @Override | ||||||
|  |                     public void onDataNotAvailable() {} | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onDataNotAvailable() { |             public void onDataNotAvailable() { | ||||||
|                 hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); |                 hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); | ||||||
| @ -492,18 +522,18 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                     @Override |                     @Override | ||||||
|                     public void onDataNotAvailable() { |                     public void onDataNotAvailable() { | ||||||
|                         favoriteRepository.insertOrUpdateFavorite(new Favorite( |                         favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||||
|                                 -1, |                                 0, | ||||||
|                                 hashtag.substring(1), |                                 hashtag.substring(1), | ||||||
|                                 FavoriteType.HASHTAG, |                                 FavoriteType.HASHTAG, | ||||||
|                                 hashtagModel.getName(), |                                 hashtagModel.getName(), | ||||||
|                                 null, |                                 hashtagModel.getSdProfilePic(), | ||||||
|                                 new Date() |                                 new Date() | ||||||
|                         ), new RepositoryCallback<Void>() { |                         ), new RepositoryCallback<Void>() { | ||||||
|                             @Override |                             @Override | ||||||
|                             public void onSuccess(final Void result) { |                             public void onSuccess(final Void result) { | ||||||
|                                 hashtagDetailsBinding.favChip.setText(R.string.favorite_short); |                                 hashtagDetailsBinding.favChip.setText(R.string.favorite_short); | ||||||
|                                 hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); |                                 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 |                             @Override | ||||||
| @ -513,7 +543,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                 })); |                 })); | ||||||
|         hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); |         hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); | ||||||
|         final String postCount = String.valueOf(hashtagModel.getPostCount()); |         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 RelativeSizeSpan(1.2f), 0, postCount.length(), 0); | ||||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); |         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); | ||||||
|         hashtagDetailsBinding.mainTagPostCount.setText(span); |         hashtagDetailsBinding.mainTagPostCount.setText(span); | ||||||
| @ -522,7 +554,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|             if (!hasStories) return; |             if (!hasStories) return; | ||||||
|             // show stories |             // show stories | ||||||
|             final NavDirections action = HashTagFragmentDirections |             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); |             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,7 +400,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | |||||||
|         // binding.swipeRefreshLayout.setRefreshing(true); |         // binding.swipeRefreshLayout.setRefreshing(true); | ||||||
|         locationDetailsBinding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); |         locationDetailsBinding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); | ||||||
|         final String postCount = String.valueOf(locationModel.getPostCount()); |         final String postCount = String.valueOf(locationModel.getPostCount()); | ||||||
|         final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, |         final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, | ||||||
|  |                 locationModel.getPostCount() > 2000000000L ? 2000000000 : locationModel.getPostCount().intValue(), | ||||||
|                 postCount)); |                 postCount)); | ||||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); |         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); | ||||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); |         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); | ||||||
| @ -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.setChipIconResource(R.drawable.ic_star_check_24); |                 locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||||
|                 locationDetailsBinding.favChip.setText(R.string.favorite_short); |                 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 |             @Override | ||||||
| @ -484,7 +499,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | |||||||
|                 @Override |                 @Override | ||||||
|                 public void onDataNotAvailable() { |                 public void onDataNotAvailable() { | ||||||
|                     favoriteRepository.insertOrUpdateFavorite(new Favorite( |                     favoriteRepository.insertOrUpdateFavorite(new Favorite( | ||||||
|                             -1, |                             0, | ||||||
|                             locationId, |                             locationId, | ||||||
|                             FavoriteType.LOCATION, |                             FavoriteType.LOCATION, | ||||||
|                             locationModel.getName(), |                             locationModel.getName(), | ||||||
| @ -495,7 +510,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | |||||||
|                         public void onSuccess(final Void result) { |                         public void onSuccess(final Void result) { | ||||||
|                             locationDetailsBinding.favChip.setText(R.string.favorite_short); |                             locationDetailsBinding.favChip.setText(R.string.favorite_short); | ||||||
|                             locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); |                             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 |                         @Override | ||||||
| @ -508,7 +523,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR | |||||||
|             if (hasStories) { |             if (hasStories) { | ||||||
|                 // show stories |                 // show stories | ||||||
|                 final NavDirections action = LocationFragmentDirections |                 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); |                 NavHostFragment.findNavController(this).navigate(action); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -18,20 +18,28 @@ import androidx.annotation.Nullable; | |||||||
| import androidx.appcompat.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
| import androidx.core.app.NotificationManagerCompat; | import androidx.core.app.NotificationManagerCompat; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
|  | import androidx.fragment.app.FragmentManager; | ||||||
|  | import androidx.fragment.app.FragmentTransaction; | ||||||
| import androidx.lifecycle.ViewModelProvider; | import androidx.lifecycle.ViewModelProvider; | ||||||
| import androidx.navigation.NavDirections; | import androidx.navigation.NavDirections; | ||||||
| import androidx.navigation.fragment.NavHostFragment; | import androidx.navigation.fragment.NavHostFragment; | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; | ||||||
| 
 | 
 | ||||||
|  | import java.util.List; | ||||||
|  | 
 | ||||||
| import awais.instagrabber.R; | import awais.instagrabber.R; | ||||||
| import awais.instagrabber.adapters.NotificationsAdapter; | import awais.instagrabber.adapters.NotificationsAdapter; | ||||||
| import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; | import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; | ||||||
| import awais.instagrabber.asyncs.NotificationsFetcher; | import awais.instagrabber.asyncs.NotificationsFetcher; | ||||||
| import awais.instagrabber.asyncs.PostFetcher; | import awais.instagrabber.asyncs.PostFetcher; | ||||||
| import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; | import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; | ||||||
|  | import awais.instagrabber.dialogs.ProfilePicDialogFragment; | ||||||
| import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; | import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; | ||||||
|  | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.interfaces.MentionClickListener; | import awais.instagrabber.interfaces.MentionClickListener; | ||||||
|  | import awais.instagrabber.models.FeedModel; | ||||||
|  | import awais.instagrabber.models.NotificationModel; | ||||||
| import awais.instagrabber.models.enums.NotificationType; | import awais.instagrabber.models.enums.NotificationType; | ||||||
| import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; | import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; | ||||||
| import awais.instagrabber.utils.Constants; | import awais.instagrabber.utils.Constants; | ||||||
| @ -40,6 +48,7 @@ import awais.instagrabber.utils.TextUtils; | |||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
| import awais.instagrabber.viewmodels.NotificationViewModel; | import awais.instagrabber.viewmodels.NotificationViewModel; | ||||||
| import awais.instagrabber.webservices.FriendshipService; | import awais.instagrabber.webservices.FriendshipService; | ||||||
|  | import awais.instagrabber.webservices.MediaService; | ||||||
| import awais.instagrabber.webservices.NewsService; | import awais.instagrabber.webservices.NewsService; | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| 
 | 
 | ||||||
| @ -53,48 +62,87 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | |||||||
|     private boolean shouldRefresh = true; |     private boolean shouldRefresh = true; | ||||||
|     private NotificationViewModel notificationViewModel; |     private NotificationViewModel notificationViewModel; | ||||||
|     private FriendshipService friendshipService; |     private FriendshipService friendshipService; | ||||||
|     private String userId; |     private MediaService mediaService; | ||||||
|     private String csrfToken; |  | ||||||
|     private NewsService newsService; |     private NewsService newsService; | ||||||
|  |     private String userId, csrfToken, type; | ||||||
|  |     private Context context; | ||||||
| 
 | 
 | ||||||
|     private final OnNotificationClickListener clickListener = model -> { |     private final OnNotificationClickListener clickListener = new OnNotificationClickListener() { | ||||||
|  |         @Override | ||||||
|  |         public void onProfileClick(final String username) { | ||||||
|  |             openProfile(username); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @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.show(getChildFragmentManager(), "post_view"); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     @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; |             if (model == null) return; | ||||||
|             final String username = model.getUsername(); |             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()))); |                 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); |                 title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); | ||||||
| 
 | 
 | ||||||
|                 String[] commentDialogList; |                 String[] commentDialogList; | ||||||
|         if (model.getShortCode() != null) { |                 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[]{ |                     commentDialogList = new String[]{ | ||||||
|                             getString(R.string.open_profile), |                             getString(R.string.open_profile), | ||||||
|                             getString(R.string.view_post) |                             getString(R.string.view_post) | ||||||
|                     }; |                     }; | ||||||
|         } else if (model.getType() == NotificationType.REQUEST) { |                 } | ||||||
|  |                 else if (model.getType() == NotificationType.REQUEST) { | ||||||
|                     commentDialogList = new String[]{ |                     commentDialogList = new String[]{ | ||||||
|                             getString(R.string.open_profile), |                             getString(R.string.open_profile), | ||||||
|                             getString(R.string.request_approve), |                             getString(R.string.request_approve), | ||||||
|                             getString(R.string.request_reject) |                             getString(R.string.request_reject) | ||||||
|                     }; |                     }; | ||||||
|         } else { |  | ||||||
|             commentDialogList = new String[]{getString(R.string.open_profile)}; |  | ||||||
|                 } |                 } | ||||||
|  |                 else commentDialogList = null; // shouldn't happen | ||||||
|                 final Context context = getContext(); |                 final Context context = getContext(); | ||||||
|                 if (context == null) return; |                 if (context == null) return; | ||||||
|                 final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { |                 final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> { | ||||||
|                     switch (which) { |                     switch (which) { | ||||||
|                         case 0: |                         case 0: | ||||||
|                     openProfile(model.getUsername()); |                             openProfile(username); | ||||||
|                             break; |                             break; | ||||||
|                         case 1: |                         case 1: | ||||||
|                             if (model.getType() == NotificationType.REQUEST) { |                             if (model.getType() == NotificationType.REQUEST) { | ||||||
|                                 friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { |                                 friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||||
|                                     @Override |                                     @Override | ||||||
|                                     public void onSuccess(final FriendshipRepoChangeRootResponse result) { |                                     public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||||
|                                 // Log.d(TAG, "onSuccess: " + result); |  | ||||||
|                                 if (result.getStatus().equals("ok")) { |  | ||||||
|                                         onRefresh(); |                                         onRefresh(); | ||||||
|                                     return; |  | ||||||
|                                 } |  | ||||||
|                                         Log.e(TAG, "approve: status was not ok!"); |                                         Log.e(TAG, "approve: status was not ok!"); | ||||||
|                                     } |                                     } | ||||||
| 
 | 
 | ||||||
| @ -105,29 +153,38 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | |||||||
|                                 }); |                                 }); | ||||||
|                                 return; |                                 return; | ||||||
|                             } |                             } | ||||||
|  |                             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) |                             final AlertDialog alertDialog = new AlertDialog.Builder(context) | ||||||
|                                     .setCancelable(false) |                                     .setCancelable(false) | ||||||
|                                     .setView(R.layout.dialog_opening_post) |                                     .setView(R.layout.dialog_opening_post) | ||||||
|                                     .create(); |                                     .create(); | ||||||
|                             alertDialog.show(); |                             alertDialog.show(); | ||||||
|                     new PostFetcher(model.getShortCode(), feedModel -> { |                             mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() { | ||||||
|  |                                 @Override | ||||||
|  |                                 public void onSuccess(final FeedModel feedModel) { | ||||||
|                                     final PostViewV2Fragment fragment = PostViewV2Fragment |                                     final PostViewV2Fragment fragment = PostViewV2Fragment | ||||||
|                                             .builder(feedModel) |                                             .builder(feedModel) | ||||||
|                                             .build(); |                                             .build(); | ||||||
|                                     fragment.setOnShowListener(dialog1 -> alertDialog.dismiss()); |                                     fragment.setOnShowListener(dialog1 -> alertDialog.dismiss()); | ||||||
|                                     fragment.show(getChildFragmentManager(), "post_view"); |                                     fragment.show(getChildFragmentManager(), "post_view"); | ||||||
|                     }).execute(); |                                 } | ||||||
|  | 
 | ||||||
|  |                                 @Override | ||||||
|  |                                 public void onFailure(final Throwable t) { | ||||||
|  |                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|  |                                 } | ||||||
|  |                             }); | ||||||
|                             break; |                             break; | ||||||
|                         case 2: |                         case 2: | ||||||
|                             friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { |                             friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { | ||||||
|                                 @Override |                                 @Override | ||||||
|                                 public void onSuccess(final FriendshipRepoChangeRootResponse result) { |                                 public void onSuccess(final FriendshipRepoChangeRootResponse result) { | ||||||
|                             // Log.d(TAG, "onSuccess: " + result); |  | ||||||
|                             if (result.getStatus().equals("ok")) { |  | ||||||
|                                     onRefresh(); |                                     onRefresh(); | ||||||
|                                 return; |  | ||||||
|                             } |  | ||||||
|                             Log.e(TAG, "ignore: status was not ok!"); |  | ||||||
|                                 } |                                 } | ||||||
| 
 | 
 | ||||||
|                                 @Override |                                 @Override | ||||||
| @ -143,6 +200,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | |||||||
|                         .setItems(commentDialogList, profileDialogListener) |                         .setItems(commentDialogList, profileDialogListener) | ||||||
|                         .setNegativeButton(R.string.cancel, null) |                         .setNegativeButton(R.string.cancel, null) | ||||||
|                         .show(); |                         .show(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|     }; |     }; | ||||||
|     private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> { |     private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> { | ||||||
|         if (getContext() == null) return; |         if (getContext() == null) return; | ||||||
| @ -158,7 +217,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | |||||||
|     @Override |     @Override | ||||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { |     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         final Context context = getContext(); |         context = getContext(); | ||||||
|         if (context == null) return; |         if (context == null) return; | ||||||
|         NotificationManagerCompat.from(context.getApplicationContext()).cancel(Constants.ACTIVITY_NOTIFICATION_ID); |         NotificationManagerCompat.from(context.getApplicationContext()).cancel(Constants.ACTIVITY_NOTIFICATION_ID); | ||||||
|         final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); |         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(); |             Toast.makeText(context, R.string.activity_notloggedin, Toast.LENGTH_SHORT).show(); | ||||||
|         } |         } | ||||||
|         friendshipService = FriendshipService.getInstance(); |         friendshipService = FriendshipService.getInstance(); | ||||||
|         newsService = NewsService.getInstance(); |         mediaService = MediaService.getInstance(); | ||||||
|         userId = CookieUtils.getUserIdFromCookie(cookie); |         userId = CookieUtils.getUserIdFromCookie(cookie); | ||||||
|         csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); |         csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); | ||||||
|     } |     } | ||||||
| @ -191,6 +250,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void init() { |     private void init() { | ||||||
|  |         final NotificationsViewerFragmentArgs fragmentArgs = NotificationsViewerFragmentArgs.fromBundle(getArguments()); | ||||||
|  |         type = fragmentArgs.getType(); | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); |         CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); | ||||||
|         binding.swipeRefreshLayout.setOnRefreshListener(this); |         binding.swipeRefreshLayout.setOnRefreshListener(this); | ||||||
| @ -205,23 +266,39 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe | |||||||
|     @Override |     @Override | ||||||
|     public void onRefresh() { |     public void onRefresh() { | ||||||
|         binding.swipeRefreshLayout.setRefreshing(true); |         binding.swipeRefreshLayout.setRefreshing(true); | ||||||
|         new NotificationsFetcher(notificationModels -> { |         switch (type) { | ||||||
|  |             case "notif": | ||||||
|  |                 new NotificationsFetcher(true, new FetchListener<List<NotificationModel>>() { | ||||||
|  |                     @Override | ||||||
|  |                     public void onResult(final List<NotificationModel> notificationModels) { | ||||||
|                         binding.swipeRefreshLayout.setRefreshing(false); |                         binding.swipeRefreshLayout.setRefreshing(false); | ||||||
|                         notificationViewModel.getList().postValue(notificationModels); |                         notificationViewModel.getList().postValue(notificationModels); | ||||||
|             final String timestamp = String.valueOf(System.currentTimeMillis() / 1000); |                     } | ||||||
|             newsService.markChecked(timestamp, csrfToken, new ServiceCallback<Boolean>() { | 
 | ||||||
|                     @Override |                     @Override | ||||||
|                 public void onSuccess(@NonNull final Boolean result) { |                     public void onFailure(Throwable t) { | ||||||
|                     // Log.d(TAG, "onResponse: body: " + result); |                         binding.swipeRefreshLayout.setRefreshing(false); | ||||||
|                     if (!result) Log.e(TAG, "onSuccess: Error marking activity checked, response is 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 |                     @Override | ||||||
|                     public void onFailure(final Throwable t) { |                     public void onFailure(final Throwable t) { | ||||||
|                     Log.e(TAG, "onFailure: Error marking activity checked", t); |                         binding.swipeRefreshLayout.setRefreshing(false); | ||||||
|  |                         Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||||
|                     } |                     } | ||||||
|                 }); |                 }); | ||||||
|         }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |                 break; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void openProfile(final String username) { |     private void openProfile(final String username) { | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ import android.view.ViewGroup; | |||||||
| import android.view.ViewTreeObserver; | import android.view.ViewTreeObserver; | ||||||
| import android.view.Window; | import android.view.Window; | ||||||
| import android.view.WindowManager; | import android.view.WindowManager; | ||||||
|  | import android.widget.EditText; | ||||||
| import android.widget.FrameLayout; | import android.widget.FrameLayout; | ||||||
| import android.widget.ScrollView; | import android.widget.ScrollView; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| @ -33,6 +34,7 @@ import android.widget.ViewSwitcher; | |||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  | import androidx.appcompat.app.AlertDialog; | ||||||
| import androidx.constraintlayout.widget.ConstraintLayout; | import androidx.constraintlayout.widget.ConstraintLayout; | ||||||
| import androidx.core.content.PermissionChecker; | import androidx.core.content.PermissionChecker; | ||||||
| import androidx.core.view.ViewCompat; | import androidx.core.view.ViewCompat; | ||||||
| @ -70,6 +72,7 @@ import awais.instagrabber.customviews.VideoPlayerCallbackAdapter; | |||||||
| import awais.instagrabber.customviews.VideoPlayerViewHelper; | import awais.instagrabber.customviews.VideoPlayerViewHelper; | ||||||
| import awais.instagrabber.customviews.drawee.AnimatedZoomableController; | import awais.instagrabber.customviews.drawee.AnimatedZoomableController; | ||||||
| import awais.instagrabber.databinding.DialogPostViewBinding; | import awais.instagrabber.databinding.DialogPostViewBinding; | ||||||
|  | import awais.instagrabber.fragments.main.ProfileFragment; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.PostChild; | import awais.instagrabber.models.PostChild; | ||||||
| import awais.instagrabber.models.ProfileModel; | import awais.instagrabber.models.ProfileModel; | ||||||
| @ -114,6 +117,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | |||||||
|     private DialogInterface.OnShowListener onShowListener; |     private DialogInterface.OnShowListener onShowListener; | ||||||
|     private boolean isLoggedIn; |     private boolean isLoggedIn; | ||||||
|     private boolean hasBeenToggled = false; |     private boolean hasBeenToggled = false; | ||||||
|  |     private CharSequence postCaption = null; | ||||||
| 
 | 
 | ||||||
|     private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() { |     private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() { | ||||||
| 
 | 
 | ||||||
| @ -306,6 +310,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | |||||||
|     @Override |     @Override | ||||||
|     public void onDestroyView() { |     public void onDestroyView() { | ||||||
|         super.onDestroyView(); |         super.onDestroyView(); | ||||||
|  |         if (feedModel == null) return; | ||||||
|         switch (feedModel.getItemType()) { |         switch (feedModel.getItemType()) { | ||||||
|             case MEDIA_TYPE_VIDEO: |             case MEDIA_TYPE_VIDEO: | ||||||
|                 if (videoPlayerViewHelper != null) { |                 if (videoPlayerViewHelper != null) { | ||||||
| @ -414,8 +419,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | |||||||
| 
 | 
 | ||||||
|     private void init() { |     private void init() { | ||||||
|         if (feedModel == null) return; |         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)) { |         if (!wasPaused && (sharedProfilePicElement != null || sharedMainPostElement != null)) { | ||||||
|             binding.getRoot().getBackground().mutate().setAlpha(0); |             binding.getRoot().getBackground().mutate().setAlpha(0); | ||||||
|         } |         } | ||||||
| @ -534,7 +538,16 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         binding.like.setOnLongClickListener(v -> { |         binding.like.setOnLongClickListener(v -> { | ||||||
|  |             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)); |                 Utils.displayToastAboveView(context, v, getString(R.string.like_without_count)); | ||||||
|  |             } | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @ -701,13 +714,52 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setupCaption() { |     private void setupCaption() { | ||||||
|         final CharSequence postCaption = feedModel.getPostCaption(); |         postCaption = feedModel.getPostCaption(); | ||||||
|         binding.date.setText(Utils.datetimeParser.format(new Date(feedModel.getTimestamp() * 1000L))); |         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.caption.setVisibility(View.GONE); | ||||||
|  |             binding.translateTitle.setVisibility(View.GONE); | ||||||
|             binding.captionToggle.setVisibility(View.GONE); |             binding.captionToggle.setVisibility(View.GONE); | ||||||
|             return; |             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 -> { |         binding.caption.addOnHashtagListener(autoLinkItem -> { | ||||||
|             final NavController navController = NavHostFragment.findNavController(this); |             final NavController navController = NavHostFragment.findNavController(this); | ||||||
|             final Bundle bundle = new Bundle(); |             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.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 == null) return; | ||||||
|             if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) return; |             if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) return; | ||||||
|             bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); |             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 -> { |         binding.captionToggle.setOnClickListener(v -> { | ||||||
|             if (bottomSheetBehavior == null) return; |             if (bottomSheetBehavior == null) return; | ||||||
|             switch (bottomSheetBehavior.getState()) { |             switch (bottomSheetBehavior.getState()) { | ||||||
| @ -894,6 +968,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment { | |||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onItemClicked(final int position) { |             public void onItemClicked(final int position) { | ||||||
|  |                 toggleDetails(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|  | |||||||
| @ -40,24 +40,26 @@ import awais.instagrabber.models.FeedModel; | |||||||
| import awais.instagrabber.models.PostsLayoutPreferences; | import awais.instagrabber.models.PostsLayoutPreferences; | ||||||
| import awais.instagrabber.models.enums.PostItemType; | import awais.instagrabber.models.enums.PostItemType; | ||||||
| import awais.instagrabber.utils.Constants; | import awais.instagrabber.utils.Constants; | ||||||
|  | import awais.instagrabber.utils.CookieUtils; | ||||||
| import awais.instagrabber.utils.DownloadUtils; | import awais.instagrabber.utils.DownloadUtils; | ||||||
|  | import awais.instagrabber.utils.TextUtils; | ||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
| 
 | 
 | ||||||
| import static androidx.core.content.PermissionChecker.checkSelfPermission; | import static androidx.core.content.PermissionChecker.checkSelfPermission; | ||||||
| import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | ||||||
|  | import static awais.instagrabber.utils.Utils.settingsHelper; | ||||||
| 
 | 
 | ||||||
| public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | 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 = 8020; | ||||||
|     private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; |     private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030; | ||||||
| 
 | 
 | ||||||
|     private FragmentSavedBinding binding; |     private FragmentSavedBinding binding; | ||||||
|     private String username; |     private String username, cookie, profileId; | ||||||
|     private ActionMode actionMode; |     private ActionMode actionMode; | ||||||
|     private SwipeRefreshLayout root; |     private SwipeRefreshLayout root; | ||||||
|     private AppCompatActivity fragmentActivity; |     private AppCompatActivity fragmentActivity; | ||||||
|     private boolean shouldRefresh = true; |     private boolean isLoggedIn, shouldRefresh = true; | ||||||
|     private PostItemType type; |     private PostItemType type; | ||||||
|     private String profileId; |  | ||||||
|     private Set<FeedModel> selectedFeedModels; |     private Set<FeedModel> selectedFeedModels; | ||||||
|     private FeedModel downloadFeedModel; |     private FeedModel downloadFeedModel; | ||||||
|     private int downloadChildPosition = -1; |     private int downloadChildPosition = -1; | ||||||
| @ -225,6 +227,8 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { |     public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { | ||||||
|  |         cookie = settingsHelper.getString(Constants.COOKIE); | ||||||
|  |         isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; | ||||||
|         if (root != null) { |         if (root != null) { | ||||||
|             shouldRefresh = false; |             shouldRefresh = false; | ||||||
|             return root; |             return root; | ||||||
| @ -281,7 +285,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL | |||||||
|     private void setupPosts() { |     private void setupPosts() { | ||||||
|         binding.posts.setViewModelStoreOwner(this) |         binding.posts.setViewModelStoreOwner(this) | ||||||
|                      .setLifeCycleOwner(this) |                      .setLifeCycleOwner(this) | ||||||
|                      .setPostFetchService(new SavedPostFetchService(profileId, type)) |                      .setPostFetchService(new SavedPostFetchService(profileId, type, isLoggedIn)) | ||||||
|                      .setLayoutPreferences(layoutPreferences) |                      .setLayoutPreferences(layoutPreferences) | ||||||
|                      .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) |                      .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) | ||||||
|                      .setFeedItemCallback(feedItemCallback) |                      .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.Log; | ||||||
| import android.util.Pair; | import android.util.Pair; | ||||||
| import android.view.GestureDetector; | import android.view.GestureDetector; | ||||||
|  | import android.view.Gravity; | ||||||
| import android.view.LayoutInflater; | import android.view.LayoutInflater; | ||||||
| import android.view.Menu; | import android.view.Menu; | ||||||
| import android.view.MenuInflater; | import android.view.MenuInflater; | ||||||
| @ -20,6 +21,9 @@ import android.view.View; | |||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
| import android.widget.ArrayAdapter; | import android.widget.ArrayAdapter; | ||||||
| import android.widget.EditText; | import android.widget.EditText; | ||||||
|  | import android.widget.LinearLayout; | ||||||
|  | import android.widget.SeekBar; | ||||||
|  | import android.widget.TextView; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
| 
 | 
 | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
| @ -54,6 +58,9 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource; | |||||||
| import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; | ||||||
| 
 | 
 | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
|  | import java.io.UnsupportedEncodingException; | ||||||
|  | import java.text.NumberFormat; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.Date; | import java.util.Date; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @ -62,10 +69,7 @@ import awais.instagrabber.BuildConfig; | |||||||
| import awais.instagrabber.R; | import awais.instagrabber.R; | ||||||
| import awais.instagrabber.adapters.StoriesAdapter; | import awais.instagrabber.adapters.StoriesAdapter; | ||||||
| import awais.instagrabber.asyncs.PostFetcher; | import awais.instagrabber.asyncs.PostFetcher; | ||||||
| import awais.instagrabber.asyncs.QuizAction; |  | ||||||
| import awais.instagrabber.asyncs.RespondAction; |  | ||||||
| import awais.instagrabber.asyncs.SeenAction; | import awais.instagrabber.asyncs.SeenAction; | ||||||
| import awais.instagrabber.asyncs.VoteAction; |  | ||||||
| import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; | import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; | ||||||
| import awais.instagrabber.customviews.helpers.SwipeGestureListener; | import awais.instagrabber.customviews.helpers.SwipeGestureListener; | ||||||
| import awais.instagrabber.databinding.FragmentStoryViewerBinding; | 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.PollModel; | ||||||
| import awais.instagrabber.models.stickers.QuestionModel; | import awais.instagrabber.models.stickers.QuestionModel; | ||||||
| import awais.instagrabber.models.stickers.QuizModel; | 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.Constants; | ||||||
|  | import awais.instagrabber.utils.CookieUtils; | ||||||
| import awais.instagrabber.utils.DownloadUtils; | import awais.instagrabber.utils.DownloadUtils; | ||||||
| import awais.instagrabber.utils.TextUtils; | import awais.instagrabber.utils.TextUtils; | ||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
|  | import awais.instagrabber.viewmodels.ArchivesViewModel; | ||||||
| import awais.instagrabber.viewmodels.FeedStoriesViewModel; | import awais.instagrabber.viewmodels.FeedStoriesViewModel; | ||||||
| import awais.instagrabber.viewmodels.HighlightsViewModel; | import awais.instagrabber.viewmodels.HighlightsViewModel; | ||||||
| import awais.instagrabber.viewmodels.StoriesViewModel; | import awais.instagrabber.viewmodels.StoriesViewModel; | ||||||
|  | import awais.instagrabber.webservices.DirectMessagesService; | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| import awais.instagrabber.webservices.StoriesService; | import awais.instagrabber.webservices.StoriesService; | ||||||
| import awaisomereport.LogCollector; | 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_THRESHOLD; | ||||||
| import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD; | import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD; | ||||||
| @ -100,8 +115,7 @@ public class StoryViewerFragment extends Fragment { | |||||||
| 
 | 
 | ||||||
|     private AppCompatActivity fragmentActivity; |     private AppCompatActivity fragmentActivity; | ||||||
|     private View root; |     private View root; | ||||||
|     private @NonNull |     private FragmentStoryViewerBinding binding; | ||||||
|     FragmentStoryViewerBinding binding; |  | ||||||
|     private String currentStoryUsername; |     private String currentStoryUsername; | ||||||
|     private StoriesAdapter storiesAdapter; |     private StoriesAdapter storiesAdapter; | ||||||
|     private SwipeEvent swipeEvent; |     private SwipeEvent swipeEvent; | ||||||
| @ -115,26 +129,32 @@ public class StoryViewerFragment extends Fragment { | |||||||
|     private QuestionModel question; |     private QuestionModel question; | ||||||
|     private String[] mentions; |     private String[] mentions; | ||||||
|     private QuizModel quiz; |     private QuizModel quiz; | ||||||
|  |     private SliderModel slider; | ||||||
|     private MenuItem menuDownload; |     private MenuItem menuDownload; | ||||||
|     private MenuItem menuDm; |     private MenuItem menuDm; | ||||||
|     private SimpleExoPlayer player; |     private SimpleExoPlayer player; | ||||||
|     private boolean isHashtag, isLoc; |     private boolean isHashtag, isLoc; | ||||||
|     private String highlight; |     private String highlight; | ||||||
|     private boolean fetching = false; |     private boolean fetching = false, sticking = false, shouldRefresh = true; | ||||||
|     private int currentFeedStoryIndex; |     private int currentFeedStoryIndex; | ||||||
|  |     private double sliderValue; | ||||||
|     private StoriesViewModel storiesViewModel; |     private StoriesViewModel storiesViewModel; | ||||||
|     private boolean shouldRefresh = true; |  | ||||||
|     private StoryViewerFragmentArgs fragmentArgs; |     private StoryViewerFragmentArgs fragmentArgs; | ||||||
|     private ViewModel viewModel; |     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 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 |     @Override | ||||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { |     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|         fragmentActivity = (AppCompatActivity) requireActivity(); |         fragmentActivity = (AppCompatActivity) requireActivity(); | ||||||
|         storiesService = StoriesService.getInstance(); |         storiesService = StoriesService.getInstance(); | ||||||
|  |         directMessagesService = DirectMessagesService.getInstance(csrfToken, userIdFromCookie, deviceId); | ||||||
|         setHasOptionsMenu(true); |         setHasOptionsMenu(true); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -188,24 +208,33 @@ public class StoryViewerFragment extends Fragment { | |||||||
|             new AlertDialog.Builder(context) |             new AlertDialog.Builder(context) | ||||||
|                     .setTitle(R.string.reply_story) |                     .setTitle(R.string.reply_story) | ||||||
|                     .setView(input) |                     .setView(input) | ||||||
|                     .setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> { |                     .setPositiveButton(R.string.confirm, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> { | ||||||
|                         // try { |                         try { | ||||||
|                         // final StoryReplyBroadcastOptions options = new StoryReplyBroadcastOptions( |                             final Call<DirectThreadBroadcastResponse> request = directMessagesService | ||||||
|                         //         threadId, |                                     .broadcastStoryReply(BroadcastOptions.ThreadIdOrUserIds.of(threadId), | ||||||
|                         //         input.getText().toString(), |                                                          input.getText().toString(), | ||||||
|                         //         currentStory.getStoryMediaId(), |                                                          currentStory.getStoryMediaId(), | ||||||
|                         //         currentStory.getUserId() |                                                          currentStory.getUserId()); | ||||||
|                         // ); |                             request.enqueue(new Callback<DirectThreadBroadcastResponse>() { | ||||||
|                         // final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadId); |                                 @Override | ||||||
|                         // broadcast.setOnTaskCompleteListener(result -> Toast.makeText( |                                 public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call, | ||||||
|                         //         context, |                                                        @NonNull final Response<DirectThreadBroadcastResponse> response) { | ||||||
|                         //         result != null ? R.string.answered_story : R.string.downloader_unknown_error, |                                     if (!response.isSuccessful()) { | ||||||
|                         //         Toast.LENGTH_SHORT |                                         Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|                         // ).show()); |                                         return; | ||||||
|                         // broadcast.execute(options); |                                     } | ||||||
|                         // } catch (UnsupportedEncodingException e) { |                                     Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||||
|                         //     Log.e(TAG, "Error", e); |                                 } | ||||||
|                         // } | 
 | ||||||
|  |                                 @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()) |                     }).execute()) | ||||||
|                     .setNegativeButton(R.string.cancel, null) |                     .setNegativeButton(R.string.cancel, null) | ||||||
|                     .show(); |                     .show(); | ||||||
| @ -244,9 +273,13 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex(); |         currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex(); | ||||||
|         highlight = fragmentArgs.getHighlight(); |         highlight = fragmentArgs.getHighlight(); | ||||||
|         isHighlight = !TextUtils.isEmpty(highlight); |         isHighlight = !TextUtils.isEmpty(highlight); | ||||||
|  |         isArchive = fragmentArgs.getIsArchive(); | ||||||
|  |         isNotification = fragmentArgs.getIsNotification(); | ||||||
|         if (currentFeedStoryIndex >= 0) { |         if (currentFeedStoryIndex >= 0) { | ||||||
|             viewModel = isHighlight |             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); |                         : new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); | ||||||
|         } |         } | ||||||
|         // feedStoryModels = feedStoriesViewModel.getList().getValue(); |         // feedStoryModels = feedStoriesViewModel.getList().getValue(); | ||||||
| @ -275,7 +308,10 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         final boolean hasFeedStories; |         final boolean hasFeedStories; | ||||||
|         List<?> models = null; |         List<?> models = null; | ||||||
|         if (currentFeedStoryIndex >= 0) { |         if (currentFeedStoryIndex >= 0) { | ||||||
|             if (isHighlight) { |             if (isArchive) { | ||||||
|  |                 final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel; | ||||||
|  |                 models = archivesViewModel.getList().getValue(); | ||||||
|  |             } else if (isHighlight) { | ||||||
|                 final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; |                 final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; | ||||||
|                 models = highlightsViewModel.getList().getValue(); |                 models = highlightsViewModel.getList().getValue(); | ||||||
|                 // final HighlightModel model = models.get(currentFeedStoryIndex); |                 // final HighlightModel model = models.get(currentFeedStoryIndex); | ||||||
| @ -296,15 +332,16 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         swipeEvent = isRightSwipe -> { |         swipeEvent = isRightSwipe -> { | ||||||
|             final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); |             final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); | ||||||
|             final int storiesLen = storyModels == null ? 0 : storyModels.size(); |             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; |             if (storiesLen <= 0) return; | ||||||
|             final boolean isLeftSwipe = !isRightSwipe; |             final boolean isLeftSwipe = !isRightSwipe; | ||||||
|             final boolean endOfCurrentStories = slidePos + 1 >= storiesLen; |             final boolean endOfCurrentStories = slidePos + 1 >= storiesLen; | ||||||
|             final boolean swipingBeyondCurrentStories = (endOfCurrentStories && isLeftSwipe) || (slidePos == 0 && isRightSwipe); |             final boolean swipingBeyondCurrentStories = (endOfCurrentStories && isLeftSwipe) || (slidePos == 0 && isRightSwipe); | ||||||
|             if (swipingBeyondCurrentStories && hasFeedStories) { |             if (swipingBeyondCurrentStories && hasFeedStories) { | ||||||
|                 final int index = currentFeedStoryIndex; |                 final int index = currentFeedStoryIndex; | ||||||
|                 if (settingsHelper.getBoolean(MARK_AS_SEEN)) { |  | ||||||
|                     new SeenAction(cookie, currentStory).execute(); |  | ||||||
|                 } |  | ||||||
|                 if ((isRightSwipe && index == 0) || (isLeftSwipe && index == finalModels.size() - 1)) { |                 if ((isRightSwipe && index == 0) || (isLeftSwipe && index == finalModels.size() - 1)) { | ||||||
|                     Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show(); |                     Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show(); | ||||||
|                     return; |                     return; | ||||||
| @ -312,7 +349,7 @@ public class StoryViewerFragment extends Fragment { | |||||||
|                 final Object feedStoryModel = isRightSwipe |                 final Object feedStoryModel = isRightSwipe | ||||||
|                                               ? finalModels.get(index - 1) |                                               ? finalModels.get(index - 1) | ||||||
|                                               : finalModels.size() == index + 1 ? null : 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; |                 return; | ||||||
|             } |             } | ||||||
|             if (isRightSwipe) { |             if (isRightSwipe) { | ||||||
| @ -351,8 +388,12 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         if (hasFeedStories) { |         if (hasFeedStories) { | ||||||
|             binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE); |             binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE); | ||||||
|             binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? 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.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), | ||||||
|             binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), context, false, |                                                                         finalModels.get(currentFeedStoryIndex), | ||||||
|  |                                                                         context, true, false)); | ||||||
|  |             binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), | ||||||
|  |                                                                        finalModels.get(currentFeedStoryIndex), | ||||||
|  |                                                                        context, false, | ||||||
|                                                                        currentFeedStoryIndex == finalModels.size() - 2)); |                                                                        currentFeedStoryIndex == finalModels.size() - 2)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -365,6 +406,14 @@ public class StoryViewerFragment extends Fragment { | |||||||
|                 startActivity(intent); |                 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 -> { |         binding.viewStoryPost.setOnClickListener(v -> { | ||||||
|             final Object tag = v.getTag(); |             final Object tag = v.getTag(); | ||||||
|             if (!(tag instanceof CharSequence)) return; |             if (!(tag instanceof CharSequence)) return; | ||||||
| @ -406,15 +455,30 @@ public class StoryViewerFragment extends Fragment { | |||||||
|                                     poll.getLeftChoice() + " (" + poll.getLeftCount() + ")", |                                     poll.getLeftChoice() + " (" + poll.getLeftCount() + ")", | ||||||
|                                     poll.getRightChoice() + " (" + poll.getRightCount() + ")" |                                     poll.getRightChoice() + " (" + poll.getRightCount() + ")" | ||||||
|                             }), (d, w) -> { |                             }), (d, w) -> { | ||||||
|                                 if (!TextUtils.isEmpty(cookie)) |                                 if (!TextUtils.isEmpty(cookie)) { | ||||||
|                                     new VoteAction(currentStory, poll, cookie, choice -> { |                                     sticking = true; | ||||||
|                                         if (choice > -1) { |                                     storiesService.respondToPoll( | ||||||
|                                             poll.setMyChoice(choice); |                                             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(); |                                                     Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show(); | ||||||
|                                             return; |  | ||||||
|                                                 } |                                                 } | ||||||
|  | 
 | ||||||
|  |                                                 @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(); |                                                     Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|                                     }).execute(w); |                                                 } | ||||||
|  |                                             }); | ||||||
|  |                                 } | ||||||
|                             }) |                             }) | ||||||
|                             .setPositiveButton(R.string.cancel, null) |                             .setPositiveButton(R.string.cancel, null) | ||||||
|                             .show(); |                             .show(); | ||||||
| @ -426,21 +490,36 @@ public class StoryViewerFragment extends Fragment { | |||||||
|                 new AlertDialog.Builder(context) |                 new AlertDialog.Builder(context) | ||||||
|                         .setTitle(question.getQuestion()) |                         .setTitle(question.getQuestion()) | ||||||
|                         .setView(input) |                         .setView(input) | ||||||
|                         .setPositiveButton(R.string.ok, (d, w) -> new RespondAction(currentStory, question, cookie, result -> { |                         .setPositiveButton(R.string.confirm, (d, w) -> { | ||||||
|                             if (result) { |                             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(); |                                             Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||||
|                             } else |                                         } | ||||||
|  | 
 | ||||||
|  |                                         @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(); |                                             Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|                         }).execute(input.getText().toString())) |                                         } | ||||||
|  |                                     }); | ||||||
|  |                         }) | ||||||
|                         .setNegativeButton(R.string.cancel, null) |                         .setNegativeButton(R.string.cancel, null) | ||||||
|                         .show(); |                         .show(); | ||||||
|             } else if (tag instanceof String[]) { |             } else if (tag instanceof String[]) { | ||||||
|                 mentions = (String[]) tag; |                 mentions = (String[]) tag; | ||||||
|                 new AlertDialog.Builder(context) |                 new AlertDialog.Builder(context) | ||||||
|                         .setTitle(R.string.story_mentions) |                         .setTitle(R.string.story_mentions) | ||||||
|                         .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> { |                         .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> openProfile(mentions[w])) | ||||||
|                             openProfile(mentions[w]); |  | ||||||
|                         }) |  | ||||||
|                         .setPositiveButton(R.string.cancel, null) |                         .setPositiveButton(R.string.cancel, null) | ||||||
|                         .show(); |                         .show(); | ||||||
|             } else if (tag instanceof QuizModel) { |             } else if (tag instanceof QuizModel) { | ||||||
| @ -451,27 +530,122 @@ public class StoryViewerFragment extends Fragment { | |||||||
|                 new AlertDialog.Builder(context) |                 new AlertDialog.Builder(context) | ||||||
|                         .setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion()) |                         .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) -> { |                         .setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices), (d, w) -> { | ||||||
|                             if (quiz.getMyChoice() == -1 && !TextUtils.isEmpty(cookie)) |                             if (quiz.getMyChoice() == -1 && !TextUtils.isEmpty(cookie)) { | ||||||
|                                 new QuizAction(currentStory, quiz, cookie, choice -> { |                                 sticking = true; | ||||||
|                                     if (choice > -1) { |                                 storiesService.respondToQuiz( | ||||||
|                                         quiz.setMyChoice(choice); |                                         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(); |                                                 Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show(); | ||||||
|                                         return; |  | ||||||
|                                             } |                                             } | ||||||
|  | 
 | ||||||
|  |                                             @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(); |                                                 Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); | ||||||
|                                 }).execute(w); |                                             } | ||||||
|  |                                         }); | ||||||
|  |                             } | ||||||
|                         }) |                         }) | ||||||
|                         .setPositiveButton(R.string.cancel, null) |                         .setPositiveButton(R.string.cancel, null) | ||||||
|                         .show(); |                         .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.poll.setOnClickListener(storyActionListener); | ||||||
|         binding.answer.setOnClickListener(storyActionListener); |         binding.answer.setOnClickListener(storyActionListener); | ||||||
|         binding.mention.setOnClickListener(storyActionListener); |         binding.mention.setOnClickListener(storyActionListener); | ||||||
|         binding.quiz.setOnClickListener(storyActionListener); |         binding.quiz.setOnClickListener(storyActionListener); | ||||||
|  |         binding.slider.setOnClickListener(storyActionListener); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void resetView() { |     private void resetView() { | ||||||
|  |         final Context context = getContext(); | ||||||
|         slidePos = 0; |         slidePos = 0; | ||||||
|         lastSlidePos = 0; |         lastSlidePos = 0; | ||||||
|         if (menuDownload != null) menuDownload.setVisible(false); |         if (menuDownload != null) menuDownload.setVisible(false); | ||||||
| @ -480,10 +654,23 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         releasePlayer(); |         releasePlayer(); | ||||||
|         String currentStoryMediaId = null; |         String currentStoryMediaId = null; | ||||||
|         if (currentFeedStoryIndex >= 0) { |         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 HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel; | ||||||
|                 final List<HighlightModel> models = highlightsViewModel.getList().getValue(); |                 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); |                 final HighlightModel model = models.get(currentFeedStoryIndex); | ||||||
|                 currentStoryMediaId = model.getId(); |                 currentStoryMediaId = model.getId(); | ||||||
|                 currentStoryUsername = model.getTitle(); |                 currentStoryUsername = model.getTitle(); | ||||||
| @ -502,7 +689,12 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         isHashtag = fragmentArgs.getIsHashtag(); |         isHashtag = fragmentArgs.getIsHashtag(); | ||||||
|         isLoc = fragmentArgs.getIsLoc(); |         isLoc = fragmentArgs.getIsLoc(); | ||||||
|         final boolean hasUsername = !TextUtils.isEmpty(currentStoryUsername); |         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("@", ""); |             currentStoryUsername = currentStoryUsername.replace("@", ""); | ||||||
|             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); |             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||||
|             if (actionBar != null) { |             if (actionBar != null) { | ||||||
| @ -511,6 +703,31 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         } |         } | ||||||
|         storiesViewModel.getList().setValue(Collections.emptyList()); |         storiesViewModel.getList().setValue(Collections.emptyList()); | ||||||
|         if (currentStoryMediaId == null) return; |         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>>() { |         final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final List<StoryModel> storyModels) { |             public void onSuccess(final List<StoryModel> storyModels) { | ||||||
| @ -521,7 +738,9 @@ public class StoryViewerFragment extends Fragment { | |||||||
|                     binding.storiesList.setVisibility(View.GONE); |                     binding.storiesList.setVisibility(View.GONE); | ||||||
|                     return; |                     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); |                 storiesViewModel.getList().setValue(storyModels); | ||||||
|                 currentStory = storyModels.get(0); |                 currentStory = storyModels.get(0); | ||||||
|                 refreshStory(); |                 refreshStory(); | ||||||
| @ -529,6 +748,8 @@ public class StoryViewerFragment extends Fragment { | |||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onFailure(final Throwable t) { |             public void onFailure(final Throwable t) { | ||||||
|  |                 final Context context = getContext(); | ||||||
|  |                 Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); | ||||||
|                 Log.e(TAG, "Error", t); |                 Log.e(TAG, "Error", t); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| @ -543,7 +764,7 @@ public class StoryViewerFragment extends Fragment { | |||||||
|     private void refreshStory() { |     private void refreshStory() { | ||||||
|         if (binding.storiesList.getVisibility() == View.VISIBLE) { |         if (binding.storiesList.getVisibility() == View.VISIBLE) { | ||||||
|             final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); |             final List<StoryModel> storyModels = storiesViewModel.getList().getValue(); | ||||||
|             if (storyModels != null) { |             if (storyModels != null && storyModels.size() > 0) { | ||||||
|                 StoryModel item = storyModels.get(lastSlidePos); |                 StoryModel item = storyModels.get(lastSlidePos); | ||||||
|                 if (item != null) { |                 if (item != null) { | ||||||
|                     item.setCurrentSlide(false); |                     item.setCurrentSlide(false); | ||||||
| @ -587,6 +808,17 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE); |         binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE); | ||||||
|         binding.quiz.setTag(quiz); |         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(); |         releasePlayer(); | ||||||
|         if (isHashtag || isLoc) { |         if (isHashtag || isLoc) { | ||||||
|             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); |             final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||||
| @ -670,8 +902,8 @@ public class StoryViewerFragment extends Fragment { | |||||||
|             @Override |             @Override | ||||||
|             public void onLoadCompleted(final int windowIndex, |             public void onLoadCompleted(final int windowIndex, | ||||||
|                                         @Nullable final MediaSource.MediaPeriodId mediaPeriodId, |                                         @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||||
|                                         final LoadEventInfo loadEventInfo, |                                         @NonNull final LoadEventInfo loadEventInfo, | ||||||
|                                         final MediaLoadData mediaLoadData) { |                                         @NonNull final MediaLoadData mediaLoadData) { | ||||||
|                 if (menuDownload != null) menuDownload.setVisible(true); |                 if (menuDownload != null) menuDownload.setVisible(true); | ||||||
|                 if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) |                 if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) | ||||||
|                     menuDm.setVisible(true); |                     menuDm.setVisible(true); | ||||||
| @ -681,8 +913,8 @@ public class StoryViewerFragment extends Fragment { | |||||||
|             @Override |             @Override | ||||||
|             public void onLoadStarted(final int windowIndex, |             public void onLoadStarted(final int windowIndex, | ||||||
|                                       @Nullable final MediaSource.MediaPeriodId mediaPeriodId, |                                       @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||||
|                                       final LoadEventInfo loadEventInfo, |                                       @NonNull final LoadEventInfo loadEventInfo, | ||||||
|                                       final MediaLoadData mediaLoadData) { |                                       @NonNull final MediaLoadData mediaLoadData) { | ||||||
|                 if (menuDownload != null) menuDownload.setVisible(true); |                 if (menuDownload != null) menuDownload.setVisible(true); | ||||||
|                 if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) |                 if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie)) | ||||||
|                     menuDm.setVisible(true); |                     menuDm.setVisible(true); | ||||||
| @ -692,17 +924,17 @@ public class StoryViewerFragment extends Fragment { | |||||||
|             @Override |             @Override | ||||||
|             public void onLoadCanceled(final int windowIndex, |             public void onLoadCanceled(final int windowIndex, | ||||||
|                                        @Nullable final MediaSource.MediaPeriodId mediaPeriodId, |                                        @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||||
|                                        final LoadEventInfo loadEventInfo, |                                        @NonNull final LoadEventInfo loadEventInfo, | ||||||
|                                        final MediaLoadData mediaLoadData) { |                                        @NonNull final MediaLoadData mediaLoadData) { | ||||||
|                 binding.progressView.setVisibility(View.GONE); |                 binding.progressView.setVisibility(View.GONE); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onLoadError(final int windowIndex, |             public void onLoadError(final int windowIndex, | ||||||
|                                     @Nullable final MediaSource.MediaPeriodId mediaPeriodId, |                                     @Nullable final MediaSource.MediaPeriodId mediaPeriodId, | ||||||
|                                     final LoadEventInfo loadEventInfo, |                                     @NonNull final LoadEventInfo loadEventInfo, | ||||||
|                                     final MediaLoadData mediaLoadData, |                                     @NonNull final MediaLoadData mediaLoadData, | ||||||
|                                     final IOException error, |                                     @NonNull final IOException error, | ||||||
|                                     final boolean wasCanceled) { |                                     final boolean wasCanceled) { | ||||||
|                 if (menuDownload != null) menuDownload.setVisible(false); |                 if (menuDownload != null) menuDownload.setVisible(false); | ||||||
|                 if (menuDm != null) menuDm.setVisible(false); |                 if (menuDm != null) menuDm.setVisible(false); | ||||||
| @ -745,12 +977,25 @@ public class StoryViewerFragment extends Fragment { | |||||||
|         player = null; |         player = null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void paginateStories(Object feedStory, Context context, boolean backward, boolean last) { |     private void paginateStories(Object newFeedStory, Object oldFeedStory, Context context, boolean backward, boolean last) { | ||||||
|         if (feedStory != null) { |         if (newFeedStory != null) { | ||||||
|             if (fetching) { |             if (fetching) { | ||||||
|                 Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show(); |                 Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show(); | ||||||
|                 return; |                 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; |             fetching = true; | ||||||
|             binding.btnBackward.setVisibility(currentFeedStoryIndex == 1 && backward ? View.INVISIBLE : View.VISIBLE); |             binding.btnBackward.setVisibility(currentFeedStoryIndex == 1 && backward ? View.INVISIBLE : View.VISIBLE); | ||||||
|             binding.btnForward.setVisibility(last ? 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.NonNull; | ||||||
| import androidx.annotation.Nullable; | import androidx.annotation.Nullable; | ||||||
|  | import androidx.appcompat.app.ActionBar; | ||||||
| import androidx.appcompat.app.AlertDialog; | import androidx.appcompat.app.AlertDialog; | ||||||
|  | import androidx.appcompat.app.AppCompatActivity; | ||||||
| import androidx.appcompat.widget.AppCompatButton; | import androidx.appcompat.widget.AppCompatButton; | ||||||
| import androidx.appcompat.widget.AppCompatImageView; | import androidx.appcompat.widget.AppCompatImageView; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| @ -42,6 +44,7 @@ import awais.instagrabber.utils.Utils; | |||||||
| public class DirectMessageSettingsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | public class DirectMessageSettingsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||||
|     private static final String TAG = "DirectMsgsSettingsFrag"; |     private static final String TAG = "DirectMsgsSettingsFrag"; | ||||||
| 
 | 
 | ||||||
|  |     private AppCompatActivity fragmentActivity; | ||||||
|     private RecyclerView userList; |     private RecyclerView userList; | ||||||
|     private RecyclerView leftUserList; |     private RecyclerView leftUserList; | ||||||
|     private EditText titleText; |     private EditText titleText; | ||||||
| @ -82,6 +85,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr | |||||||
|     @Override |     @Override | ||||||
|     public void onCreate(@Nullable final Bundle savedInstanceState) { |     public void onCreate(@Nullable final Bundle savedInstanceState) { | ||||||
|         super.onCreate(savedInstanceState); |         super.onCreate(savedInstanceState); | ||||||
|  |         fragmentActivity = (AppCompatActivity) requireActivity(); | ||||||
|         basicClickListener = v -> { |         basicClickListener = v -> { | ||||||
|             final Object tag = v.getTag(); |             final Object tag = v.getTag(); | ||||||
|             if (tag instanceof ProfileModel) { |             if (tag instanceof ProfileModel) { | ||||||
| @ -147,6 +151,11 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr | |||||||
|         threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle(); |         threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle(); | ||||||
|         binding.swipeRefreshLayout.setEnabled(false); |         binding.swipeRefreshLayout.setEnabled(false); | ||||||
| 
 | 
 | ||||||
|  |         final ActionBar actionBar = fragmentActivity.getSupportActionBar(); | ||||||
|  |         if (actionBar != null) { | ||||||
|  |             actionBar.setTitle(threadTitle); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         userList = binding.userList; |         userList = binding.userList; | ||||||
|         userList.setHasFixedSize(true); |         userList.setHasFixedSize(true); | ||||||
|         userList.setLayoutManager(layoutManager); |         userList.setLayoutManager(layoutManager); | ||||||
| @ -209,7 +218,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr | |||||||
|     class ChangeSettings extends AsyncTask<String, Void, Void> { |     class ChangeSettings extends AsyncTask<String, Void, Void> { | ||||||
|         String action, argument; |         String action, argument; | ||||||
|         boolean ok = false; |         boolean ok = false; | ||||||
|         private String text; |         private final String text; | ||||||
| 
 | 
 | ||||||
|         public ChangeSettings(final String text) { |         public ChangeSettings(final String text) { | ||||||
|             this.text = text; |             this.text = text; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import android.view.MenuInflater; | |||||||
| import android.view.MenuItem; | import android.view.MenuItem; | ||||||
| import android.view.View; | import android.view.View; | ||||||
| import android.view.ViewGroup; | import android.view.ViewGroup; | ||||||
|  | import android.widget.Toast; | ||||||
| 
 | 
 | ||||||
| import androidx.activity.OnBackPressedCallback; | import androidx.activity.OnBackPressedCallback; | ||||||
| import androidx.activity.OnBackPressedDispatcher; | import androidx.activity.OnBackPressedDispatcher; | ||||||
| @ -47,6 +48,7 @@ import awais.instagrabber.models.FeedModel; | |||||||
| import awais.instagrabber.models.FeedStoryModel; | import awais.instagrabber.models.FeedStoryModel; | ||||||
| import awais.instagrabber.models.PostsLayoutPreferences; | import awais.instagrabber.models.PostsLayoutPreferences; | ||||||
| import awais.instagrabber.utils.Constants; | import awais.instagrabber.utils.Constants; | ||||||
|  | import awais.instagrabber.utils.CookieUtils; | ||||||
| import awais.instagrabber.utils.DownloadUtils; | import awais.instagrabber.utils.DownloadUtils; | ||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
| import awais.instagrabber.viewmodels.FeedStoriesViewModel; | import awais.instagrabber.viewmodels.FeedStoriesViewModel; | ||||||
| @ -55,6 +57,7 @@ import awais.instagrabber.webservices.StoriesService; | |||||||
| 
 | 
 | ||||||
| import static androidx.core.content.PermissionChecker.checkSelfPermission; | import static androidx.core.content.PermissionChecker.checkSelfPermission; | ||||||
| import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; | ||||||
|  | import static awais.instagrabber.utils.Utils.settingsHelper; | ||||||
| 
 | 
 | ||||||
| public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { | ||||||
|     private static final String TAG = "FeedFragment"; |     private static final String TAG = "FeedFragment"; | ||||||
| @ -74,6 +77,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | |||||||
|     private int downloadChildPosition = -1; |     private int downloadChildPosition = -1; | ||||||
|     private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_POSTS_LAYOUT); |     private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_POSTS_LAYOUT); | ||||||
|     private RecyclerView storiesRecyclerView; |     private RecyclerView storiesRecyclerView; | ||||||
|  |     private MenuItem storyListMenu; | ||||||
| 
 | 
 | ||||||
|     private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { |     private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() { | ||||||
|         @Override |         @Override | ||||||
| @ -272,26 +276,23 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | |||||||
|     @Override |     @Override | ||||||
|     public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { |     public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { | ||||||
|         inflater.inflate(R.menu.feed_menu, menu); |         inflater.inflate(R.menu.feed_menu, menu); | ||||||
|  |         storyListMenu = menu.findItem(R.id.storyList); | ||||||
|  |         storyListMenu.setVisible(!storiesFetching); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public boolean onOptionsItemSelected(@NonNull final MenuItem item) { |     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(); |             showPostsLayoutPreferences(); | ||||||
|             return true; |             return true; | ||||||
|         } |         } | ||||||
|         return super.onOptionsItemSelected(item); |         return super.onOptionsItemSelected(item); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     @Override |  | ||||||
|     public void onResume() { |  | ||||||
|         super.onResume(); |  | ||||||
|         updateSwipeRefreshState(); |  | ||||||
|         // if (videoAwareRecyclerScroller != null && shouldAutoPlay) { |  | ||||||
|         //     videoAwareRecyclerScroller.startPlaying(); |  | ||||||
|         // } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     @Override |     @Override | ||||||
|     public void onRefresh() { |     public void onRefresh() { | ||||||
|         binding.feedRecyclerView.refresh(); |         binding.feedRecyclerView.refresh(); | ||||||
| @ -309,9 +310,13 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | |||||||
|     @Override |     @Override | ||||||
|     public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { |     public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { | ||||||
|         super.onRequestPermissionsResult(requestCode, permissions, grantResults); |         super.onRequestPermissionsResult(requestCode, permissions, grantResults); | ||||||
|         final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; |         final boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED; | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         if (context == null) return; |         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 (requestCode == STORAGE_PERM_REQUEST_CODE && granted) { | ||||||
|             if (downloadFeedModel == null) return; |             if (downloadFeedModel == null) return; | ||||||
|             DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition); |             DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition); | ||||||
| @ -346,11 +351,22 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setupFeedStories() { |     private void setupFeedStories() { | ||||||
|  |         if (storyListMenu != null) storyListMenu.setVisible(false); | ||||||
|         feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); |         feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class); | ||||||
|         final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter((model, position) -> { |         final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter( | ||||||
|             final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null); |             new FeedStoriesAdapter.OnFeedStoryClickListener() { | ||||||
|             NavHostFragment.findNavController(this).navigate(action); |                 @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(); |         final Context context = getContext(); | ||||||
|         if (context == null) return; |         if (context == null) return; | ||||||
|         storiesRecyclerView = new RecyclerView(context); |         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.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)); | ||||||
|         storiesRecyclerView.setAdapter(feedStoriesAdapter); |         storiesRecyclerView.setAdapter(feedStoriesAdapter); | ||||||
|         fragmentActivity.setCollapsingView(storiesRecyclerView); |         fragmentActivity.setCollapsingView(storiesRecyclerView); | ||||||
|         feedStoriesViewModel.getList().observe(fragmentActivity, feedStoriesAdapter::submitList); |         feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), feedStoriesAdapter::submitList); | ||||||
|         fetchStories(); |         fetchStories(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void fetchStories() { |     private void fetchStories() { | ||||||
|  |         final String cookie = settingsHelper.getString(Constants.COOKIE); | ||||||
|         storiesFetching = true; |         storiesFetching = true; | ||||||
|         updateSwipeRefreshState(); |         updateSwipeRefreshState(); | ||||||
|         storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { |         storiesService.getFeedStories(CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<List<FeedStoryModel>>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final List<FeedStoryModel> result) { |             public void onSuccess(final List<FeedStoryModel> result) { | ||||||
|                 feedStoriesViewModel.getList().postValue(result); |                 feedStoriesViewModel.getList().postValue(result); | ||||||
|                 storiesFetching = false; |                 storiesFetching = false; | ||||||
|  |                 if (storyListMenu != null) storyListMenu.setVisible(true); | ||||||
|                 updateSwipeRefreshState(); |                 updateSwipeRefreshState(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -54,7 +54,6 @@ import awais.instagrabber.R; | |||||||
| import awais.instagrabber.activities.MainActivity; | import awais.instagrabber.activities.MainActivity; | ||||||
| import awais.instagrabber.adapters.FeedAdapterV2; | import awais.instagrabber.adapters.FeedAdapterV2; | ||||||
| import awais.instagrabber.adapters.HighlightsAdapter; | import awais.instagrabber.adapters.HighlightsAdapter; | ||||||
| import awais.instagrabber.asyncs.HighlightsFetcher; |  | ||||||
| import awais.instagrabber.asyncs.ProfileFetcher; | import awais.instagrabber.asyncs.ProfileFetcher; | ||||||
| import awais.instagrabber.asyncs.ProfilePostFetchService; | import awais.instagrabber.asyncs.ProfilePostFetchService; | ||||||
| import awais.instagrabber.asyncs.UsernameFetcher; | import awais.instagrabber.asyncs.UsernameFetcher; | ||||||
| @ -75,6 +74,7 @@ import awais.instagrabber.dialogs.ProfilePicDialogFragment; | |||||||
| import awais.instagrabber.fragments.PostViewV2Fragment; | import awais.instagrabber.fragments.PostViewV2Fragment; | ||||||
| import awais.instagrabber.interfaces.FetchListener; | import awais.instagrabber.interfaces.FetchListener; | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
|  | import awais.instagrabber.models.HighlightModel; | ||||||
| import awais.instagrabber.models.PostsLayoutPreferences; | import awais.instagrabber.models.PostsLayoutPreferences; | ||||||
| import awais.instagrabber.models.ProfileModel; | import awais.instagrabber.models.ProfileModel; | ||||||
| import awais.instagrabber.models.StoryModel; | import awais.instagrabber.models.StoryModel; | ||||||
| @ -89,6 +89,7 @@ import awais.instagrabber.utils.TextUtils; | |||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
| import awais.instagrabber.viewmodels.HighlightsViewModel; | import awais.instagrabber.viewmodels.HighlightsViewModel; | ||||||
| import awais.instagrabber.webservices.FriendshipService; | import awais.instagrabber.webservices.FriendshipService; | ||||||
|  | import awais.instagrabber.webservices.MediaService; | ||||||
| import awais.instagrabber.webservices.ServiceCallback; | import awais.instagrabber.webservices.ServiceCallback; | ||||||
| import awais.instagrabber.webservices.StoriesService; | import awais.instagrabber.webservices.StoriesService; | ||||||
| 
 | 
 | ||||||
| @ -113,6 +114,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|     private Handler usernameSettingHandler; |     private Handler usernameSettingHandler; | ||||||
|     private FriendshipService friendshipService; |     private FriendshipService friendshipService; | ||||||
|     private StoriesService storiesService; |     private StoriesService storiesService; | ||||||
|  |     private MediaService mediaService; | ||||||
|     private boolean shouldRefresh = true; |     private boolean shouldRefresh = true; | ||||||
|     private boolean hasStories = false; |     private boolean hasStories = false; | ||||||
|     private HighlightsAdapter highlightsAdapter; |     private HighlightsAdapter highlightsAdapter; | ||||||
| @ -298,6 +300,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|         fragmentActivity = (MainActivity) requireActivity(); |         fragmentActivity = (MainActivity) requireActivity(); | ||||||
|         friendshipService = FriendshipService.getInstance(); |         friendshipService = FriendshipService.getInstance(); | ||||||
|         storiesService = StoriesService.getInstance(); |         storiesService = StoriesService.getInstance(); | ||||||
|  |         mediaService = MediaService.getInstance(); | ||||||
|         accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); |         accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); | ||||||
|         favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); |         favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); | ||||||
|         setHasOptionsMenu(true); |         setHasOptionsMenu(true); | ||||||
| @ -370,10 +373,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|         } |         } | ||||||
|         if (item.getItemId() == R.id.restrict) { |         if (item.getItemId() == R.id.restrict) { | ||||||
|             if (!isLoggedIn) return false; |             if (!isLoggedIn) return false; | ||||||
|             final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; |             final String action = profileModel.isRestricted() ? "Unrestrict" : "Restrict"; | ||||||
|             friendshipService.toggleRestrict( |             friendshipService.toggleRestrict( | ||||||
|                     profileModel.getId(), |                     profileModel.getId(), | ||||||
|                     !profileModel.getRestricted(), |                     !profileModel.isRestricted(), | ||||||
|                     CookieUtils.getCsrfTokenFromCookie(cookie), |                     CookieUtils.getCsrfTokenFromCookie(cookie), | ||||||
|                     new ServiceCallback<FriendshipRepoRestrictRootResponse>() { |                     new ServiceCallback<FriendshipRepoRestrictRootResponse>() { | ||||||
|                         @Override |                         @Override | ||||||
| @ -392,7 +395,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|         if (item.getItemId() == R.id.block) { |         if (item.getItemId() == R.id.block) { | ||||||
|             final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); |             final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); | ||||||
|             if (!isLoggedIn) return false; |             if (!isLoggedIn) return false; | ||||||
|             if (profileModel.getBlocked()) { |             if (profileModel.isBlocked()) { | ||||||
|                 friendshipService.unblock( |                 friendshipService.unblock( | ||||||
|                         userIdFromCookie, |                         userIdFromCookie, | ||||||
|                         profileModel.getId(), |                         profileModel.getId(), | ||||||
| @ -434,6 +437,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
| 
 | 
 | ||||||
|     @Override |     @Override | ||||||
|     public void onRefresh() { |     public void onRefresh() { | ||||||
|  |         profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE); | ||||||
|         fetchProfileDetails(); |         fetchProfileDetails(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -571,44 +575,99 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|             fetchStoryAndHighlights(profileId); |             fetchStoryAndHighlights(profileId); | ||||||
|         } |         } | ||||||
|         setupButtons(profileId, myId); |         setupButtons(profileId, myId); | ||||||
|         if (!profileId.equals(myId)) { |         profileDetailsBinding.favChip.setVisibility(View.VISIBLE); | ||||||
|             profileDetailsBinding.favCb.setVisibility(View.VISIBLE); |         final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); | ||||||
|             favoriteRepository.getFavorite(username.substring(1), FavoriteType.USER, new RepositoryCallback<Favorite>() { |         favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onSuccess(final Favorite result) { |             public void onSuccess(final Favorite result) { | ||||||
|                     profileDetailsBinding.favCb.setChecked(true); |                 profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24); | ||||||
|                     profileDetailsBinding.favCb.setButtonDrawable(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() {} | ||||||
|  |                 }); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             @Override |             @Override | ||||||
|             public void onDataNotAvailable() { |             public void onDataNotAvailable() { | ||||||
|                     profileDetailsBinding.favCb.setChecked(false); |                 profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); | ||||||
|                     profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); |                 profileDetailsBinding.favChip.setText(R.string.add_to_favorites); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|         } else { |         profileDetailsBinding.favChip.setOnClickListener( | ||||||
|             profileDetailsBinding.favCb.setVisibility(View.GONE); |                 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)); | ||||||
|                             } |                             } | ||||||
|         profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getHdProfilePic()); |  | ||||||
| 
 | 
 | ||||||
|         final long followersCount = profileModel.getFollowersCount(); |                             @Override | ||||||
|         final long followingCount = profileModel.getFollowingCount(); |                             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 String postCount = String.valueOf(profileModel.getPostCount()); |         final String postCount = String.valueOf(profileModel.getPostCount()); | ||||||
| 
 | 
 | ||||||
|         SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, |         SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, | ||||||
|  |                 profileModel.getPostCount() > 2000000000L ? 2000000000 : profileModel.getPostCount().intValue(), | ||||||
|                 postCount)); |                 postCount)); | ||||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); |         span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); | ||||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); |         span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); | ||||||
|         profileDetailsBinding.mainPostCount.setText(span); |         profileDetailsBinding.mainPostCount.setText(span); | ||||||
|  |         profileDetailsBinding.mainPostCount.setVisibility(View.VISIBLE); | ||||||
| 
 | 
 | ||||||
|         final String followersCountStr = String.valueOf(followersCount); |         final String followersCountStr = String.valueOf(followersCount); | ||||||
|         final int followersCountStrLen = followersCountStr.length(); |         final int followersCountStrLen = followersCountStr.length(); | ||||||
|         span = new SpannableStringBuilder(getString(R.string.main_posts_followers, |         span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_followers, | ||||||
|  |                                                                             followersCount > 2000000000L ? 2000000000 : followersCount.intValue(), | ||||||
|                                                                             followersCountStr)); |                                                                             followersCountStr)); | ||||||
|         span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0); |         span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0); | ||||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0); |         span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0); | ||||||
|         profileDetailsBinding.mainFollowers.setText(span); |         profileDetailsBinding.mainFollowers.setText(span); | ||||||
|  |         profileDetailsBinding.mainFollowers.setVisibility(View.VISIBLE); | ||||||
| 
 | 
 | ||||||
|         final String followingCountStr = String.valueOf(followingCount); |         final String followingCountStr = String.valueOf(followingCount); | ||||||
|         final int followingCountStrLen = followingCountStr.length(); |         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 RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0); | ||||||
|         span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0); |         span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0); | ||||||
|         profileDetailsBinding.mainFollowing.setText(span); |         profileDetailsBinding.mainFollowing.setText(span); | ||||||
|  |         profileDetailsBinding.mainFollowing.setVisibility(View.VISIBLE); | ||||||
| 
 | 
 | ||||||
|         profileDetailsBinding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername() |         profileDetailsBinding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername() | ||||||
|                                                                                              : profileModel.getName()); |                                                                                              : profileModel.getName()); | ||||||
| @ -640,6 +700,51 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                                                                                                                            .trim())); |                                                                                                                            .trim())); | ||||||
|             profileDetailsBinding.mainBiography |             profileDetailsBinding.mainBiography | ||||||
|                     .addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim())); |                     .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 -> { |             profileDetailsBinding.mainBiography.setOnLongClickListener(v -> { | ||||||
|                 Utils.copyText(context, biography); |                 Utils.copyText(context, biography); | ||||||
|                 return true; |                 return true; | ||||||
| @ -687,6 +792,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private void setupButtons(final String profileId, final String myId) { |     private void setupButtons(final String profileId, final String myId) { | ||||||
|  |         profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE); | ||||||
|         if (isLoggedIn) { |         if (isLoggedIn) { | ||||||
|             if (profileId.equals(myId)) { |             if (profileId.equals(myId)) { | ||||||
|                 profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE); |                 profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE); | ||||||
| @ -696,15 +802,29 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                 profileDetailsBinding.btnSaved.setText(R.string.saved); |                 profileDetailsBinding.btnSaved.setText(R.string.saved); | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|             profileDetailsBinding.btnTagged.setVisibility(View.GONE); |  | ||||||
|             profileDetailsBinding.btnSaved.setVisibility(View.GONE); |             profileDetailsBinding.btnSaved.setVisibility(View.GONE); | ||||||
|             profileDetailsBinding.btnLiked.setVisibility(View.GONE); |             profileDetailsBinding.btnLiked.setVisibility(View.GONE); | ||||||
|             profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism? |             profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism? | ||||||
|             profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE); |             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.setText(R.string.unfollow); | ||||||
|                 profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); |                 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.setText(R.string.cancel); | ||||||
|                 profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); |                 profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); | ||||||
|             } else { |             } else { | ||||||
| @ -713,16 +833,15 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|             } |             } | ||||||
|             if (restrictMenuItem != null) { |             if (restrictMenuItem != null) { | ||||||
|                 restrictMenuItem.setVisible(true); |                 restrictMenuItem.setVisible(true); | ||||||
|                 if (profileModel.getRestricted()) { |                 if (profileModel.isRestricted()) { | ||||||
|                     restrictMenuItem.setTitle(R.string.unrestrict); |                     restrictMenuItem.setTitle(R.string.unrestrict); | ||||||
|                 } else { |                 } else { | ||||||
|                     restrictMenuItem.setTitle(R.string.restrict); |                     restrictMenuItem.setTitle(R.string.restrict); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE); |  | ||||||
|             if (blockMenuItem != null) { |             if (blockMenuItem != null) { | ||||||
|                 blockMenuItem.setVisible(true); |                 blockMenuItem.setVisible(true); | ||||||
|                 if (profileModel.getBlocked()) { |                 if (profileModel.isBlocked()) { | ||||||
|                     blockMenuItem.setTitle(R.string.unblock); |                     blockMenuItem.setTitle(R.string.unblock); | ||||||
|                 } else { |                 } else { | ||||||
|                     blockMenuItem.setTitle(R.string.block); |                     blockMenuItem.setTitle(R.string.block); | ||||||
| @ -732,7 +851,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|         } |         } | ||||||
|         if (!profileModel.isReallyPrivate() && restrictMenuItem != null) { |         if (!profileModel.isReallyPrivate() && restrictMenuItem != null) { | ||||||
|             restrictMenuItem.setVisible(true); |             restrictMenuItem.setVisible(true); | ||||||
|             if (profileModel.getRestricted()) { |             if (profileModel.isRestricted()) { | ||||||
|                 restrictMenuItem.setTitle(R.string.unrestrict); |                 restrictMenuItem.setTitle(R.string.unrestrict); | ||||||
|             } else { |             } else { | ||||||
|                 restrictMenuItem.setTitle(R.string.restrict); |                 restrictMenuItem.setTitle(R.string.restrict); | ||||||
| @ -760,20 +879,55 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                                             Log.e(TAG, "Error", t); |                                             Log.e(TAG, "Error", t); | ||||||
|                                         } |                                         } | ||||||
|                                     }); |                                     }); | ||||||
|         new HighlightsFetcher(profileId, |         storiesService.fetchHighlights(profileId, | ||||||
|                               result -> { |                                         new ServiceCallback<List<HighlightModel>>() { | ||||||
|  |                                             @Override | ||||||
|  |                                             public void onSuccess(final List<HighlightModel> result) { | ||||||
|                                                 highlightsFetching = false; |                                                 highlightsFetching = false; | ||||||
|                                                 if (result != null) { |                                                 if (result != null) { | ||||||
|                                                     profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); |                                                     profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE); | ||||||
|                                                     highlightsViewModel.getList().postValue(result); |                                                     highlightsViewModel.getList().postValue(result); | ||||||
|                                   } else profileDetailsBinding.highlightsList.setVisibility(View.GONE); |                                                 } | ||||||
|                               }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); |                                                 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() { |     private void setupCommonListeners() { | ||||||
|  |         final Context context = getContext(); | ||||||
|         final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); |         final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); | ||||||
|         profileDetailsBinding.btnFollow.setOnClickListener(v -> { |         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( |                 friendshipService.unfollow( | ||||||
|                         userIdFromCookie, |                         userIdFromCookie, | ||||||
|                         profileModel.getId(), |                         profileModel.getId(), | ||||||
| @ -854,72 +1008,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|                 if (which == 1) { |                 if (which == 1) { | ||||||
|                     // show stories |                     // show stories | ||||||
|                     final NavDirections action = ProfileFragmentDirections |                     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); |                     NavHostFragment.findNavController(this).navigate(action); | ||||||
|                     return; |                     return; | ||||||
|                 } |                 } | ||||||
|                 showProfilePicDialog(); |                 showProfilePicDialog(); | ||||||
|             }; |             }; | ||||||
|             final Context context = getContext(); |  | ||||||
|             if (context == null) return; |             if (context == null) return; | ||||||
|             new AlertDialog.Builder(context) |             new AlertDialog.Builder(context) | ||||||
|                     .setItems(options, profileDialogListener) |                     .setItems(options, profileDialogListener) | ||||||
|                     .setNegativeButton(R.string.cancel, null) |                     .setNegativeButton(R.string.cancel, null) | ||||||
|                     .show(); |                     .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) { |     private void showSnackbar(final String message) { | ||||||
| @ -951,7 +1051,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|     private void setupPosts() { |     private void setupPosts() { | ||||||
|         binding.postsRecyclerView.setViewModelStoreOwner(this) |         binding.postsRecyclerView.setViewModelStoreOwner(this) | ||||||
|                                  .setLifeCycleOwner(this) |                                  .setLifeCycleOwner(this) | ||||||
|                                  .setPostFetchService(new ProfilePostFetchService(profileModel)) |                                  .setPostFetchService(new ProfilePostFetchService(profileModel, isLoggedIn)) | ||||||
|                                  .setLayoutPreferences(layoutPreferences) |                                  .setLayoutPreferences(layoutPreferences) | ||||||
|                                  .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) |                                  .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState()) | ||||||
|                                  .setFeedItemCallback(feedItemCallback) |                                  .setFeedItemCallback(feedItemCallback) | ||||||
| @ -969,7 +1069,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe | |||||||
|         highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class); |         highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class); | ||||||
|         highlightsAdapter = new HighlightsAdapter((model, position) -> { |         highlightsAdapter = new HighlightsAdapter((model, position) -> { | ||||||
|             final NavDirections action = ProfileFragmentDirections |             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); |             NavHostFragment.findNavController(this).navigate(action); | ||||||
|         }); |         }); | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|  | |||||||
| @ -88,9 +88,10 @@ public class AboutFragment extends BasePreferencesFragment { | |||||||
|         preference.setSummary(R.string.about_feedback_summary); |         preference.setSummary(R.string.about_feedback_summary); | ||||||
|         preference.setIconSpaceReserved(false); |         preference.setIconSpaceReserved(false); | ||||||
|         preference.setOnPreferenceClickListener(p -> { |         preference.setOnPreferenceClickListener(p -> { | ||||||
|             final Intent intent = new Intent(Intent.ACTION_SENDTO); |             final Intent intent = new Intent(Intent.ACTION_SEND); | ||||||
|             intent.setData(Uri.parse(getString(R.string.about_feedback_summary) |             intent.setType("message/rfc822") | ||||||
|              + "?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.")); |                     .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); |             if (intent.resolveActivity(context.getPackageManager()) != null) startActivity(intent); | ||||||
|             return true; |             return true; | ||||||
|         }); |         }); | ||||||
|  | |||||||
| @ -26,10 +26,30 @@ public class BackupPreferencesFragment extends BasePreferencesFragment { | |||||||
|         if (context == null) { |         if (context == null) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         screen.addPreference(getAboutPreference(context)); | ||||||
|  |         screen.addPreference(getWarningPreference(context)); | ||||||
|         screen.addPreference(getCreatePreference(context)); |         screen.addPreference(getCreatePreference(context)); | ||||||
|         screen.addPreference(getRestorePreference(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) { |     private Preference getCreatePreference(@NonNull final Context context) { | ||||||
|         final Preference preference = new Preference(context); |         final Preference preference = new Preference(context); | ||||||
|         preference.setTitle(R.string.create_backup); |         preference.setTitle(R.string.create_backup); | ||||||
|  | |||||||
| @ -134,7 +134,18 @@ public class MorePreferencesFragment extends BasePreferencesFragment { | |||||||
|         screen.addPreference(getDivider(context)); |         screen.addPreference(getDivider(context)); | ||||||
|         if (isLoggedIn) { |         if (isLoggedIn) { | ||||||
|             screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { |             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; |                 return true; | ||||||
|             })); |             })); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -79,6 +79,7 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment { | |||||||
|             screen.addPreference(loggedInUsersPreferenceCategory); |             screen.addPreference(loggedInUsersPreferenceCategory); | ||||||
|             loggedInUsersPreferenceCategory.setIconSpaceReserved(false); |             loggedInUsersPreferenceCategory.setIconSpaceReserved(false); | ||||||
|             loggedInUsersPreferenceCategory.setTitle(R.string.login_settings); |             loggedInUsersPreferenceCategory.setTitle(R.string.login_settings); | ||||||
|  |             loggedInUsersPreferenceCategory.addPreference(getStorySortPreference()); | ||||||
|             loggedInUsersPreferenceCategory.addPreference(getMarkStoriesSeenPreference()); |             loggedInUsersPreferenceCategory.addPreference(getMarkStoriesSeenPreference()); | ||||||
|             loggedInUsersPreferenceCategory.addPreference(getMarkDMSeenPreference()); |             loggedInUsersPreferenceCategory.addPreference(getMarkDMSeenPreference()); | ||||||
|             loggedInUsersPreferenceCategory.addPreference(getEnableActivityNotificationsPreference()); |             loggedInUsersPreferenceCategory.addPreference(getEnableActivityNotificationsPreference()); | ||||||
| @ -204,6 +205,25 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment { | |||||||
|         return preference; |         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() { |     private Preference getMarkStoriesSeenPreference() { | ||||||
|         final Context context = getContext(); |         final Context context = getContext(); | ||||||
|         if (context == null) return null; |         if (context == null) return null; | ||||||
|  | |||||||
| @ -11,16 +11,12 @@ import awais.instagrabber.models.enums.MediaItemType; | |||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
| 
 | 
 | ||||||
| public abstract class BasePostModel implements Serializable, Selectable { | public abstract class BasePostModel implements Serializable, Selectable { | ||||||
|     protected String postId; |     protected String postId, displayUrl, shortCode, captionId; | ||||||
|     protected String displayUrl; |  | ||||||
|     protected String shortCode; |  | ||||||
|     protected CharSequence postCaption; |     protected CharSequence postCaption; | ||||||
|     protected MediaItemType itemType; |     protected MediaItemType itemType; | ||||||
|     protected boolean isSelected; |     protected boolean isSelected, isDownloaded; | ||||||
|     protected boolean isDownloaded; |  | ||||||
|     protected long timestamp; |     protected long timestamp; | ||||||
|     boolean liked; |     boolean liked, saved; | ||||||
|     boolean saved; |  | ||||||
| 
 | 
 | ||||||
|     public boolean getLike() { |     public boolean getLike() { | ||||||
|         return liked; |         return liked; | ||||||
| @ -46,6 +42,10 @@ public abstract class BasePostModel implements Serializable, Selectable { | |||||||
|         return postCaption; |         return postCaption; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public final String getCaptionId() { | ||||||
|  |         return captionId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public final String getShortCode() { |     public final String getShortCode() { | ||||||
|         return shortCode; |         return shortCode; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -11,11 +11,10 @@ public class CommentModel { | |||||||
|     private final ProfileModel profileModel; |     private final ProfileModel profileModel; | ||||||
|     private final String id; |     private final String id; | ||||||
|     private final String text; |     private final String text; | ||||||
|     private final long likes; |     private long likes; | ||||||
|     private final long timestamp; |     private final long timestamp; | ||||||
|     private List<CommentModel> childCommentModels; |     private List<CommentModel> childCommentModels; | ||||||
|     private final boolean liked; |     private boolean liked, hasNextPage; | ||||||
|     private boolean hasNextPage; |  | ||||||
|     private String endCursor; |     private String endCursor; | ||||||
| 
 | 
 | ||||||
|     public CommentModel(final String id, |     public CommentModel(final String id, | ||||||
| @ -53,6 +52,11 @@ public class CommentModel { | |||||||
|         return liked; |         return liked; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setLiked(boolean liked) { | ||||||
|  |         this.likes = liked ? likes + 1 : likes - 1; | ||||||
|  |         this.liked = liked; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public ProfileModel getProfileModel() { |     public ProfileModel getProfileModel() { | ||||||
|         return profileModel; |         return profileModel; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -24,7 +24,7 @@ public final class FeedModel extends PostModel { | |||||||
|         private String displayUrl; |         private String displayUrl; | ||||||
|         private String thumbnailUrl; |         private String thumbnailUrl; | ||||||
|         private String shortCode; |         private String shortCode; | ||||||
|         private String postCaption; |         private String postCaption, captionId; | ||||||
|         private long commentsCount; |         private long commentsCount; | ||||||
|         private long timestamp; |         private long timestamp; | ||||||
|         private boolean liked; |         private boolean liked; | ||||||
| @ -76,6 +76,11 @@ public final class FeedModel extends PostModel { | |||||||
|             return this; |             return this; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         public Builder setCaptionId(final String captionId) { | ||||||
|  |             this.captionId = captionId; | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         public Builder setCommentsCount(final long commentsCount) { |         public Builder setCommentsCount(final long commentsCount) { | ||||||
|             this.commentsCount = commentsCount; |             this.commentsCount = commentsCount; | ||||||
|             return this; |             return this; | ||||||
| @ -127,8 +132,8 @@ public final class FeedModel extends PostModel { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public FeedModel build() { |         public FeedModel build() { | ||||||
|             return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, commentsCount, |             return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, captionId, | ||||||
|                                  timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth); |                     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 thumbnailUrl, | ||||||
|                       final String shortCode, |                       final String shortCode, | ||||||
|                       final String postCaption, |                       final String postCaption, | ||||||
|  |                       final String captionId, | ||||||
|                       final long commentsCount, |                       final long commentsCount, | ||||||
|                       final long timestamp, |                       final long timestamp, | ||||||
|                       final boolean liked, |                       final boolean liked, | ||||||
| @ -150,7 +156,7 @@ public final class FeedModel extends PostModel { | |||||||
|                       final List<PostChild> sliderItems, |                       final List<PostChild> sliderItems, | ||||||
|                       final int imageHeight, |                       final int imageHeight, | ||||||
|                       final int imageWidth) { |                       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.profileModel = profileModel; | ||||||
|         this.commentsCount = commentsCount; |         this.commentsCount = commentsCount; | ||||||
|         this.likesCount = likesCount; |         this.likesCount = likesCount; | ||||||
|  | |||||||
| @ -1,36 +1,66 @@ | |||||||
| package awais.instagrabber.models; | package awais.instagrabber.models; | ||||||
| 
 | 
 | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | 
 | ||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
|  | import java.util.Date; | ||||||
|  | 
 | ||||||
|  | import awais.instagrabber.utils.Utils; | ||||||
| 
 | 
 | ||||||
| public final class FeedStoryModel implements Serializable { | public final class FeedStoryModel implements Serializable { | ||||||
|     private final String storyMediaId; |     private final String storyMediaId; | ||||||
|     private final ProfileModel profileModel; |     private final ProfileModel profileModel; | ||||||
|     private StoryModel[] storyModels; |     private final StoryModel firstStoryModel; | ||||||
|     private boolean fullyRead; |     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.storyMediaId = storyMediaId; | ||||||
|         this.profileModel = profileModel; |         this.profileModel = profileModel; | ||||||
|         this.fullyRead = fullyRead; |         this.fullyRead = fullyRead; | ||||||
|  |         this.timestamp = timestamp; | ||||||
|  |         this.firstStoryModel = firstStoryModel; | ||||||
|  |         this.mediaCount = mediaCount; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getStoryMediaId() { |     public String getStoryMediaId() { | ||||||
|         return storyMediaId; |         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() { |     public ProfileModel getProfileModel() { | ||||||
|         return profileModel; |         return profileModel; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void setStoryModels(final StoryModel[] storyModels) { | //    public void setFirstStoryModel(final StoryModel firstStoryModel) { | ||||||
|         this.storyModels = storyModels; | //        this.firstStoryModel = firstStoryModel; | ||||||
|  | //    } | ||||||
|  | 
 | ||||||
|  |     public StoryModel getFirstStoryModel() { | ||||||
|  |         return firstStoryModel; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public StoryModel[] getStoryModels() { |     public Boolean isFullyRead() { | ||||||
|         return storyModels; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public boolean getFullyRead() { |  | ||||||
|         return fullyRead; |         return fullyRead; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public void setFullyRead(final boolean fullyRead) { | ||||||
|  |         this.fullyRead = fullyRead; | ||||||
|  |     } | ||||||
| } | } | ||||||
| @ -29,7 +29,7 @@ public final class HashtagModel implements Serializable { | |||||||
|         return sdProfilePic; |         return sdProfilePic; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public long getPostCount() { return postCount; } |     public Long getPostCount() { return postCount; } | ||||||
| 
 | 
 | ||||||
|     public boolean getFollowing() { return following; } |     public boolean getFollowing() { return following; } | ||||||
| } | } | ||||||
| @ -1,16 +1,28 @@ | |||||||
| package awais.instagrabber.models; | package awais.instagrabber.models; | ||||||
| 
 | 
 | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | 
 | ||||||
|  | import java.util.Date; | ||||||
|  | 
 | ||||||
|  | import awais.instagrabber.utils.Utils; | ||||||
|  | 
 | ||||||
| public final class HighlightModel { | public final class HighlightModel { | ||||||
|     private final String title; |     private final String title; | ||||||
|     private final String id; |     private final String id; | ||||||
|     private final String thumbnailUrl; |     private final String thumbnailUrl; | ||||||
|  |     private final long timestamp; | ||||||
|  |     private final int mediaCount; | ||||||
| 
 | 
 | ||||||
|     public HighlightModel(final String title, |     public HighlightModel(final String title, | ||||||
|                           final String id, |                           final String id, | ||||||
|                           final String thumbnailUrl) { |                           final String thumbnailUrl, | ||||||
|  |                           final long timestamp, | ||||||
|  |                           final int mediaCount) { | ||||||
|         this.title = title; |         this.title = title; | ||||||
|         this.id = id; |         this.id = id; | ||||||
|         this.thumbnailUrl = thumbnailUrl; |         this.thumbnailUrl = thumbnailUrl; | ||||||
|  |         this.timestamp = timestamp; | ||||||
|  |         this.mediaCount = mediaCount; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getTitle() { |     public String getTitle() { | ||||||
| @ -24,4 +36,17 @@ public final class HighlightModel { | |||||||
|     public String getThumbnailUrl() { |     public String getThumbnailUrl() { | ||||||
|         return thumbnailUrl; |         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; |         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 userId; | ||||||
|     private final String username; |     private final String username; | ||||||
|     private final String profilePicUrl; |     private final String profilePicUrl; | ||||||
|     private final String shortCode; |     private final String postId; | ||||||
|     private final String previewUrl; |     private final String previewUrl; | ||||||
|     private final NotificationType type; |     private final NotificationType type; | ||||||
|     private final CharSequence text; |     private final CharSequence text; | ||||||
| @ -25,7 +25,7 @@ public final class NotificationModel { | |||||||
|                              final String userId, |                              final String userId, | ||||||
|                              final String username, |                              final String username, | ||||||
|                              final String profilePicUrl, |                              final String profilePicUrl, | ||||||
|                              final String shortCode, |                              final String postId, | ||||||
|                              final String previewUrl, |                              final String previewUrl, | ||||||
|                              final NotificationType type) { |                              final NotificationType type) { | ||||||
|         this.id = id; |         this.id = id; | ||||||
| @ -34,7 +34,7 @@ public final class NotificationModel { | |||||||
|         this.userId = userId; |         this.userId = userId; | ||||||
|         this.username = username; |         this.username = username; | ||||||
|         this.profilePicUrl = profilePicUrl; |         this.profilePicUrl = profilePicUrl; | ||||||
|         this.shortCode = shortCode; |         this.postId = postId; | ||||||
|         this.previewUrl = previewUrl; |         this.previewUrl = previewUrl; | ||||||
|         this.type = type; |         this.type = type; | ||||||
|     } |     } | ||||||
| @ -47,6 +47,10 @@ public final class NotificationModel { | |||||||
|         return text; |         return text; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public long getTimestamp() { | ||||||
|  |         return timestamp; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @NonNull |     @NonNull | ||||||
|     public String getDateTime() { |     public String getDateTime() { | ||||||
|         return Utils.datetimeParser.format(new Date(timestamp * 1000L)); |         return Utils.datetimeParser.format(new Date(timestamp * 1000L)); | ||||||
| @ -64,8 +68,8 @@ public final class NotificationModel { | |||||||
|         return profilePicUrl; |         return profilePicUrl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getShortCode() { |     public String getPostId() { | ||||||
|         return shortCode; |         return postId; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getPreviewPic() { |     public String getPreviewPic() { | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ public class PostModel extends BasePostModel { | |||||||
|                      final String thumbnailUrl, |                      final String thumbnailUrl, | ||||||
|                      final String shortCode, |                      final String shortCode, | ||||||
|                      final CharSequence postCaption, |                      final CharSequence postCaption, | ||||||
|  |                      final String captionId, | ||||||
|                      long timestamp, |                      long timestamp, | ||||||
|                      boolean liked, |                      boolean liked, | ||||||
|                      boolean bookmarked) { |                      boolean bookmarked) { | ||||||
| @ -28,6 +29,7 @@ public class PostModel extends BasePostModel { | |||||||
|         this.thumbnailUrl = thumbnailUrl; |         this.thumbnailUrl = thumbnailUrl; | ||||||
|         this.shortCode = shortCode; |         this.shortCode = shortCode; | ||||||
|         this.postCaption = postCaption; |         this.postCaption = postCaption; | ||||||
|  |         this.captionId = captionId; | ||||||
|         this.timestamp = timestamp; |         this.timestamp = timestamp; | ||||||
|         this.liked = liked; |         this.liked = liked; | ||||||
|         this.saved = bookmarked; |         this.saved = bookmarked; | ||||||
|  | |||||||
| @ -3,15 +3,15 @@ package awais.instagrabber.models; | |||||||
| import java.io.Serializable; | import java.io.Serializable; | ||||||
| 
 | 
 | ||||||
| public final class ProfileModel implements 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 long postCount, followersCount, followingCount; | ||||||
|     private final String id, username, name, biography, url, sdProfilePic, hdProfilePic; |     private final String id, username, name, biography, url, sdProfilePic, hdProfilePic; | ||||||
| 
 | 
 | ||||||
|     public ProfileModel(final boolean isPrivate, final boolean reallyPrivate, |     public ProfileModel(final boolean isPrivate, final boolean reallyPrivate, | ||||||
|                         final boolean isVerified, final String id, final String username, final String name, final String biography, |                         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 String url, final String sdProfilePic, final String hdProfilePic, final long postCount, | ||||||
|                         final long followersCount, final long followingCount, final boolean following, final boolean restricted, |                         final long followersCount, final long followingCount, final boolean following, final boolean follower, | ||||||
|                         final boolean blocked, final boolean requested) { |                         final boolean restricted, final boolean blocked, final boolean requested) { | ||||||
|         this.isPrivate = isPrivate; |         this.isPrivate = isPrivate; | ||||||
|         this.reallyPrivate = reallyPrivate; |         this.reallyPrivate = reallyPrivate; | ||||||
|         this.isVerified = isVerified; |         this.isVerified = isVerified; | ||||||
| @ -26,21 +26,22 @@ public final class ProfileModel implements Serializable { | |||||||
|         this.followersCount = followersCount; |         this.followersCount = followersCount; | ||||||
|         this.followingCount = followingCount; |         this.followingCount = followingCount; | ||||||
|         this.following = following; |         this.following = following; | ||||||
|  |         this.follower = follower; | ||||||
|         this.restricted = restricted; |         this.restricted = restricted; | ||||||
|         this.blocked = blocked; |         this.blocked = blocked; | ||||||
|         this.requested = requested; |         this.requested = requested; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static ProfileModel getDefaultProfileModel() { |     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) { |     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) { |     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() { |     public boolean isPrivate() { | ||||||
| @ -83,31 +84,35 @@ public final class ProfileModel implements Serializable { | |||||||
|         return hdProfilePic; |         return hdProfilePic; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public long getPostCount() { |     public Long getPostCount() { | ||||||
|         return postCount; |         return postCount; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public long getFollowersCount() { |     public Long getFollowersCount() { | ||||||
|         return followersCount; |         return followersCount; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public long getFollowingCount() { |     public Long getFollowingCount() { | ||||||
|         return followingCount; |         return followingCount; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean getFollowing() { |     public boolean isFollowing() { | ||||||
|         return following; |         return following; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean getRestricted() { |     public boolean isFollower() { | ||||||
|  |         return follower; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isRestricted() { | ||||||
|         return restricted; |         return restricted; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean getBlocked() { |     public boolean isBlocked() { | ||||||
|         return blocked; |         return blocked; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public boolean getRequested() { |     public boolean isRequested() { | ||||||
|         return requested; |         return requested; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -6,11 +6,13 @@ import awais.instagrabber.models.enums.MediaItemType; | |||||||
| import awais.instagrabber.models.stickers.PollModel; | import awais.instagrabber.models.stickers.PollModel; | ||||||
| import awais.instagrabber.models.stickers.QuestionModel; | import awais.instagrabber.models.stickers.QuestionModel; | ||||||
| import awais.instagrabber.models.stickers.QuizModel; | import awais.instagrabber.models.stickers.QuizModel; | ||||||
|  | import awais.instagrabber.models.stickers.SliderModel; | ||||||
| import awais.instagrabber.models.stickers.SwipeUpModel; | import awais.instagrabber.models.stickers.SwipeUpModel; | ||||||
| 
 | 
 | ||||||
| public final class StoryModel implements Serializable { | public final class StoryModel implements Serializable { | ||||||
|     private final String storyMediaId; |     private final String storyMediaId; | ||||||
|     private final String storyUrl; |     private final String storyUrl; | ||||||
|  |     private String thumbnail; | ||||||
|     private final String username; |     private final String username; | ||||||
|     private final String userId; |     private final String userId; | ||||||
|     private final MediaItemType itemType; |     private final MediaItemType itemType; | ||||||
| @ -21,6 +23,7 @@ public final class StoryModel implements Serializable { | |||||||
|     private String spotify; |     private String spotify; | ||||||
|     private PollModel poll; |     private PollModel poll; | ||||||
|     private QuestionModel question; |     private QuestionModel question; | ||||||
|  |     private SliderModel slider; | ||||||
|     private QuizModel quiz; |     private QuizModel quiz; | ||||||
|     private SwipeUpModel swipeUp; |     private SwipeUpModel swipeUp; | ||||||
|     private String[] mentions; |     private String[] mentions; | ||||||
| @ -28,10 +31,11 @@ public final class StoryModel implements Serializable { | |||||||
|     private boolean isCurrentSlide = false; |     private boolean isCurrentSlide = false; | ||||||
|     private final boolean canReply; |     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) { |                       final long timestamp, final String username, final String userId, final boolean canReply) { | ||||||
|         this.storyMediaId = storyMediaId; |         this.storyMediaId = storyMediaId; | ||||||
|         this.storyUrl = storyUrl; |         this.storyUrl = storyUrl; | ||||||
|  |         this.thumbnail = thumbnail; | ||||||
|         this.itemType = itemType; |         this.itemType = itemType; | ||||||
|         this.timestamp = timestamp; |         this.timestamp = timestamp; | ||||||
|         this.username = username; |         this.username = username; | ||||||
| @ -43,6 +47,10 @@ public final class StoryModel implements Serializable { | |||||||
|         return storyUrl; |         return storyUrl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public String getThumbnail() { | ||||||
|  |         return thumbnail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public String getStoryMediaId() { |     public String getStoryMediaId() { | ||||||
|         return storyMediaId; |         return storyMediaId; | ||||||
|     } |     } | ||||||
| @ -71,6 +79,10 @@ public final class StoryModel implements Serializable { | |||||||
|         return question; |         return question; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public SliderModel getSlider() { | ||||||
|  |         return slider; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public QuizModel getQuiz() { |     public QuizModel getQuiz() { | ||||||
|         return quiz; |         return quiz; | ||||||
|     } |     } | ||||||
| @ -85,6 +97,10 @@ public final class StoryModel implements Serializable { | |||||||
|         return position; |         return position; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setThumbnail(final String thumbnail) { | ||||||
|  |         this.thumbnail = thumbnail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public void setVideoUrl(final String videoUrl) { |     public void setVideoUrl(final String videoUrl) { | ||||||
|         this.videoUrl = videoUrl; |         this.videoUrl = videoUrl; | ||||||
|     } |     } | ||||||
| @ -109,6 +125,10 @@ public final class StoryModel implements Serializable { | |||||||
|         this.question = question; |         this.question = question; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public void setSlider(final SliderModel slider) { | ||||||
|  |         this.slider = slider; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public void setQuiz(final QuizModel quiz) { |     public void setQuiz(final QuizModel quiz) { | ||||||
|         this.quiz = quiz; |         this.quiz = quiz; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -5,12 +5,20 @@ import java.util.HashMap; | |||||||
| import java.util.Map; | import java.util.Map; | ||||||
| 
 | 
 | ||||||
| public enum NotificationType implements Serializable { | public enum NotificationType implements Serializable { | ||||||
|  |     // web | ||||||
|     LIKE("GraphLikeAggregatedStory"), |     LIKE("GraphLikeAggregatedStory"), | ||||||
|     FOLLOW("GraphFollowAggregatedStory"), |     FOLLOW("GraphFollowAggregatedStory"), | ||||||
|     COMMENT("GraphCommentMediaStory"), |     COMMENT("GraphCommentMediaStory"), | ||||||
|     MENTION("GraphMentionStory"), |     MENTION("GraphMentionStory"), | ||||||
|     TAGGED("GraphUserTaggedStory"), |     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 final String itemType; | ||||||
|     private static final Map<String, NotificationType> map = new HashMap<>(); |     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 java.util.Map; | ||||||
| 
 | 
 | ||||||
| import retrofit2.Call; | import retrofit2.Call; | ||||||
| import retrofit2.http.GET; | import retrofit2.http.FieldMap; | ||||||
| import retrofit2.http.QueryMap; | import retrofit2.http.FormUrlEncoded; | ||||||
|  | import retrofit2.http.POST; | ||||||
| 
 | 
 | ||||||
| public interface FeedRepository { | public interface FeedRepository { | ||||||
|     @GET("/graphql/query/") |     @FormUrlEncoded | ||||||
|     Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams); |     @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}/") |     @GET("/api/v1/feed/location/{location}/") | ||||||
|     Call<String> fetchPosts(@Path("location") final String locationId, |     Call<String> fetchPosts(@Path("location") final String locationId, | ||||||
|                             @QueryMap Map<String, String> queryParams); |                             @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.Call; | ||||||
| import retrofit2.http.FieldMap; | import retrofit2.http.FieldMap; | ||||||
| import retrofit2.http.FormUrlEncoded; | import retrofit2.http.FormUrlEncoded; | ||||||
|  | import retrofit2.http.GET; | ||||||
| import retrofit2.http.Header; | import retrofit2.http.Header; | ||||||
| import retrofit2.http.POST; | import retrofit2.http.POST; | ||||||
| import retrofit2.http.Path; | import retrofit2.http.Path; | ||||||
| import retrofit2.http.QueryMap; | import retrofit2.http.QueryMap; | ||||||
| 
 | 
 | ||||||
| public interface MediaRepository { | 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 |     @FormUrlEncoded | ||||||
|     @POST("/api/v1/media/{mediaId}/{action}/") |     @POST("/api/v1/media/{mediaId}/{action}/") | ||||||
|     Call<String> action(@Header("User-Agent") final String userAgent, |     Call<String> action(@Path("action") final String action, | ||||||
|                         @Path("action") final String action, |  | ||||||
|                         @Path("mediaId") final String mediaId, |                         @Path("mediaId") final String mediaId, | ||||||
|                         @FieldMap final Map<String, String> signedForm); |                         @FieldMap final Map<String, String> signedForm); | ||||||
| 
 | 
 | ||||||
|     @FormUrlEncoded |     @FormUrlEncoded | ||||||
|     @POST("/api/v1/media/{mediaId}/comment/") |     @POST("/api/v1/media/{mediaId}/comment/") | ||||||
|     Call<String> comment(@Header("User-Agent") final String userAgent, |     Call<String> comment(@Path("mediaId") final String mediaId, | ||||||
|                          @Path("mediaId") final String mediaId, |  | ||||||
|                          @FieldMap final Map<String, String> signedForm); |                          @FieldMap final Map<String, String> signedForm); | ||||||
| 
 | 
 | ||||||
|     @FormUrlEncoded |     @FormUrlEncoded | ||||||
|     @POST("/api/v1/media/{mediaId}/comment/bulk_delete/") |     @POST("/api/v1/media/{mediaId}/comment/bulk_delete/") | ||||||
|     Call<String> commentsBulkDelete(@Header("User-Agent") final String userAgent, |     Call<String> commentsBulkDelete(@Path("mediaId") final String mediaId, | ||||||
|                                     @Path("mediaId") final String mediaId, |  | ||||||
|                                     @FieldMap final Map<String, String> signedForm); |                                     @FieldMap final Map<String, String> signedForm); | ||||||
| 
 | 
 | ||||||
|     @FormUrlEncoded |     @FormUrlEncoded | ||||||
|     @POST("/api/v1/media/{commentId}/comment_like/") |     @POST("/api/v1/media/{commentId}/comment_like/") | ||||||
|     Call<String> commentLike(@Header("User-Agent") final String userAgent, |     Call<String> commentLike(@Path("commentId") final String commentId, | ||||||
|                              @Path("commentId") final String commentId, |  | ||||||
|                              @FieldMap final Map<String, String> signedForm); |                              @FieldMap final Map<String, String> signedForm); | ||||||
| 
 | 
 | ||||||
|     @FormUrlEncoded |     @FormUrlEncoded | ||||||
|     @POST("/api/v1/media/{commentId}/comment_unlike/") |     @POST("/api/v1/media/{commentId}/comment_unlike/") | ||||||
|     Call<String> commentUnlike(@Header("User-Agent") final String userAgent, |     Call<String> commentUnlike(@Path("commentId") final String commentId, | ||||||
|                                @Path("commentId") final String commentId, |  | ||||||
|                                @FieldMap final Map<String, String> signedForm); |                                @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 |     @FormUrlEncoded | ||||||
|     @POST("/api/v1/media/upload_finish/") |     @POST("/api/v1/media/upload_finish/") | ||||||
|     Call<String> uploadFinish(// @Header("User-Agent") final String userAgent, |     Call<String> uploadFinish(@Header("retry_context") final String retryContext, | ||||||
|                               @Header("retry_context") final String retryContext, |  | ||||||
|                               @QueryMap Map<String, String> queryParams, |                               @QueryMap Map<String, String> queryParams, | ||||||
|                               @FieldMap final Map<String, String> signedForm); |                               @FieldMap final Map<String, String> signedForm); | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,16 +6,24 @@ import awais.instagrabber.utils.Constants; | |||||||
| import retrofit2.Call; | import retrofit2.Call; | ||||||
| import retrofit2.http.FieldMap; | import retrofit2.http.FieldMap; | ||||||
| import retrofit2.http.FormUrlEncoded; | import retrofit2.http.FormUrlEncoded; | ||||||
|  | import retrofit2.http.GET; | ||||||
| import retrofit2.http.Header; | import retrofit2.http.Header; | ||||||
| import retrofit2.http.Headers; | import retrofit2.http.Headers; | ||||||
| import retrofit2.http.POST; | import retrofit2.http.POST; | ||||||
|  | import retrofit2.http.Query; | ||||||
| 
 | 
 | ||||||
| public interface NewsRepository { | 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 |     @FormUrlEncoded | ||||||
|     @Headers("User-Agent: " + Constants.USER_AGENT) |     @Headers("User-Agent: " + Constants.I_USER_AGENT) | ||||||
|     @POST("https://www.instagram.com/web/activity/mark_checked/") |     @POST("/api/v1/discover/ayml/") | ||||||
|     Call<String> markChecked(@Header("x-csrftoken") String csrfToken, @FieldMap Map<String, String> map); |     Call<String> getAyml(@FieldMap final Map<String, String> form); | ||||||
| } | } | ||||||
|  | |||||||
| @ -9,11 +9,11 @@ import retrofit2.http.QueryMap; | |||||||
| 
 | 
 | ||||||
| public interface ProfileRepository { | public interface ProfileRepository { | ||||||
| 
 | 
 | ||||||
|     @GET("api/v1/users/{uid}/info/") |     @GET("/api/v1/users/{uid}/info/") | ||||||
|     Call<String> getUserInfo(@Path("uid") final String uid); |     Call<String> getUserInfo(@Path("uid") final String uid); | ||||||
| 
 | 
 | ||||||
|     @GET("/graphql/query/") |     @GET("/api/v1/feed/user/{uid}/") | ||||||
|     Call<String> fetch(@QueryMap Map<String, String> queryMap); |     Call<String> fetch(@Path("uid") final String uid, @QueryMap Map<String, String> queryParams); | ||||||
| 
 | 
 | ||||||
|     @GET("/api/v1/feed/saved/") |     @GET("/api/v1/feed/saved/") | ||||||
|     Call<String> fetchSaved(@QueryMap Map<String, String> queryParams); |     Call<String> fetchSaved(@QueryMap Map<String, String> queryParams); | ||||||
|  | |||||||
| @ -2,17 +2,40 @@ package awais.instagrabber.repositories; | |||||||
| 
 | 
 | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| 
 | 
 | ||||||
|  | import awais.instagrabber.repositories.responses.StoryStickerResponse; | ||||||
| import retrofit2.Call; | import retrofit2.Call; | ||||||
|  | import retrofit2.http.FieldMap; | ||||||
|  | import retrofit2.http.FormUrlEncoded; | ||||||
| import retrofit2.http.GET; | import retrofit2.http.GET; | ||||||
| import retrofit2.http.Header; | import retrofit2.http.Header; | ||||||
|  | import retrofit2.http.Path; | ||||||
|  | import retrofit2.http.POST; | ||||||
| import retrofit2.http.QueryMap; | import retrofit2.http.QueryMap; | ||||||
| import retrofit2.http.Url; | import retrofit2.http.Url; | ||||||
| 
 | 
 | ||||||
| public interface StoriesRepository { | 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/") |     @GET("/api/v1/feed/reels_tray/") | ||||||
|     Call<String> getStories(@QueryMap(encoded = true) Map<String, String> variables); |     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 |     @GET | ||||||
|     Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url); |     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}/") |     @GET("/api/v1/feed/tag/{tag}/") | ||||||
|     Call<String> fetchPosts(@Path("tag") final String tag, |     Call<String> fetchPosts(@Path("tag") final String tag, | ||||||
|                             @QueryMap Map<String, String> queryParams); |                             @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 { | public class UserInfo { | ||||||
|     private final String pk; |     private final String pk; | ||||||
|     private final String username; |     private final String username, fullName, profilePicUrl, hdProfilePicUrl; | ||||||
|     private final String fullName; |  | ||||||
|     private final String profilePicUrl; |  | ||||||
| 
 | 
 | ||||||
|     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.pk = pk; | ||||||
|         this.username = username; |         this.username = username; | ||||||
|         this.fullName = fullName; |         this.fullName = fullName; | ||||||
|         this.profilePicUrl = profilePicUrl; |         this.profilePicUrl = profilePicUrl; | ||||||
|  |         this.hdProfilePicUrl = hdProfilePicUrl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public String getPk() { |     public String getPk() { | ||||||
| @ -29,6 +32,10 @@ public class UserInfo { | |||||||
|         return profilePicUrl; |         return profilePicUrl; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public String getHDProfilePicUrl() { | ||||||
|  |         return hdProfilePicUrl; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     @Override |     @Override | ||||||
|     public String toString() { |     public String toString() { | ||||||
|         return "UserInfo{" + |         return "UserInfo{" + | ||||||
| @ -36,6 +43,7 @@ public class UserInfo { | |||||||
|                 ", username='" + username + '\'' + |                 ", username='" + username + '\'' + | ||||||
|                 ", fullName='" + fullName + '\'' + |                 ", fullName='" + fullName + '\'' + | ||||||
|                 ", profilePicUrl='" + profilePicUrl + '\'' + |                 ", profilePicUrl='" + profilePicUrl + '\'' + | ||||||
|  |                 ", hdProfilePicUrl='" + hdProfilePicUrl + '\'' + | ||||||
|                 '}'; |                 '}'; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -99,11 +99,11 @@ public class DirectUser { | |||||||
|                 profileModel.getSdProfilePic(), |                 profileModel.getSdProfilePic(), | ||||||
|                 null, |                 null, | ||||||
|                 new DirectUserFriendshipStatus( |                 new DirectUserFriendshipStatus( | ||||||
|                         profileModel.getFollowing(), |                         profileModel.isFollowing(), | ||||||
|                         profileModel.getBlocked(), |                         profileModel.isBlocked(), | ||||||
|                         profileModel.isPrivate(), |                         profileModel.isPrivate(), | ||||||
|                         false, |                         false, | ||||||
|                         profileModel.getRequested(), |                         profileModel.isRequested(), | ||||||
|                         false |                         false | ||||||
|                 ), |                 ), | ||||||
|                 profileModel.isVerified(), |                 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 CUSTOM_DATE_TIME_FORMAT = "date_time_custom_format"; | ||||||
|     public static final String APP_THEME = "app_theme_v19"; |     public static final String APP_THEME = "app_theme_v19"; | ||||||
|     public static final String APP_LANGUAGE = "app_language_v19"; |     public static final String APP_LANGUAGE = "app_language_v19"; | ||||||
|  |     public static final String STORY_SORT = "story_sort"; | ||||||
|     // int prefs |     // int prefs | ||||||
|     public static final String PREV_INSTALL_VERSION = "prevVersion"; |     public static final String PREV_INSTALL_VERSION = "prevVersion"; | ||||||
|     // boolean prefs |     // boolean prefs | ||||||
| @ -56,9 +57,9 @@ public final class Constants { | |||||||
|     // spoof |     // spoof | ||||||
|     public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) " + |     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 " + |             "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 = |     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"; |     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 |     // 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\":" + |     public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" + | ||||||
|  | |||||||
| @ -152,7 +152,7 @@ public final class ExportImportUtils { | |||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             final Favorite favorite = new Favorite( |             final Favorite favorite = new Favorite( | ||||||
|                     -1, |                     0, | ||||||
|                     query, |                     query, | ||||||
|                     favoriteType, |                     favoriteType, | ||||||
|                     favsObject.optString("s"), |                     favsObject.optString("s"), | ||||||
|  | |||||||
| @ -73,6 +73,10 @@ public final class LocaleUtils { | |||||||
|         if (appLanguageIndex == 14) return "zh_TW"; |         if (appLanguageIndex == 14) return "zh_TW"; | ||||||
|         if (appLanguageIndex == 15) return "ca"; |         if (appLanguageIndex == 15) return "ca"; | ||||||
|         if (appLanguageIndex == 16) return "ru"; |         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; |         return null; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -20,11 +20,17 @@ import awais.instagrabber.models.FeedModel; | |||||||
| import awais.instagrabber.models.PostChild; | import awais.instagrabber.models.PostChild; | ||||||
| import awais.instagrabber.models.ProfileModel; | import awais.instagrabber.models.ProfileModel; | ||||||
| import awais.instagrabber.models.enums.MediaItemType; | import awais.instagrabber.models.enums.MediaItemType; | ||||||
|  | import awais.instagrabber.models.StoryModel; | ||||||
| import awais.instagrabber.repositories.responses.directmessages.DirectItem; | import awais.instagrabber.repositories.responses.directmessages.DirectItem; | ||||||
| import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory; | import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory; | ||||||
| import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt; | import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt; | ||||||
| import awais.instagrabber.repositories.responses.directmessages.ImageVersions2; | import awais.instagrabber.repositories.responses.directmessages.ImageVersions2; | ||||||
| import awais.instagrabber.repositories.responses.directmessages.MediaCandidate; | 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; | import awaisomereport.LogCollector; | ||||||
| 
 | 
 | ||||||
| public final class ResponseBodyUtils { | public final class ResponseBodyUtils { | ||||||
| @ -186,8 +192,8 @@ public final class ResponseBodyUtils { | |||||||
|         return null; |         return null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // public static DirectItemMediaModel getDirectMediaModel(final JSONObject mediaObj) throws Exception { |     // public static DirectItemModel.DirectItemMediaModel getDirectMediaModel(final JSONObject mediaObj) throws Exception { | ||||||
|     //     final DirectItemMediaModel mediaModel; |     //     final DirectItemModel.DirectItemMediaModel mediaModel; | ||||||
|     //     if (mediaObj == null) mediaModel = null; |     //     if (mediaObj == null) mediaModel = null; | ||||||
|     //     else { |     //     else { | ||||||
|     //         final JSONObject userObj = mediaObj.optJSONObject("user"); |     //         final JSONObject userObj = mediaObj.optJSONObject("user"); | ||||||
| @ -203,7 +209,7 @@ public final class ResponseBodyUtils { | |||||||
|     //                     userObj.getString("full_name"), |     //                     userObj.getString("full_name"), | ||||||
|     //                     null, null, |     //                     null, null, | ||||||
|     //                     userObj.getString("profile_pic_url"), |     //                     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)); |     //         final MediaItemType mediaType = getMediaItemType(mediaObj.optInt("media_type", -1)); | ||||||
| @ -212,7 +218,7 @@ public final class ResponseBodyUtils { | |||||||
|     //         if (TextUtils.isEmpty(id)) id = null; |     //         if (TextUtils.isEmpty(id)) id = null; | ||||||
|     // |     // | ||||||
|     //         final ThumbnailDetails thumbnailDetails = getThumbnailUrl(mediaObj, mediaType); |     //         final ThumbnailDetails thumbnailDetails = getThumbnailUrl(mediaObj, mediaType); | ||||||
|     //         mediaModel = new DirectItemMediaModel( |     //         mediaModel = new DirectItemModel.DirectItemMediaModel( | ||||||
|     //                 mediaType, |     //                 mediaType, | ||||||
|     //                 mediaObj.optLong("expiring_at"), |     //                 mediaObj.optLong("expiring_at"), | ||||||
|     //                 mediaObj.optLong("pk"), |     //                 mediaObj.optLong("pk"), | ||||||
| @ -226,7 +232,7 @@ public final class ResponseBodyUtils { | |||||||
|     //     } |     //     } | ||||||
|     //     return mediaModel; |     //     return mediaModel; | ||||||
|     // } |     // } | ||||||
| 
 |     // | ||||||
|     // private static DirectItemType getDirectItemType(final String itemType) { |     // private static DirectItemType getDirectItemType(final String itemType) { | ||||||
|     //     if ("placeholder".equals(itemType)) return DirectItemType.PLACEHOLDER; |     //     if ("placeholder".equals(itemType)) return DirectItemType.PLACEHOLDER; | ||||||
|     //     if ("media".equals(itemType)) return DirectItemType.MEDIA; |     //     if ("media".equals(itemType)) return DirectItemType.MEDIA; | ||||||
| @ -245,81 +251,99 @@ public final class ResponseBodyUtils { | |||||||
|     //     if ("felix_share".equals(itemType)) return DirectItemType.FELIX_SHARE; |     //     if ("felix_share".equals(itemType)) return DirectItemType.FELIX_SHARE; | ||||||
|     //     return DirectItemType.TEXT; |     //     return DirectItemType.TEXT; | ||||||
|     // } |     // } | ||||||
| 
 |     // | ||||||
|     // @NonNull |     // @NonNull | ||||||
|     // public static InboxThreadModel createInboxThreadModel(@NonNull final JSONObject data, final boolean inThreadView) throws Exception { |     // 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 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 String threadNewestCursor = data.optString("newest_cursor"); | ||||||
|     //     final List<ProfileModel> users = new ArrayList<>(); |     //     final String threadOldestCursor = data.optString("oldest_cursor"); | ||||||
|     //     for (int j = 0; j < usersJsonArray.length(); ++j) { |     //     final String threadNextCursor = data.has("next_cursor") ? data.getString("next_cursor") : null; | ||||||
|     //         final JSONObject userObject = usersJsonArray.getJSONObject(j); |     //     final String threadPrevCursor = data.has("prev_cursor") ? data.getString("prev_cursor") : null; | ||||||
|     //         users.add(new ProfileModel(userObject.optBoolean("is_private"), |     // | ||||||
|  |     //     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 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, |     //                                          false, | ||||||
|     //                                          userObject.optBoolean("is_verified"), |     //                                          userObject.optBoolean("is_verified"), | ||||||
|     //                                          String.valueOf(userObject.get("pk")), |     //                                          String.valueOf(userObject.get("pk")), | ||||||
|     //                                          userObject.optString("username"), // optional cuz fb users |     //                                          userObject.optString("username"), // optional cuz fb users | ||||||
|     //                                          userObject.getString("full_name"), |     //                                          userObject.getString("full_name"), | ||||||
|     //                                    null, |     //                                          null, null, | ||||||
|     //                                    null, |  | ||||||
|     //                                          userObject.getString("profile_pic_url"), |     //                                          userObject.getString("profile_pic_url"), | ||||||
|     //                                    null, |     //                                          null, 0, 0, 0, false, false, false, false, false); | ||||||
|     //                                    0, |  | ||||||
|     //                                    0, |  | ||||||
|     //                                    0, |  | ||||||
|     //                                    false, |  | ||||||
|     //                                    false, |  | ||||||
|     //                                    false, |  | ||||||
|     //                                    false)); |  | ||||||
|     //     } |     //     } | ||||||
|     // |     // | ||||||
|     //     final List<ProfileModel> leftUsers = new ArrayList<>(); |     //     final ProfileModel[] leftuserModels = new ProfileModel[leftusersLen]; | ||||||
|     //     for (int j = 0; j < leftUsersJsonArray.length(); ++j) { |     //     for (int j = 0; j < leftusersLen; ++j) { | ||||||
|     //         final JSONObject userObject = leftUsersJsonArray.getJSONObject(j); |     //         final JSONObject userObject = leftusers.getJSONObject(j); | ||||||
|     //         leftUsers.add(new ProfileModel(userObject.getBoolean("is_private"), |     //         leftuserModels[j] = new ProfileModel(userObject.getBoolean("is_private"), | ||||||
|     //                                              false, |     //                                              false, | ||||||
|     //                                              userObject.optBoolean("is_verified"), |     //                                              userObject.optBoolean("is_verified"), | ||||||
|     //                                              String.valueOf(userObject.get("pk")), |     //                                              String.valueOf(userObject.get("pk")), | ||||||
|     //                                              userObject.getString("username"), |     //                                              userObject.getString("username"), | ||||||
|     //                                              userObject.getString("full_name"), |     //                                              userObject.getString("full_name"), | ||||||
|     //                                        null, |     //                                              null, null, | ||||||
|     //                                        null, |  | ||||||
|     //                                              userObject.getString("profile_pic_url"), |     //                                              userObject.getString("profile_pic_url"), | ||||||
|     //                                        null, |     //                                              null, 0, 0, 0, false, false, false, false, false); | ||||||
|     //                                        0, |  | ||||||
|     //                                        0, |  | ||||||
|     //                                        0, |  | ||||||
|     //                                        false, |  | ||||||
|     //                                        false, |  | ||||||
|     //                                        false, |  | ||||||
|     //                                        false)); |  | ||||||
|     //     } |     //     } | ||||||
|     // |     // | ||||||
|     //     final JSONArray admins = data.getJSONArray("admin_user_ids"); |     //     final Long[] adminIDs = new Long[adminsLen]; | ||||||
|     //     final List<Long> adminIDs = new ArrayList<>(); |     //     for (int j = 0; j < adminsLen; ++j) { | ||||||
|     //     for (int j = 0; j < admins.length(); ++j) { |     //         adminIDs[j] = admins.getLong(j); | ||||||
|     //         adminIDs.add(admins.getLong(j)); |  | ||||||
|     //     } |     //     } | ||||||
|     // |     // | ||||||
|     //     final JSONArray items = data.getJSONArray("items"); |     //     final JSONArray items = data.getJSONArray("items"); | ||||||
|     //     final int itemsLen = items.length(); |     //     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) { |     //     for (int i = 0; i < itemsLen; ++i) { | ||||||
|     //         final JSONObject itemObject = items.getJSONObject(i); |     //         final JSONObject itemObject = items.getJSONObject(i); | ||||||
|     // |     // | ||||||
|     //         CharSequence text = null; |     //         CharSequence text = null; | ||||||
|     //         ProfileModel profileModel = 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")); |     //         final DirectItemType itemType = getDirectItemType(itemObject.getString("item_type")); | ||||||
|     //         switch (itemType) { |     //         switch (itemType) { | ||||||
|     //             case ANIMATED_MEDIA: { |     //             case ANIMATED_MEDIA: { | ||||||
|     //                 final JSONObject animatedMedia = itemObject.getJSONObject("animated_media"); |     //                 final JSONObject animatedMedia = itemObject.getJSONObject("animated_media"); | ||||||
|     //                 final JSONObject stickerImage = animatedMedia.getJSONObject("images").getJSONObject("fixed_height"); |     //                 final JSONObject stickerImage = animatedMedia.getJSONObject("images").getJSONObject("fixed_height"); | ||||||
|     //                 mediaModel = new DirectItemAnimatedMediaModel( |     // | ||||||
|  |     //                 animatedMediaModel = new DirectItemModel.DirectItemAnimatedMediaModel( | ||||||
|     //                         animatedMedia.getBoolean("is_random"), |     //                         animatedMedia.getBoolean("is_random"), | ||||||
|     //                         animatedMedia.getBoolean("is_sticker"), |     //                         animatedMedia.getBoolean("is_sticker"), | ||||||
|     //                         animatedMedia.getString("id"), |     //                         animatedMedia.getString("id"), | ||||||
| @ -330,9 +354,11 @@ public final class ResponseBodyUtils { | |||||||
|     //                         stickerImage.getInt("width")); |     //                         stickerImage.getInt("width")); | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case VOICE_MEDIA: { |     //             case VOICE_MEDIA: { | ||||||
|     //                 final JSONObject voiceMedia = itemObject.getJSONObject("voice_media").getJSONObject("media"); |     //                 final JSONObject voiceMedia = itemObject.getJSONObject("voice_media").getJSONObject("media"); | ||||||
|     //                 final JSONObject audio = voiceMedia.getJSONObject("audio"); |     //                 final JSONObject audio = voiceMedia.getJSONObject("audio"); | ||||||
|  |     // | ||||||
|     //                 int[] waveformData = null; |     //                 int[] waveformData = null; | ||||||
|     //                 final JSONArray waveformDataArray = audio.optJSONArray("waveform_data"); |     //                 final JSONArray waveformDataArray = audio.optJSONArray("waveform_data"); | ||||||
|     //                 if (waveformDataArray != null) { |     //                 if (waveformDataArray != null) { | ||||||
| @ -343,35 +369,40 @@ public final class ResponseBodyUtils { | |||||||
|     //                         waveformData[j] = (int) (waveformDataArray.optDouble(j) * 10); |     //                         waveformData[j] = (int) (waveformDataArray.optDouble(j) * 10); | ||||||
|     //                     } |     //                     } | ||||||
|     //                 } |     //                 } | ||||||
|     //                 mediaModel = new DirectItemVoiceMediaModel( |     // | ||||||
|  |     //                 voiceMediaModel = new DirectItemModel.DirectItemVoiceMediaModel( | ||||||
|     //                         voiceMedia.getString("id"), |     //                         voiceMedia.getString("id"), | ||||||
|     //                         audio.getString("audio_src"), |     //                         audio.getString("audio_src"), | ||||||
|     //                         audio.getLong("duration"), |     //                         audio.getLong("duration"), | ||||||
|     //                         waveformData); |     //                         waveformData); | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case LINK: { |     //             case LINK: { | ||||||
|     //                 final JSONObject linkObj = itemObject.getJSONObject("link"); |     //                 final JSONObject linkObj = itemObject.getJSONObject("link"); | ||||||
|     //                 DirectItemLinkContext itemLinkContext = null; |     // | ||||||
|  |     //                 DirectItemModel.DirectItemLinkContext itemLinkContext = null; | ||||||
|     //                 final JSONObject linkContext = linkObj.optJSONObject("link_context"); |     //                 final JSONObject linkContext = linkObj.optJSONObject("link_context"); | ||||||
|     //                 if (linkContext != null) { |     //                 if (linkContext != null) { | ||||||
|     //                     itemLinkContext = new DirectItemLinkContext( |     //                     itemLinkContext = new DirectItemModel.DirectItemLinkContext( | ||||||
|     //                             linkContext.getString("link_url"), |     //                             linkContext.getString("link_url"), | ||||||
|     //                             linkContext.optString("link_title"), |     //                             linkContext.optString("link_title"), | ||||||
|     //                             linkContext.optString("link_summary"), |     //                             linkContext.optString("link_summary"), | ||||||
|     //                             linkContext.optString("link_image_url") |     //                             linkContext.optString("link_image_url") | ||||||
|     //                     ); |     //                     ); | ||||||
|     //                 } |     //                 } | ||||||
|     //                 mediaModel = new DirectItemLinkModel( |     // | ||||||
|  |     //                 linkModel = new DirectItemModel.DirectItemLinkModel( | ||||||
|     //                         linkObj.getString("text"), |     //                         linkObj.getString("text"), | ||||||
|     //                         linkObj.getString("client_context"), |     //                         linkObj.getString("client_context"), | ||||||
|     //                         linkObj.optString("mutation_token"), |     //                         linkObj.optString("mutation_token"), | ||||||
|     //                         itemLinkContext); |     //                         itemLinkContext); | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case REEL_SHARE: { |     //             case REEL_SHARE: { | ||||||
|     //                 final JSONObject reelShare = itemObject.getJSONObject("reel_share"); |     //                 final JSONObject reelShare = itemObject.getJSONObject("reel_share"); | ||||||
|     //                 mediaModel = new DirectItemReelShareModel( |     //                 reelShareModel = new DirectItemModel.DirectItemReelShareModel( | ||||||
|     //                         reelShare.optBoolean("is_reel_persisted"), |     //                         reelShare.optBoolean("is_reel_persisted"), | ||||||
|     //                         reelShare.getLong("reel_owner_id"), |     //                         reelShare.getLong("reel_owner_id"), | ||||||
|     //                         reelShare.getJSONObject("media").getJSONObject("user").getString("username"), |     //                         reelShare.getJSONObject("media").getJSONObject("user").getString("username"), | ||||||
| @ -383,34 +414,39 @@ public final class ResponseBodyUtils { | |||||||
|     //                         getDirectMediaModel(reelShare.optJSONObject("media"))); |     //                         getDirectMediaModel(reelShare.optJSONObject("media"))); | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case RAVEN_MEDIA: { |     //             case RAVEN_MEDIA: { | ||||||
|     //                 final JSONObject visualMedia = itemObject.getJSONObject("visual_media"); |     //                 final JSONObject visualMedia = itemObject.getJSONObject("visual_media"); | ||||||
|  |     // | ||||||
|     //                 final JSONArray seenUserIdsArray = visualMedia.getJSONArray("seen_user_ids"); |     //                 final JSONArray seenUserIdsArray = visualMedia.getJSONArray("seen_user_ids"); | ||||||
|     //                 final int seenUsersLen = seenUserIdsArray.length(); |     //                 final int seenUsersLen = seenUserIdsArray.length(); | ||||||
|     //                 final String[] seenUserIds = new String[seenUsersLen]; |     //                 final String[] seenUserIds = new String[seenUsersLen]; | ||||||
|     //                 for (int j = 0; j < seenUsersLen; j++) |     //                 for (int j = 0; j < seenUsersLen; j++) | ||||||
|     //                     seenUserIds[j] = seenUserIdsArray.getString(j); |     //                     seenUserIds[j] = seenUserIdsArray.getString(j); | ||||||
|     //                 RavenExpiringMediaActionSummary expiringSummaryModel = null; |     // | ||||||
|  |     //                 DirectItemModel.RavenExpiringMediaActionSummaryModel expiringSummaryModel = null; | ||||||
|     //                 final JSONObject actionSummary = visualMedia.optJSONObject("expiring_media_action_summary"); |     //                 final JSONObject actionSummary = visualMedia.optJSONObject("expiring_media_action_summary"); | ||||||
|     //                 if (actionSummary != null) |     //                 if (actionSummary != null) | ||||||
|     //                     expiringSummaryModel = new RavenExpiringMediaActionSummary( |     //                     expiringSummaryModel = new DirectItemModel.RavenExpiringMediaActionSummaryModel( | ||||||
|     //                             actionSummary.getLong("timestamp"), actionSummary.getInt("count"), |     //                             actionSummary.getLong("timestamp"), actionSummary.getInt("count"), | ||||||
|     //                             getExpiringMediaType(actionSummary.getString("type"))); |     //                             getExpiringMediaType(actionSummary.getString("type"))); | ||||||
|     //                 final RavenMediaViewMode viewType; |     // | ||||||
|  |     //                 final RavenMediaViewType viewType; | ||||||
|     //                 final String viewMode = visualMedia.getString("view_mode"); |     //                 final String viewMode = visualMedia.getString("view_mode"); | ||||||
|     //                 switch (viewMode) { |     //                 switch (viewMode) { | ||||||
|     //                     case "replayable": |     //                     case "replayable": | ||||||
|     //                         viewType = RavenMediaViewMode.REPLAYABLE; |     //                         viewType = RavenMediaViewType.REPLAYABLE; | ||||||
|     //                         break; |     //                         break; | ||||||
|     //                     case "permanent": |     //                     case "permanent": | ||||||
|     //                         viewType = RavenMediaViewMode.PERMANENT; |     //                         viewType = RavenMediaViewType.PERMANENT; | ||||||
|     //                         break; |     //                         break; | ||||||
|     //                     case "once": |     //                     case "once": | ||||||
|     //                     default: |     //                     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.optInt("playback_duration_secs"), | ||||||
|     //                         visualMedia.getInt("seen_count"), |     //                         visualMedia.getInt("seen_count"), | ||||||
|     //                         seenUserIds, |     //                         seenUserIds, | ||||||
| @ -420,14 +456,16 @@ public final class ResponseBodyUtils { | |||||||
|     // |     // | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case VIDEO_CALL_EVENT: { |     //             case VIDEO_CALL_EVENT: { | ||||||
|     //                 final JSONObject videoCallEvent = itemObject.getJSONObject("video_call_event"); |     //                 final JSONObject videoCallEvent = itemObject.getJSONObject("video_call_event"); | ||||||
|     //                 mediaModel = new DirectItemVideoCallEventModel(videoCallEvent.getLong("vc_id"), |     //                 videoCallEventModel = new DirectItemModel.DirectItemVideoCallEventModel(videoCallEvent.optLong("vc_id"), | ||||||
|     //                                                                                         videoCallEvent.optBoolean("thread_has_audio_only_call"), |     //                                                                                         videoCallEvent.optBoolean("thread_has_audio_only_call"), | ||||||
|     //                                                                                         videoCallEvent.getString("action"), |     //                                                                                         videoCallEvent.getString("action"), | ||||||
|     //                                                                                         videoCallEvent.getString("description")); |     //                                                                                         videoCallEvent.getString("description")); | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case PROFILE: { |     //             case PROFILE: { | ||||||
|     //                 final JSONObject profile = itemObject.getJSONObject("profile"); |     //                 final JSONObject profile = itemObject.getJSONObject("profile"); | ||||||
|     //                 profileModel = new ProfileModel(profile.getBoolean("is_private"), |     //                 profileModel = new ProfileModel(profile.getBoolean("is_private"), | ||||||
| @ -438,13 +476,15 @@ public final class ResponseBodyUtils { | |||||||
|     //                                                 profile.getString("full_name"), |     //                                                 profile.getString("full_name"), | ||||||
|     //                                                 null, null, |     //                                                 null, null, | ||||||
|     //                                                 profile.getString("profile_pic_url"), |     //                                                 profile.getString("profile_pic_url"), | ||||||
|     //                                                 null, 0, 0, 0, false, false, false, false); |     //                                                 null, 0, 0, 0, false, false, false, false, false); | ||||||
|     //             } |     //             } | ||||||
|     //             break; |     //             break; | ||||||
|  |     // | ||||||
|     //             case PLACEHOLDER: |     //             case PLACEHOLDER: | ||||||
|     //                 final JSONObject placeholder = itemObject.getJSONObject("placeholder"); |     //                 final JSONObject placeholder = itemObject.getJSONObject("placeholder"); | ||||||
|     //                 text = placeholder.getString("title") + "<br><small>" + placeholder.getString("message") + "</small>"; |     //                 text = placeholder.getString("title") + "<br><small>" + placeholder.getString("message") + "</small>"; | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case ACTION_LOG: |     //             case ACTION_LOG: | ||||||
|     //                 if (inThreadView && itemObject.optInt("hide_in_thread", 0) != 0) |     //                 if (inThreadView && itemObject.optInt("hide_in_thread", 0) != 0) | ||||||
|     //                     continue; |     //                     continue; | ||||||
| @ -457,29 +497,35 @@ public final class ResponseBodyUtils { | |||||||
|     //                             + desc.substring(boldItem.getInt("start") + q * 7, boldItem.getInt("end") + q * 7) |     //                             + desc.substring(boldItem.getInt("start") + q * 7, boldItem.getInt("end") + q * 7) | ||||||
|     //                             + "</b>" + desc.substring(boldItem.getInt("end") + q * 7); |     //                             + "</b>" + desc.substring(boldItem.getInt("end") + q * 7); | ||||||
|     //                 } |     //                 } | ||||||
|     //                 mediaModel = new DirectItemActionLogModel(desc); |     //                 actionLogModel = new DirectItemModel.DirectItemActionLogModel(desc); | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case MEDIA_SHARE: |     //             case MEDIA_SHARE: | ||||||
|     //                 mediaModel = getDirectMediaModel(itemObject.getJSONObject("media_share")); |     //                 directMedia = getDirectMediaModel(itemObject.getJSONObject("media_share")); | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case CLIP: |     //             case CLIP: | ||||||
|     //                 mediaModel = getDirectMediaModel(itemObject.getJSONObject("clip").getJSONObject("clip")); |     //                 directMedia = getDirectMediaModel(itemObject.getJSONObject("clip").getJSONObject("clip")); | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case FELIX_SHARE: |     //             case FELIX_SHARE: | ||||||
|     //                 mediaModel = getDirectMediaModel(itemObject.getJSONObject("felix_share").getJSONObject("video")); |     //                 directMedia = getDirectMediaModel(itemObject.getJSONObject("felix_share").getJSONObject("video")); | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case MEDIA: |     //             case MEDIA: | ||||||
|     //                 mediaModel = getDirectMediaModel(itemObject.optJSONObject("media")); |     //                 directMedia = getDirectMediaModel(itemObject.optJSONObject("media")); | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case LIKE: |     //             case LIKE: | ||||||
|     //                 text = itemObject.getString("like"); |     //                 text = itemObject.getString("like"); | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case STORY_SHARE: |     //             case STORY_SHARE: | ||||||
|     //                 final JSONObject storyShare = itemObject.getJSONObject("story_share"); |     //                 final JSONObject storyShare = itemObject.getJSONObject("story_share"); | ||||||
|     //                 if (!storyShare.has("media")) |     //                 if (!storyShare.has("media")) | ||||||
|     //                     text = "<small>" + storyShare.optString("message") + "</small>"; |     //                     text = "<small>" + storyShare.optString("message") + "</small>"; | ||||||
|     //                 else { |     //                 else { | ||||||
|     //                     mediaModel = new DirectItemReelShareModel( |     //                     reelShareModel = new DirectItemModel.DirectItemReelShareModel( | ||||||
|     //                             storyShare.optBoolean("is_reel_persisted"), |     //                             storyShare.optBoolean("is_reel_persisted"), | ||||||
|     //                             storyShare.getJSONObject("media").getJSONObject("user").getLong("pk"), |     //                             storyShare.getJSONObject("media").getJSONObject("user").getLong("pk"), | ||||||
|     //                             storyShare.getJSONObject("media").getJSONObject("user").getString("username"), |     //                             storyShare.getJSONObject("media").getJSONObject("user").getString("username"), | ||||||
| @ -491,6 +537,7 @@ public final class ResponseBodyUtils { | |||||||
|     //                             getDirectMediaModel(storyShare.optJSONObject("media"))); |     //                             getDirectMediaModel(storyShare.optJSONObject("media"))); | ||||||
|     //                 } |     //                 } | ||||||
|     //                 break; |     //                 break; | ||||||
|  |     // | ||||||
|     //             case TEXT: |     //             case TEXT: | ||||||
|     //                 if (!itemObject.has("text")) |     //                 if (!itemObject.has("text")) | ||||||
|     //                     Log.d("AWAISKING_APP", "itemObject: " + itemObject); // todo |     //                     Log.d("AWAISKING_APP", "itemObject: " + itemObject); // todo | ||||||
| @ -514,49 +561,40 @@ public final class ResponseBodyUtils { | |||||||
|     //                 liked, |     //                 liked, | ||||||
|     //                 itemType, |     //                 itemType, | ||||||
|     //                 text, |     //                 text, | ||||||
|  |     //                 linkModel, | ||||||
|     //                 profileModel, |     //                 profileModel, | ||||||
|     //                 mediaModel)); |     //                 reelShareModel, | ||||||
|  |     //                 directMedia, | ||||||
|  |     //                 actionLogModel, | ||||||
|  |     //                 voiceMediaModel, | ||||||
|  |     //                 ravenMediaModel, | ||||||
|  |     //                 videoCallEventModel, | ||||||
|  |     //                 animatedMediaModel)); | ||||||
|     //     } |     //     } | ||||||
|     // |     // | ||||||
|     //     return new InboxThreadModel(readState, |     //     itemModels.trimToSize(); | ||||||
|     //                                 data.getString("thread_id"), |     // | ||||||
|     //                                 data.getString("thread_v2_id"), |     //     return new InboxThreadModel(readState, threadId, threadV2Id, threadType, threadTitle, | ||||||
|     //                                 data.getString("thread_type"), |     //                                 threadNewestCursor, threadOldestCursor, threadNextCursor, threadPrevCursor, | ||||||
|     //                                 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, |  | ||||||
|     //                                 null, // todo |     //                                 null, // todo | ||||||
|     //                                 users, |     //                                 userModels, leftuserModels, adminIDs, | ||||||
|     //                                 leftUsers, |     //                                 itemModels.toArray(new DirectItemModel[0]), | ||||||
|     //                                 adminIDs, |     //                                 muted, isPin, named, canonical, | ||||||
|     //                                 itemModels, |     //                                 pending, threadHasOlder, unreadCount, isSpam, isGroup, archived, lastActivityAt); | ||||||
|     //                                 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")); |  | ||||||
|     // } |     // } | ||||||
| 
 |     // | ||||||
|     // private static ActionType getExpiringMediaType(final String type) { |     // private static RavenExpiringMediaType getExpiringMediaType(final String type) { | ||||||
|     //     if ("raven_sent".equals(type)) return ActionType.SENT; |     //     if ("raven_sent".equals(type)) return RavenExpiringMediaType.RAVEN_SENT; | ||||||
|     //     if ("raven_opened".equals(type)) return ActionType.OPENED; |     //     if ("raven_opened".equals(type)) return RavenExpiringMediaType.RAVEN_OPENED; | ||||||
|     //     if ("raven_blocked".equals(type)) return ActionType.BLOCKED; |     //     if ("raven_blocked".equals(type)) return RavenExpiringMediaType.RAVEN_BLOCKED; | ||||||
|     //     if ("raven_sending".equals(type)) return ActionType.SENDING; |     //     if ("raven_sending".equals(type)) return RavenExpiringMediaType.RAVEN_SENDING; | ||||||
|     //     if ("raven_replayed".equals(type)) return ActionType.REPLAYED; |     //     if ("raven_replayed".equals(type)) return RavenExpiringMediaType.RAVEN_REPLAYED; | ||||||
|     //     if ("raven_delivered".equals(type)) return ActionType.DELIVERED; |     //     if ("raven_delivered".equals(type)) return RavenExpiringMediaType.RAVEN_DELIVERED; | ||||||
|     //     if ("raven_suggested".equals(type)) return ActionType.SUGGESTED; |     //     if ("raven_suggested".equals(type)) return RavenExpiringMediaType.RAVEN_SUGGESTED; | ||||||
|     //     if ("raven_screenshot".equals(type)) return ActionType.SCREENSHOT; |     //     if ("raven_screenshot".equals(type)) return RavenExpiringMediaType.RAVEN_SCREENSHOT; | ||||||
|     //     if ("raven_cannot_deliver".equals(type)) return ActionType.CANNOT_DELIVER; |     //     if ("raven_cannot_deliver".equals(type)) return RavenExpiringMediaType.RAVEN_CANNOT_DELIVER; | ||||||
|     //     //if ("raven_unknown".equals(type)) [default?] |     //     //if ("raven_unknown".equals(type)) [default?] | ||||||
|     //     return ActionType.UNKNOWN; |     //     return RavenExpiringMediaType.RAVEN_UNKNOWN; | ||||||
|     // } |     // } | ||||||
| 
 | 
 | ||||||
|     public static FeedModel parseItem(final JSONObject itemJson) throws JSONException { |     public static FeedModel parseItem(final JSONObject itemJson) throws JSONException { | ||||||
| @ -590,6 +628,7 @@ public final class ResponseBodyUtils { | |||||||
|                     0, |                     0, | ||||||
|                     0, |                     0, | ||||||
|                     following, |                     following, | ||||||
|  |                     false, | ||||||
|                     restricted, |                     restricted, | ||||||
|                     false, |                     false, | ||||||
|                     requested); |                     requested); | ||||||
| @ -606,7 +645,8 @@ public final class ResponseBodyUtils { | |||||||
|                 .setPostId(itemJson.getString(Constants.EXTRAS_ID)) |                 .setPostId(itemJson.getString(Constants.EXTRAS_ID)) | ||||||
|                 .setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(itemJson) : null) |                 .setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(itemJson) : null) | ||||||
|                 .setShortCode(itemJson.getString("code")) |                 .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")) |                 .setCommentsCount(itemJson.optInt("comment_count")) | ||||||
|                 .setTimestamp(itemJson.optLong("taken_at", -1)) |                 .setTimestamp(itemJson.optLong("taken_at", -1)) | ||||||
|                 .setLiked(itemJson.optBoolean("has_liked")) |                 .setLiked(itemJson.optBoolean("has_liked")) | ||||||
| @ -627,7 +667,9 @@ public final class ResponseBodyUtils { | |||||||
|                 break; |                 break; | ||||||
|             case MEDIA_TYPE_SLIDER: |             case MEDIA_TYPE_SLIDER: | ||||||
|                 final List<PostChild> childPosts = getChildPosts(itemJson); |                 final List<PostChild> childPosts = getChildPosts(itemJson); | ||||||
|                 feedModelBuilder.setSliderItems(childPosts); |                 feedModelBuilder.setSliderItems(childPosts) | ||||||
|  |                                 .setImageHeight(childPosts.get(0).getHeight()) | ||||||
|  |                                 .setImageWidth(childPosts.get(0).getWidth()); | ||||||
|                 break; |                 break; | ||||||
|         } |         } | ||||||
|         return feedModelBuilder.build(); |         return feedModelBuilder.build(); | ||||||
| @ -673,6 +715,7 @@ public final class ResponseBodyUtils { | |||||||
|                     false, |                     false, | ||||||
|                     false, |                     false, | ||||||
|                     false, |                     false, | ||||||
|  |                     false, | ||||||
|                     false); |                     false); | ||||||
|         } |         } | ||||||
|         JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); |         JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment"); | ||||||
| @ -743,7 +786,9 @@ public final class ResponseBodyUtils { | |||||||
|                 final JSONArray children = sidecar.optJSONArray("edges"); |                 final JSONArray children = sidecar.optJSONArray("edges"); | ||||||
|                 if (children != null) { |                 if (children != null) { | ||||||
|                     final List<PostChild> sliderItems = getSliderItems(children); |                     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; |         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) { |     public static String getThumbUrl(final ImageVersions2 imageVersions2) { | ||||||
|         if (imageVersions2 == null) return null; |         if (imageVersions2 == null) return null; | ||||||
|         final List<MediaCandidate> candidates = imageVersions2.getCandidates(); |         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.PREV_INSTALL_VERSION; | ||||||
| import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG; | import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG; | ||||||
| import static awais.instagrabber.utils.Constants.SKIPPED_VERSION; | 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; | import static awais.instagrabber.utils.Constants.SWAP_DATE_TIME_FORMAT_ENABLED; | ||||||
| 
 | 
 | ||||||
| public final class SettingsHelper { | 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, |             {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, |                     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_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 {} |     public @interface StringSettings {} | ||||||
| 
 | 
 | ||||||
|     @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, |     @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) { |         if (signed == null) { | ||||||
|             return null; |             return null; | ||||||
|         } |         } | ||||||
|         final String[] parts = signed.split("&"); |  | ||||||
|         final Map<String, String> map = new HashMap<>(); |         final Map<String, String> map = new HashMap<>(); | ||||||
|         for (final String part : parts) { |         map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION); | ||||||
|             final String[] partSplit = part.split("="); |         map.put("signed_body", signed.split("&signed_body=")[1]); | ||||||
|             map.put(partSplit[0], partSplit[1]); |  | ||||||
|         } |  | ||||||
|         return map; |         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; |         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.MutableLiveData; | ||||||
| import androidx.lifecycle.ViewModel; | import androidx.lifecycle.ViewModel; | ||||||
| 
 | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import awais.instagrabber.models.FeedStoryModel; | 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 org.json.JSONArray; | ||||||
| 
 | 
 | ||||||
|  | import java.io.UnsupportedEncodingException; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
|  | import java.util.UUID; | ||||||
| 
 | 
 | ||||||
| import awais.instagrabber.repositories.DirectMessagesRepository; | import awais.instagrabber.repositories.DirectMessagesRepository; | ||||||
| import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions; | import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions; | ||||||
| import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds; | import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds; | ||||||
| import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions; | import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions; | ||||||
| import awais.instagrabber.repositories.requests.directmessages.PhotoBroadcastOptions; | 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.TextBroadcastOptions; | ||||||
| import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOptions; | import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOptions; | ||||||
| import awais.instagrabber.repositories.requests.directmessages.VoiceBroadcastOptions; | 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)); |         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) { |     private Call<DirectThreadBroadcastResponse> broadcast(@NonNull final BroadcastOptions broadcastOptions) { | ||||||
|         if (TextUtils.isEmpty(broadcastOptions.getClientContext())) { |         if (TextUtils.isEmpty(broadcastOptions.getClientContext())) { | ||||||
|             throw new IllegalArgumentException("Broadcast requires a valid client context value"); |             throw new IllegalArgumentException("Broadcast requires a valid client context value"); | ||||||
|  | |||||||
| @ -165,6 +165,7 @@ public class DiscoverService extends BaseService { | |||||||
|                     false, |                     false, | ||||||
|                     false, |                     false, | ||||||
|                     false, |                     false, | ||||||
|  |                     false, | ||||||
|                     false); |                     false); | ||||||
|         } |         } | ||||||
|         final String resourceUrl = ResponseBodyUtils.getHighQualityImage(coverMediaJson); |         final String resourceUrl = ResponseBodyUtils.getHighQualityImage(coverMediaJson); | ||||||
|  | |||||||
| @ -19,6 +19,8 @@ import java.util.Collections; | |||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
|  | import java.util.TimeZone; | ||||||
|  | import java.util.UUID; | ||||||
| 
 | 
 | ||||||
| import awais.instagrabber.models.FeedModel; | import awais.instagrabber.models.FeedModel; | ||||||
| import awais.instagrabber.models.PostChild; | import awais.instagrabber.models.PostChild; | ||||||
| @ -36,7 +38,6 @@ import retrofit2.Retrofit; | |||||||
| 
 | 
 | ||||||
| public class FeedService extends BaseService { | public class FeedService extends BaseService { | ||||||
|     private static final String TAG = "FeedService"; |     private static final String TAG = "FeedService"; | ||||||
|     private static final boolean loadFromMock = false; |  | ||||||
| 
 | 
 | ||||||
|     private final FeedRepository repository; |     private final FeedRepository repository; | ||||||
| 
 | 
 | ||||||
| @ -44,7 +45,7 @@ public class FeedService extends BaseService { | |||||||
| 
 | 
 | ||||||
|     private FeedService() { |     private FeedService() { | ||||||
|         final Retrofit retrofit = getRetrofitBuilder() |         final Retrofit retrofit = getRetrofitBuilder() | ||||||
|                 .baseUrl("https://www.instagram.com") |                 .baseUrl("https://i.instagram.com") | ||||||
|                 .build(); |                 .build(); | ||||||
|         repository = retrofit.create(FeedRepository.class); |         repository = retrofit.create(FeedRepository.class); | ||||||
|     } |     } | ||||||
| @ -56,41 +57,26 @@ public class FeedService extends BaseService { | |||||||
|         return instance; |         return instance; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public void fetch(final int maxItemsToLoad, |     public void fetch(final String csrfToken, | ||||||
|                       final String cursor, |                       final String cursor, | ||||||
|                       final ServiceCallback<PostsFetchResponse> callback) { |                       final ServiceCallback<PostsFetchResponse> callback) { | ||||||
|         if (loadFromMock) { |         final Map<String, String> form = new HashMap<>(); | ||||||
|             final Handler handler = new Handler(); |         form.put("_uuid", UUID.randomUUID().toString()); | ||||||
|             handler.postDelayed(() -> { |         form.put("_csrftoken", csrfToken); | ||||||
|                 final ClassLoader classLoader = getClass().getClassLoader(); |         form.put("phone_id", UUID.randomUUID().toString()); | ||||||
|                 if (classLoader == null) { |         form.put("device_id", UUID.randomUUID().toString()); | ||||||
|                     Log.e(TAG, "fetch: classLoader is null!"); |         form.put("client_session_id", UUID.randomUUID().toString()); | ||||||
|                     return; |         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"); | ||||||
|         } |         } | ||||||
|                 try (InputStream resourceAsStream = classLoader.getResourceAsStream("feed_response.json"); |         else { | ||||||
|                      Reader in = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)) { |             form.put("is_pull_to_refresh", "1"); | ||||||
|                     final int bufferSize = 1024; |             form.put("reason", "pull_to_refresh"); | ||||||
|                     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())); |         final Call<String> request = repository.fetch(form); | ||||||
|                 } catch (IOException | JSONException e) { |  | ||||||
|                     Log.e(TAG, "fetch: ", e); |  | ||||||
|                 } |  | ||||||
|             }, 1000); |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         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); |  | ||||||
|         request.enqueue(new Callback<String>() { |         request.enqueue(new Callback<String>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { | ||||||
| @ -130,35 +116,45 @@ public class FeedService extends BaseService { | |||||||
|     @NonNull |     @NonNull | ||||||
|     private PostsFetchResponse parseResponseBody(@NonNull final String body) |     private PostsFetchResponse parseResponseBody(@NonNull final String body) | ||||||
|             throws JSONException { |             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 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) { |         for (int i = 0; i < feedItems.length(); ++i) { | ||||||
|             final JSONObject itemJson = feedItems.optJSONObject(i); |             final JSONObject itemJson = feedItems.optJSONObject(i); | ||||||
|             if (itemJson == null) { |             if (itemJson == null || itemJson.has("injected")) { | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson); |             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) { |                             if (feedModel != null) { | ||||||
|                                 feedModels.add(feedModel); |                                 feedModels.add(feedModel); | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|         return new PostsFetchResponse(feedModels, hasNextPage, endCursor); |                     } | ||||||
|  |                     else continue; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 final FeedModel feedModel = ResponseBodyUtils.parseItem(itemJson); | ||||||
|  |                 if (feedModel != null) { | ||||||
|  |                     feedModels.add(feedModel); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return new PostsFetchResponse(feedModels, moreAvailable, nextMaxId); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -153,13 +153,12 @@ public class FriendshipService extends BaseService { | |||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // log in required |  | ||||||
|     public void getList(final boolean follower, |     public void getList(final boolean follower, | ||||||
|                         final String targetUserId, |                         final String targetUserId, | ||||||
|                         final String maxId, |                         final String maxId, | ||||||
|                         final ServiceCallback<FriendshipRepoListFetchResponse> callback) { |                         final ServiceCallback<FriendshipRepoListFetchResponse> callback) { | ||||||
|         final Map<String, String> queryMap = new HashMap<>(); |         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, |         final Call<String> request = repository.getList(Constants.I_USER_AGENT, | ||||||
|                                                         targetUserId, |                                                         targetUserId, | ||||||
|                                                         follower ? "followers" : "following", |                                                         follower ? "followers" : "following", | ||||||
| @ -173,7 +172,6 @@ public class FriendshipService extends BaseService { | |||||||
|                     } |                     } | ||||||
|                     final String body = response.body(); |                     final String body = response.body(); | ||||||
|                     if (TextUtils.isEmpty(body)) { |                     if (TextUtils.isEmpty(body)) { | ||||||
| 
 |  | ||||||
|                         callback.onSuccess(null); |                         callback.onSuccess(null); | ||||||
|                         return; |                         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.models.FeedModel; | ||||||
| import awais.instagrabber.repositories.LocationRepository; | import awais.instagrabber.repositories.LocationRepository; | ||||||
|  | import awais.instagrabber.repositories.responses.PostsFetchResponse; | ||||||
| import awais.instagrabber.utils.ResponseBodyUtils; | import awais.instagrabber.utils.ResponseBodyUtils; | ||||||
| import awais.instagrabber.utils.TextUtils; | import awais.instagrabber.utils.TextUtils; | ||||||
| import retrofit2.Call; | import retrofit2.Call; | ||||||
| @ -29,7 +30,7 @@ import retrofit2.Retrofit; | |||||||
| public class LocationService extends BaseService { | public class LocationService extends BaseService { | ||||||
|     private static final String TAG = "LocationService"; |     private static final String TAG = "LocationService"; | ||||||
| 
 | 
 | ||||||
|     private final LocationRepository repository, webRepository; |     private final LocationRepository repository; | ||||||
| 
 | 
 | ||||||
|     private static LocationService instance; |     private static LocationService instance; | ||||||
| 
 | 
 | ||||||
| @ -38,10 +39,6 @@ public class LocationService extends BaseService { | |||||||
|                 .baseUrl("https://i.instagram.com") |                 .baseUrl("https://i.instagram.com") | ||||||
|                 .build(); |                 .build(); | ||||||
|         repository = retrofit.create(LocationRepository.class); |         repository = retrofit.create(LocationRepository.class); | ||||||
|         final Retrofit webRetrofit = getRetrofitBuilder() |  | ||||||
|                 .baseUrl("https://www.instagram.com") |  | ||||||
|                 .build(); |  | ||||||
|         webRepository = webRetrofit.create(LocationRepository.class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public static LocationService getInstance() { |     public static LocationService getInstance() { | ||||||
| @ -53,7 +50,7 @@ public class LocationService extends BaseService { | |||||||
| 
 | 
 | ||||||
|     public void fetchPosts(@NonNull final String locationId, |     public void fetchPosts(@NonNull final String locationId, | ||||||
|                            final String maxId, |                            final String maxId, | ||||||
|                            final ServiceCallback<LocationPostsFetchResponse> callback) { |                            final ServiceCallback<PostsFetchResponse> callback) { | ||||||
|         final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); |         final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); | ||||||
|         if (!TextUtils.isEmpty(maxId)) { |         if (!TextUtils.isEmpty(maxId)) { | ||||||
|             builder.put("max_id", maxId); |             builder.put("max_id", maxId); | ||||||
| @ -71,7 +68,7 @@ public class LocationService extends BaseService { | |||||||
|                         callback.onSuccess(null); |                         callback.onSuccess(null); | ||||||
|                         return; |                         return; | ||||||
|                     } |                     } | ||||||
|                     final LocationPostsFetchResponse tagPostsFetchResponse = parseResponse(body); |                     final PostsFetchResponse tagPostsFetchResponse = parseResponse(body); | ||||||
|                     callback.onSuccess(tagPostsFetchResponse); |                     callback.onSuccess(tagPostsFetchResponse); | ||||||
|                 } catch (JSONException e) { |                 } catch (JSONException e) { | ||||||
|                     Log.e(TAG, "onResponse", 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 JSONObject root = new JSONObject(body); | ||||||
|         final boolean moreAvailable = root.optBoolean("more_available"); |         final boolean moreAvailable = root.optBoolean("more_available"); | ||||||
|         final String nextMaxId = root.optString("next_max_id"); |         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 JSONArray itemsJson = root.optJSONArray("items"); | ||||||
|         final List<FeedModel> items = parseItems(itemsJson); |         final List<FeedModel> items = parseItems(itemsJson); | ||||||
|         return new LocationPostsFetchResponse( |         return new PostsFetchResponse( | ||||||
|  |                 items, | ||||||
|                 moreAvailable, |                 moreAvailable, | ||||||
|                 nextMaxId, |                 nextMaxId | ||||||
|                 numResults, |  | ||||||
|                 status, |  | ||||||
|                 items |  | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -122,174 +115,4 @@ public class LocationService extends BaseService { | |||||||
|         } |         } | ||||||
|         return feedModels; |         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 com.google.common.collect.ImmutableMap; | ||||||
| 
 | 
 | ||||||
|  | import org.json.JSONArray; | ||||||
| import org.json.JSONException; | import org.json.JSONException; | ||||||
| import org.json.JSONObject; | import org.json.JSONObject; | ||||||
| 
 | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.UUID; | import java.util.UUID; | ||||||
| 
 | 
 | ||||||
|  | import awais.instagrabber.models.FeedModel; | ||||||
|  | import awais.instagrabber.models.ProfileModel; | ||||||
| import awais.instagrabber.repositories.MediaRepository; | import awais.instagrabber.repositories.MediaRepository; | ||||||
| import awais.instagrabber.repositories.requests.UploadFinishOptions; | import awais.instagrabber.repositories.requests.UploadFinishOptions; | ||||||
| import awais.instagrabber.utils.Constants; | import awais.instagrabber.utils.Constants; | ||||||
| import awais.instagrabber.utils.DateUtils; | import awais.instagrabber.utils.DateUtils; | ||||||
| import awais.instagrabber.utils.MediaUploadHelper; | import awais.instagrabber.utils.MediaUploadHelper; | ||||||
|  | import awais.instagrabber.utils.ResponseBodyUtils; | ||||||
| import awais.instagrabber.utils.Utils; | import awais.instagrabber.utils.Utils; | ||||||
| import retrofit2.Call; | import retrofit2.Call; | ||||||
| import retrofit2.Callback; | import retrofit2.Callback; | ||||||
| @ -48,6 +53,37 @@ public class MediaService extends BaseService { | |||||||
|         return instance; |         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, |     public void like(final String mediaId, | ||||||
|                      final String userId, |                      final String userId, | ||||||
|                      final String csrfToken, |                      final String csrfToken, | ||||||
| @ -88,7 +124,7 @@ public class MediaService extends BaseService { | |||||||
|         form.put("_uuid", UUID.randomUUID().toString()); |         form.put("_uuid", UUID.randomUUID().toString()); | ||||||
|         // form.put("radio_type", "wifi-none"); |         // form.put("radio_type", "wifi-none"); | ||||||
|         final Map<String, String> signedForm = Utils.sign(form); |         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>() { |         request.enqueue(new Callback<String>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onResponse(@NonNull final Call<String> call, |             public void onResponse(@NonNull final Call<String> call, | ||||||
| @ -137,7 +173,7 @@ public class MediaService extends BaseService { | |||||||
|             form.put("replied_to_comment_id", replyToCommentId); |             form.put("replied_to_comment_id", replyToCommentId); | ||||||
|         } |         } | ||||||
|         final Map<String, String> signedForm = Utils.sign(form); |         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>() { |         commentRequest.enqueue(new Callback<String>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |             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("_uid", userId); | ||||||
|         form.put("_uuid", UUID.randomUUID().toString()); |         form.put("_uuid", UUID.randomUUID().toString()); | ||||||
|         final Map<String, String> signedForm = Utils.sign(form); |         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>() { |         bulkDeleteRequest.enqueue(new Callback<String>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |             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("_uid", userId); | ||||||
|         // form.put("_uuid", UUID.randomUUID().toString()); |         // form.put("_uuid", UUID.randomUUID().toString()); | ||||||
|         final Map<String, String> signedForm = Utils.sign(form); |         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>() { |         commentLikeRequest.enqueue(new Callback<String>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |             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("_uid", userId); | ||||||
|         // form.put("_uuid", UUID.randomUUID().toString()); |         // form.put("_uuid", UUID.randomUUID().toString()); | ||||||
|         final Map<String, String> signedForm = Utils.sign(form); |         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>() { |         commentUnlikeRequest.enqueue(new Callback<String>() { | ||||||
|             @Override |             @Override | ||||||
|             public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) { |             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, |     public Call<String> uploadFinish(final long userId, | ||||||
|                                      @NonNull final String csrfToken, |                                      @NonNull final String csrfToken, | ||||||
|                                      @NonNull final UploadFinishOptions options) { |                                      @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