Merge branch 'master' into dm-notifications-enhancements

This commit is contained in:
Ammar Githam 2021-01-02 13:00:14 +09:00
commit d42d4dd3ea
187 changed files with 8193 additions and 5040 deletions

View File

@ -61,6 +61,15 @@
"translation"
]
},
{
"login": "CrazyMarvin",
"name": "CrazyMarvin",
"avatar_url": "https://avatars3.githubusercontent.com/u/15004217?v=4",
"profile": "https://github.com/CrazyMarvin",
"contributions": [
"financial"
]
},
{
"login": "KevinNThomas",
"name": "Kevin Thomas",
@ -82,16 +91,6 @@
"question"
]
},
{
"login": "e-edgren",
"name": "Airikr",
"avatar_url": "https://avatars0.githubusercontent.com/u/53869451",
"profile": "https://airikr.me/",
"contributions": [
"ideas",
"question"
]
},
{
"login": "alin-1",
"name": "ALIN",
@ -102,6 +101,26 @@
"ideas"
]
},
{
"login": "RickyM7",
"name": "Ricardo",
"avatar_url": "https://avatars3.githubusercontent.com/u/24703825?v=4",
"profile": "https://github.com/RickyM7",
"contributions": [
"bug",
"translation"
]
},
{
"login": "e-edgren",
"name": "Airikr",
"avatar_url": "https://avatars0.githubusercontent.com/u/53869451",
"profile": "https://airikr.me/",
"contributions": [
"ideas",
"question"
]
},
{
"login": "Akrai",
"name": "Akrai",
@ -112,6 +131,15 @@
"translation"
]
},
{
"login": "cizordj",
"name": "Cézar Augusto",
"avatar_url": "https://avatars2.githubusercontent.com/u/32869222?v=4",
"profile": "https://github.com/cizordj",
"contributions": [
"translation"
]
},
{
"login": "farzadx",
"name": "farzadx",
@ -149,19 +177,28 @@
]
},
{
"login": "kernoeb",
"name": "kernoeb",
"avatar_url": "https://avatars3.githubusercontent.com/u/24623168",
"profile": "https://becauseofprog.fr/",
"login": "initdebugs",
"name": "Initdebugs",
"avatar_url": "https://avatars0.githubusercontent.com/u/75781464?v=4",
"profile": "https://github.com/initdebugs",
"contributions": [
"translation"
]
},
{
"login": "Lego8486",
"name": "Ten_Lego",
"avatar_url": "https://avatars1.githubusercontent.com/u/47414485",
"profile": "https://github.com/Lego8486",
"login": "CrafterSvK",
"name": "Jakub Janek",
"avatar_url": "https://avatars3.githubusercontent.com/u/8365659?v=4",
"profile": "https://janek.xyz/",
"contributions": [
"translation"
]
},
{
"login": "kernoeb",
"name": "kernoeb",
"avatar_url": "https://avatars3.githubusercontent.com/u/24623168",
"profile": "https://becauseofprog.fr/",
"contributions": [
"translation"
]
@ -229,16 +266,6 @@
"translation"
]
},
{
"login": "RickyM7",
"name": "Ricardo",
"avatar_url": "https://avatars3.githubusercontent.com/u/24703825?v=4",
"profile": "https://github.com/RickyM7",
"contributions": [
"bug",
"translation"
]
},
{
"login": "rikishi0071",
"name": "rikishi0071",
@ -257,6 +284,15 @@
"translation"
]
},
{
"login": "Lego8486",
"name": "Ten_Lego",
"avatar_url": "https://avatars1.githubusercontent.com/u/47414485",
"profile": "https://github.com/Lego8486",
"contributions": [
"translation"
]
},
{
"login": "wagnim",
"name": "wagnim",
@ -265,6 +301,15 @@
"contributions": [
"translation"
]
},
{
"login": "ysakamoto",
"name": "ysakamoto",
"avatar_url": "https://avatars3.githubusercontent.com/u/1331642?v=4",
"profile": "https://github.com/ysakamoto",
"contributions": [
"translation"
]
}
],
"contributorsPerLine": 6,

View File

@ -9,7 +9,7 @@
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)
[![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE)
[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-32-orange.svg)](#contributors)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
We're previously known as InstaGrabber.
@ -32,11 +32,15 @@ Version status: ![F-Droid](https://img.shields.io/f-droid/v/me.austinhuang.insta
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg" alt="Hashtag" width="15%"/></a>
<a href="https://github.com/austinhuang0131/instagrabber/blob/master/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg"><img src="./fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg" alt="Discover Topics" width="15%"/></a>
## We need maintainers!
To speed up development, we need more hands on deck. If you are proficient in Java and Android development, and are willing to perform such a public service, please [contact us](https://t.me/austinhuang).
## Contact us
* Use [GitHub issues](https://github.com/austinhuang0131/instagrabber/issues) when possible.
* Email: [Barinsta@AustinHuang.me](mailto:barinsta@austinhuang.me?body=Please%20note%20that%20your%20email%20address%20and%20the%20entire%20content%20will%20be%20published%20onto%20GitHub%20issues.%20If%20you%20do%20not%20wish%20to%20do%20that%2C%20use%20other%20contact%20methods%20instead.) (Synced to GitHub issues)
* Reddit: [r/Barinsta](https://reddit.com/r/barinsta)
* Reddit: [![r/Barinsta](https://img.shields.io/reddit/subreddit-subscribers/Barinsta?style=social)](https://reddit.com/r/barinsta)
* Chat (Bridged to each other): [![Matrix](https://img.shields.io/badge/Matrix-%23Barinsta:matrix.org-000000?logo=matrix)](https://matrix.to/#/#barinsta:matrix.org) [![Telegram](https://img.shields.io/badge/Telegram-@Grabber__App-2CA5E0?logo=telegram)](https://t.me/grabber_app) [![Discord](https://img.shields.io/badge/Discord-YtEDzN2-7289da?logo=discord&logoColor=white)](https://discord.gg/YtEDzN2) [![Gitter](https://img.shields.io/badge/Gitter-InstaGrabber/General-ed1965?logo=gitter)](https://gitter.im/instagrabber/general)
## Contributors
@ -53,36 +57,43 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
<td align="center"><a href="https://github.com/andersonvom"><img src="https://avatars3.githubusercontent.com/u/69922?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Anderson Mesquita</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=andersonvom" title="Code">💻</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aandersonvom" title="Bug reports">🐛</a></td>
<td align="center"><a href="http://rerolledgeek.blogspot.com/"><img src="https://avatars3.githubusercontent.com/u/5278488?s=100" width="100px;" alt=""/><br /><sub><b>AWAiS</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/commits?author=AwaisKing" title="Code">💻</a></td>
<td align="center"><a href="https://stefannajdovski.com/"><img src="https://avatars2.githubusercontent.com/u/42580385?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Stefan Najdovski</b></sub></a><br /><a href="#design-snajdovski" title="Design">🎨</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
<td align="center"><a href="https://github.com/CrazyMarvin"><img src="https://avatars3.githubusercontent.com/u/15004217?v=4?s=100" width="100px;" alt=""/><br /><sub><b>CrazyMarvin</b></sub></a><br /><a href="#financial-CrazyMarvin" title="Financial">💵</a></td>
</tr>
<tr>
<td align="center"><a href="http://kevinthomas.dev"><img src="https://avatars2.githubusercontent.com/u/15370181?s=100" width="100px;" alt=""/><br /><sub><b>Kevin Thomas</b></sub></a><br /><a href="#financial-KevinNThomas" title="Financial">💵</a></td>
<td align="center"><a href="https://github.com/Shadowspear123"><img src="https://avatars1.githubusercontent.com/u/50462281?s=100" width="100px;" alt=""/><br /><sub><b>Shadowspear123</b></sub></a><br /><a href="#blog-Shadowspear123" title="Blogposts">📝</a> <a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3AShadowspear123" title="Bug reports">🐛</a> <a href="#ideas-Shadowspear123" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-Shadowspear123" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://airikr.me/"><img src="https://avatars0.githubusercontent.com/u/53869451?s=100" width="100px;" alt=""/><br /><sub><b>Airikr</b></sub></a><br /><a href="#ideas-e-edgren" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-e-edgren" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://alin.atwebpages.com/"><img src="https://avatars2.githubusercontent.com/u/13281020?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ALIN</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3Aalin-1" title="Bug reports">🐛</a> <a href="#ideas-alin-1" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://airikr.me/"><img src="https://avatars0.githubusercontent.com/u/53869451?s=100" width="100px;" alt=""/><br /><sub><b>Airikr</b></sub></a><br /><a href="#ideas-e-edgren" title="Ideas, Planning, & Feedback">🤔</a> <a href="#question-e-edgren" title="Answering Questions">💬</a></td>
<td align="center"><a href="https://github.com/Akrai"><img src="https://avatars1.githubusercontent.com/u/5624597?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Akrai</b></sub></a><br /><a href="#ideas-Akrai" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/cizordj"><img src="https://avatars2.githubusercontent.com/u/32869222?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cézar Augusto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/farzadx"><img src="https://avatars2.githubusercontent.com/u/70059397?v=4?s=100" width="100px;" alt=""/><br /><sub><b>farzadx</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/faydin"><img src="https://avatars2.githubusercontent.com/u/22706676?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Fatih Aydın</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/fouze555"><img src="https://avatars3.githubusercontent.com/u/71935341?v=4?s=100" width="100px;" alt=""/><br /><sub><b>fouze555</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Galang23"><img src="https://avatars3.githubusercontent.com/u/13700948?s=100" width="100px;" alt=""/><br /><sub><b>Galang23</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/initdebugs"><img src="https://avatars0.githubusercontent.com/u/75781464?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Initdebugs</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://janek.xyz/"><img src="https://avatars3.githubusercontent.com/u/8365659?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jakub Janek</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://becauseofprog.fr/"><img src="https://avatars3.githubusercontent.com/u/24623168?s=100" width="100px;" alt=""/><br /><sub><b>kernoeb</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/MoaufmKlo"><img src="https://avatars1.githubusercontent.com/u/45636897?s=100" width="100px;" alt=""/><br /><sub><b>MoaufmKlo</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/nalinalini"><img src="https://avatars0.githubusercontent.com/u/65640431?v=4?s=100" width="100px;" alt=""/><br /><sub><b>nalinalini</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/peterge1998"><img src="https://avatars2.githubusercontent.com/u/47355238?s=100" width="100px;" alt=""/><br /><sub><b>peterge1998</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/PierreM0"><img src="https://avatars3.githubusercontent.com/u/71077853?v=4?s=100" width="100px;" alt=""/><br /><sub><b>PierreM0</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/RAMAR-RAR"><img src="https://avatars3.githubusercontent.com/u/47423745?s=100" width="100px;" alt=""/><br /><sub><b>RAMAR-RAR</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/rohang02"><img src="https://avatars3.githubusercontent.com/u/47921164?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rohang02</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/retiolus"><img src="https://avatars1.githubusercontent.com/u/65604466?v=4?s=100" width="100px;" alt=""/><br /><sub><b>retiolus</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/RickyM7"><img src="https://avatars3.githubusercontent.com/u/24703825?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Ricardo</b></sub></a><br /><a href="https://github.com/austinhuang0131/barinsta/issues?q=author%3ARickyM7" title="Bug reports">🐛</a> <a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/rikishi0071"><img src="https://avatars3.githubusercontent.com/u/18183855?v=4?s=100" width="100px;" alt=""/><br /><sub><b>rikishi0071</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://stillu.cc/"><img src="https://avatars2.githubusercontent.com/u/5843208?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Still Hsu</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/Lego8486"><img src="https://avatars1.githubusercontent.com/u/47414485?s=100" width="100px;" alt=""/><br /><sub><b>Ten_Lego</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/wagnim"><img src="https://avatars0.githubusercontent.com/u/30241419?s=100" width="100px;" alt=""/><br /><sub><b>wagnim</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
<td align="center"><a href="https://github.com/ysakamoto"><img src="https://avatars3.githubusercontent.com/u/1331642?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ysakamoto</b></sub></a><br /><a href="https://crowdin.com/project/instagrabber" title="Translation">🌍</a></td>
</tr>
</table>
@ -92,12 +103,11 @@ Prominent contributors are listed here in the [all-contributors](https://allcont
## License
This app is originally made by [@AwaisKing](https://github.com/AwaisKing) on [GitLab](https://gitlab.com/AwaisKing/instagrabber).
This app's predecessor, InstaGrabber, is originally made by [@AwaisKing](https://github.com/AwaisKing) on [GitLab](https://gitlab.com/AwaisKing/instagrabber).
Barinsta (ex-InstaGrabber)
Copyright (C) 2019 AWAiS <chapter50000@hotmail.com>
Copyright (C) 2020 Austin Huang <im@austinhuang.me>
Ammar Githam <ammargitham786@gmail.com>
Barinsta
Copyright (C) 2020-2021 Austin Huang <im@austinhuang.me>
Ammar Githam <ammargitham786@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@ -10,8 +10,8 @@ android {
minSdkVersion 21
targetSdkVersion 29
versionCode 54
versionName '19.0.2'
versionCode 56
versionName '19.0.4'
multiDexEnabled true
@ -98,6 +98,9 @@ dependencies {
implementation "androidx.emoji:emoji:$emoji_compat_version"
implementation "androidx.emoji:emoji-appcompat:$emoji_compat_version"
// implementation 'com.github.hendrawd:StorageUtil:1.1.0'
implementation 'me.austinhuang:AutoLinkTextViewV2:-SNAPSHOT'
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.facebook.fresco:fresco:2.3.0'
implementation 'com.facebook.fresco:animated-webp:2.3.0'
@ -109,7 +112,6 @@ dependencies {
implementation 'org.apache.commons:commons-imaging:1.0-alpha2'
implementation 'com.ibm.icu:icu4j:68.1'
implementation 'com.github.ammargitham:AutoLinkTextViewV2:master-SNAPSHOT'
implementation 'com.github.ammargitham:uCrop:2.3-native-beta-2'
implementation 'com.github.ammargitham:android-gpuimage:2.1.1-beta4'

View File

@ -83,8 +83,8 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
}
};
private final CommentCallback commentCallback;
private CommentModel selected;
private int selectedIndex;
private CommentModel selected, toChangeLike;
private int selectedIndex, likedIndex;
public CommentsAdapter(final CommentCallback commentCallback) {
super(DIFF_CALLBACK);
@ -106,10 +106,12 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
final CommentModel commentModel = getItem(position);
CommentModel commentModel = getItem(position);
if (commentModel == null) return;
final int type = getItemViewType(position);
final boolean selected = this.selected != null && this.selected.getId().equals(commentModel.getId());
final boolean toLike = this.toChangeLike != null && this.toChangeLike.getId().equals(commentModel.getId());
if (toLike) commentModel = this.toChangeLike;
if (type == TYPE_PARENT) {
final ParentCommentViewHolder viewHolder = (ParentCommentViewHolder) holder;
viewHolder.bind(commentModel, selected, commentCallback);
@ -174,6 +176,14 @@ public final class CommentsAdapter extends ListAdapter<CommentModel, RecyclerVie
notifyItemChanged(selectedIndex);
}
public void setLiked(final CommentModel commentModel, final boolean liked) {
likedIndex = getCurrentList().indexOf(commentModel);
CommentModel newCommentModel = commentModel;
newCommentModel.setLiked(liked);
this.toChangeLike = newCommentModel;
notifyItemChanged(likedIndex);
}
public CommentModel getSelected() {
return selected;
}

View File

@ -46,7 +46,7 @@ public final class FeedAdapterV2 extends ListAdapter<FeedModel, RecyclerView.Vie
@Override
public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId());
return oldItem.getPostId().equals(newItem.getPostId()) && oldItem.getPostCaption().equals(newItem.getPostCaption());
}
};
private final AdapterSelectionCallback adapterSelectionCallback = new AdapterSelectionCallback() {

View File

@ -4,12 +4,19 @@ import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import awais.instagrabber.adapters.viewholder.FeedStoryViewHolder;
import awais.instagrabber.databinding.ItemHighlightBinding;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedStoryViewHolder> {
private final OnFeedStoryClickListener listener;
@ -22,7 +29,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt
@Override
public boolean areContentsTheSame(@NonNull final FeedStoryModel oldItem, @NonNull final FeedStoryModel newItem) {
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId());
return oldItem.getStoryMediaId().equals(newItem.getStoryMediaId()) && oldItem.isFullyRead().equals(newItem.isFullyRead());
}
};
@ -47,5 +54,7 @@ public final class FeedStoriesAdapter extends ListAdapter<FeedStoryModel, FeedSt
public interface OnFeedStoryClickListener {
void onFeedStoryClick(FeedStoryModel model, int position);
void onFeedStoryLongClick(FeedStoryModel model, int position);
}
}

View 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);
}
}

View File

@ -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);
}
}

View 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();
}
}

View File

@ -76,16 +76,23 @@ public final class NotificationsAdapter extends ListAdapter<NotificationModel, N
private List<NotificationModel> sort(final List<NotificationModel> list) {
final List<NotificationModel> listCopy = new ArrayList<>(list);
Collections.sort(listCopy, (o1, o2) -> {
if (o1.getType() == o2.getType()) return 0;
// keep requests at top
if (o1.getType() == NotificationType.REQUEST) return -1;
if (o2.getType() == NotificationType.REQUEST) return 1;
return 0;
if (o1.getType() == o2.getType()
&& o1.getType() == NotificationType.REQUEST
&& o2.getType() == NotificationType.REQUEST) return 0;
else if (o1.getType() == NotificationType.REQUEST) return -1;
else if (o2.getType() == NotificationType.REQUEST) return 1;
// timestamp
return o1.getTimestamp() > o2.getTimestamp() ? -1 : (o1.getTimestamp() == o2.getTimestamp() ? 0 : 1);
});
return listCopy;
}
public interface OnNotificationClickListener {
void onNotificationClick(final NotificationModel model);
void onProfileClick(final String username);
void onPreviewClick(final NotificationModel model);
}
}

View File

@ -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);
// }
// }

View File

@ -24,10 +24,14 @@ public final class FeedStoryViewHolder extends RecyclerView.ViewHolder {
if (listener == null) return;
listener.onFeedStoryClick(model, position);
});
binding.getRoot().setOnLongClickListener(v -> {
if (listener != null) listener.onFeedStoryLongClick(model, position);
return true;
});
final ProfileModel profileModel = model.getProfileModel();
binding.title.setText(profileModel.getUsername());
binding.title.setAlpha(model.getFullyRead() ? 0.5F : 1.0F);
binding.title.setAlpha(model.isFullyRead() ? 0.5F : 1.0F);
binding.icon.setImageURI(profileModel.getSdProfilePic());
binding.icon.setAlpha(model.getFullyRead() ? 0.5F : 1.0F);
binding.icon.setAlpha(model.isFullyRead() ? 0.5F : 1.0F);
}
}

View File

@ -1,5 +1,6 @@
package awais.instagrabber.adapters.viewholder;
import android.util.Log;
import android.view.View;
import androidx.recyclerview.widget.RecyclerView;

View File

@ -24,10 +24,6 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
public void bind(final NotificationModel model,
final OnNotificationClickListener notificationClickListener) {
if (model == null) return;
itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return;
notificationClickListener.onNotificationClick(model);
});
int text = -1;
CharSequence subtext = null;
switch (model.getType()) {
@ -52,20 +48,54 @@ public final class NotificationViewHolder extends RecyclerView.ViewHolder {
text = R.string.request_notif;
subtext = model.getText();
break;
case COMMENT_LIKE:
case TAGGED_COMMENT:
case RESPONDED_STORY:
subtext = model.getText();
break;
case AYML:
subtext = model.getPostId();
break;
}
binding.tvUsername.setText(model.getUsername());
binding.tvComment.setText(text);
binding.tvSubComment.setText(subtext, subtext instanceof Spannable ? TextView.BufferType.SPANNABLE : TextView.BufferType.NORMAL);
// binding.tvSubComment.setMentionClickListener(mentionClickListener);
if (model.getType() != NotificationType.REQUEST) {
binding.tvSubComment.setText(model.getType() == NotificationType.AYML ? model.getText() : subtext);
if (text == -1 && subtext != null) {
binding.tvComment.setText(subtext);
binding.tvComment.setVisibility(TextUtils.isEmpty(subtext) ? View.GONE : View.VISIBLE);
binding.tvSubComment.setVisibility(model.getType() == NotificationType.AYML ? View.VISIBLE : View.GONE);
}
else if (text != -1) {
binding.tvComment.setText(text);
binding.tvSubComment.setVisibility(subtext == null ? View.GONE : View.VISIBLE);
}
if (model.getType() != NotificationType.REQUEST && model.getType() != NotificationType.AYML) {
binding.tvDate.setText(model.getDateTime());
}
binding.tvUsername.setText(model.getUsername());
binding.ivProfilePic.setImageURI(model.getProfilePic());
if (TextUtils.isEmpty(model.getPreviewPic())) {
binding.ivProfilePic.setOnClickListener(v -> {
if (notificationClickListener == null) return;
notificationClickListener.onProfileClick(model.getUsername());
});
if (model.getType() == NotificationType.AYML) {
binding.ivPreviewPic.setVisibility(View.GONE);
}
else if (TextUtils.isEmpty(model.getPreviewPic())) {
binding.ivPreviewPic.setVisibility(View.INVISIBLE);
} else {
binding.ivPreviewPic.setVisibility(View.VISIBLE);
binding.ivPreviewPic.setImageURI(model.getPreviewPic());
binding.ivPreviewPic.setOnClickListener(v -> {
if (notificationClickListener == null) return;
notificationClickListener.onPreviewClick(model);
});
}
itemView.setOnClickListener(v -> {
if (notificationClickListener == null) return;
notificationClickListener.onNotificationClick(model);
});
}
}

View File

@ -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);
// }
// }
// }

View File

@ -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);
});
}
}

View File

@ -89,8 +89,7 @@ public final class ChildCommentViewHolder extends RecyclerView.ViewHolder {
public final void setLiked(final boolean liked) {
if (liked) {
// container.setBackgroundColor(0x40FF69B4);
return;
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
}
}
}

View File

@ -89,8 +89,7 @@ public final class ParentCommentViewHolder extends RecyclerView.ViewHolder {
public final void setLiked(final boolean liked) {
if (liked) {
// container.setBackgroundColor(0x40FF69B4);
return;
itemView.setBackgroundColor(itemView.getResources().getColor(R.color.comment_liked));
}
}
}

View File

@ -28,11 +28,12 @@ import static awais.instagrabber.utils.Utils.logCollector;
public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
private static final String TAG = "CommentsFetcher";
private final String shortCode;
private final String shortCode, endCursor;
private final FetchListener<List<CommentModel>> fetchListener;
public CommentsFetcher(final String shortCode, final FetchListener<List<CommentModel>> fetchListener) {
public CommentsFetcher(final String shortCode, final String endCursor, final FetchListener<List<CommentModel>> fetchListener) {
this.shortCode = shortCode;
this.endCursor = endCursor;
this.fetchListener = fetchListener;
}
@ -48,15 +49,17 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables={"comment_id":"18100041898085322","first":50,"after":""}
*/
final List<CommentModel> commentModels = getParentComments();
for (final CommentModel commentModel : commentModels) {
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
final int childCommentsLen = childCommentModels.size();
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
commentModel.setChildCommentModels(remoteChildComments);
lastChild.setPageCursor(false, null);
if (commentModels != null) {
for (final CommentModel commentModel : commentModels) {
final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) {
final int childCommentsLen = childCommentModels.size();
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) {
final List<CommentModel> remoteChildComments = getChildComments(commentModel.getId());
commentModel.setChildCommentModels(remoteChildComments);
lastChild.setPageCursor(false, null);
}
}
}
}
@ -76,11 +79,10 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
@NonNull
private synchronized List<CommentModel> getChildComments(final String commentId) {
final List<CommentModel> commentModels = new ArrayList<>();
String endCursor = "";
while (endCursor != null) {
String childEndCursor = "";
while (childEndCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" +
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
"{\"comment_id\":\"" + commentId + "\",\"first\":50,\"after\":\"" + childEndCursor + "\"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
@ -93,8 +95,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
.getJSONObject("edge_threaded_comments");
final JSONObject pageInfo = data.getJSONObject("page_info");
endCursor = pageInfo.getString("end_cursor");
if (TextUtils.isEmpty(endCursor)) endCursor = null;
childEndCursor = pageInfo.getString("end_cursor");
if (TextUtils.isEmpty(childEndCursor)) childEndCursor = null;
final JSONArray childComments = data.optJSONArray("edges");
if (childComments != null) {
@ -120,6 +122,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
false,
false,
false,
false,
false);
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
@ -142,6 +145,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
"getChildComments",
new Pair<>("commentModels.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
if (fetchListener != null) fetchListener.onFailure(e);
break;
}
}
@ -152,17 +156,14 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
@NonNull
private synchronized List<CommentModel> getParentComments() {
final List<CommentModel> commentModels = new ArrayList<>();
String endCursor = "";
while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" +
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor + "\"}";
try {
"{\"shortcode\":\"" + shortCode + "\",\"first\":50,\"after\":\"" + endCursor.replace("\"", "\\\"") + "\"}";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setUseCaches(false);
conn.connect();
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) return null;
else {
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data")
.getJSONObject("shortcode_media")
@ -170,8 +171,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
"edge_media_to_parent_comment");
final JSONObject pageInfo = parentComments.getJSONObject("page_info");
endCursor = pageInfo.optString("end_cursor");
if (TextUtils.isEmpty(endCursor)) endCursor = null;
final String foundEndCursor = pageInfo.optString("end_cursor");
final boolean hasNextPage = pageInfo.optBoolean("has_next_page", !TextUtils.isEmpty(foundEndCursor));
// final boolean containsToken = endCursor.contains("bifilter_token");
// if (!Utils.isEmpty(endCursor) && (containsToken || endCursor.contains("cached_comments_cursor"))) {
@ -209,6 +210,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
false,
false,
false,
false,
false);
final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
@ -219,6 +221,8 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
likedBy != null ? likedBy.optLong("count", 0) : 0,
comment.getBoolean("viewer_has_liked"),
profileModel);
if (i == 0 && !foundEndCursor.contains("tao_cursor"))
commentModel.setPageCursor(hasNextPage, TextUtils.isEmpty(foundEndCursor) ? null : foundEndCursor);
JSONObject tempJsonObject;
final JSONArray childCommentsArray;
final int childCommentsLen;
@ -227,13 +231,13 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
&& (childCommentsLen = childCommentsArray.length()) > 0) {
final String childEndCursor;
final boolean hasNextPage;
final boolean childHasNextPage;
if ((tempJsonObject = tempJsonObject.optJSONObject("page_info")) != null) {
childEndCursor = tempJsonObject.optString("end_cursor");
hasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor));
childHasNextPage = tempJsonObject.optBoolean("has_next_page", !TextUtils.isEmpty(childEndCursor));
} else {
childEndCursor = null;
hasNextPage = false;
childHasNextPage = false;
}
final List<CommentModel> childCommentModels = new ArrayList<>();
@ -257,6 +261,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
false,
false,
false,
false,
false);
tempJsonObject = childComment.optJSONObject("edge_liked_by");
@ -267,7 +272,7 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
childComment.getBoolean("viewer_has_liked"),
childProfileModel));
}
childCommentModels.get(childCommentsLen - 1).setPageCursor(hasNextPage, childEndCursor);
childCommentModels.get(childCommentsLen - 1).setPageCursor(childHasNextPage, childEndCursor);
commentModel.setChildCommentModels(childCommentModels);
}
commentModels.add(commentModel);
@ -280,9 +285,9 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentMod
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments",
new Pair<>("commentModelsList.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
break;
if (fetchListener != null) fetchListener.onFailure(e);
return null;
}
}
return commentModels;
}
}

View File

@ -1,34 +1,61 @@
package awais.instagrabber.asyncs;
//import android.os.Handler;
//import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.webservices.FeedService;
import awais.instagrabber.webservices.ServiceCallback;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedPostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "FeedPostFetchService";
private final FeedService feedService;
private String nextCursor;
// private final Handler handler;
private boolean hasNextPage;
// private static final int DELAY_MILLIS = 500;
public FeedPostFetchService() {
feedService = FeedService.getInstance();
// handler = new Handler();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
feedService.fetch(25, nextCursor, new ServiceCallback<PostsFetchResponse>() {
final List<FeedModel> feedModels = new ArrayList<>();
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
feedModels.clear();
feedService.fetch(csrfToken, nextCursor, new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
if (result == null && feedModels.size() > 0) {
fetchListener.onResult(feedModels);
return;
}
else if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage();
feedModels.addAll(result.getFeedModels());
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
if (feedModels.size() < 15 && hasNextPage) {
// handler.postDelayed(() -> {
feedService.fetch(csrfToken, nextCursor, this);
// }, DELAY_MILLIS);
}
else {
fetchListener.onResult(feedModels);
}
}
}

View File

@ -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);
}
}

View File

@ -16,12 +16,21 @@ import awais.instagrabber.utils.TextUtils;
public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsyncTask.NotificationCounts> {
private static final String TAG = "GetActivityAsyncTask";
private OnTaskCompleteListener onTaskCompleteListener;
private final OnTaskCompleteListener onTaskCompleteListener;
public GetActivityAsyncTask(final OnTaskCompleteListener onTaskCompleteListener) {
this.onTaskCompleteListener = onTaskCompleteListener;
}
/*
This needs to be redone to fetch i inbox instead
Within inbox, data is (body JSON => counts)
Then we have these counts:
new_posts, activity_feed_dot_badge, relationships, campaign_notification
usertags, likes, comment_likes, shopping_notification, comments
photos_of_you (not sure about difference to usertags), requests
*/
protected NotificationCounts doInBackground(final String... cookiesArray) {
if (cookiesArray == null) return null;
final String cookie = cookiesArray[0];
@ -70,11 +79,11 @@ public class GetActivityAsyncTask extends AsyncTask<String, Void, GetActivityAsy
}
public static class NotificationCounts {
private int relationshipsCount;
private int userTagsCount;
private int commentsCount;
private int commentLikesCount;
private int likesCount;
private final int relationshipsCount;
private final int userTagsCount;
private final int commentsCount;
private final int commentLikesCount;
private final int likesCount;
public NotificationCounts(final int relationshipsCount,
final int userTagsCount,

View File

@ -89,6 +89,7 @@ public final class HashtagFetcher extends AsyncTask<Void, Void, HashtagModel> {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
if (fetchListener != null) fetchListener.onFailure(e);
}
return result;

View File

@ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.TagsService;
import awais.instagrabber.webservices.TagsService.TagPostsFetchResponse;
public class HashtagPostFetchService implements PostFetcher.PostFetchService {
private final TagsService tagsService;
private final GraphQLService graphQLService;
private final HashtagModel hashtagModel;
private String nextMaxId;
private boolean moreAvailable;
@ -20,19 +22,20 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
public HashtagPostFetchService(final HashtagModel hashtagModel, final boolean isLoggedIn) {
this.hashtagModel = hashtagModel;
this.isLoggedIn = isLoggedIn;
tagsService = TagsService.getInstance();
tagsService = isLoggedIn ? TagsService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback cb = new ServiceCallback<TagPostsFetchResponse>() {
final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final TagPostsFetchResponse result) {
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
fetchListener.onResult(result.getFeedModels());
}
}
@ -45,7 +48,7 @@ public class HashtagPostFetchService implements PostFetcher.PostFetchService {
}
};
if (isLoggedIn) tagsService.fetchPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else tagsService.fetchGraphQLPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
else graphQLService.fetchHashtagPosts(hashtagModel.getName().toLowerCase(), nextMaxId, cb);
}
@Override

View File

@ -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);
}
}

View File

@ -6,12 +6,14 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.LocationModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.LocationService.LocationPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback;
public class LocationPostFetchService implements PostFetcher.PostFetchService {
private final LocationService locationService;
private final GraphQLService graphQLService;
private final LocationModel locationModel;
private String nextMaxId;
private boolean moreAvailable;
@ -20,19 +22,20 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
public LocationPostFetchService(final LocationModel locationModel, final boolean isLoggedIn) {
this.locationModel = locationModel;
this.isLoggedIn = isLoggedIn;
locationService = LocationService.getInstance();
locationService = isLoggedIn ? LocationService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback cb = new ServiceCallback<LocationPostsFetchResponse>() {
final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final LocationPostsFetchResponse result) {
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
fetchListener.onResult(result.getFeedModels());
}
}
@ -45,7 +48,7 @@ public class LocationPostFetchService implements PostFetcher.PostFetchService {
}
};
if (isLoggedIn) locationService.fetchPosts(locationModel.getId(), nextMaxId, cb);
else locationService.fetchGraphQLPosts(locationModel.getId(), nextMaxId, cb);
else graphQLService.fetchLocationPosts(locationModel.getId(), nextMaxId, cb);
}
@Override

View File

@ -3,21 +3,14 @@ package awais.instagrabber.asyncs;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.LocaleUtils;
import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.webservices.NewsService;
import awais.instagrabber.webservices.ServiceCallback;
import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector;
@ -26,89 +19,48 @@ public final class NotificationsFetcher extends AsyncTask<Void, Void, List<Notif
private static final String TAG = "NotificationsFetcher";
private final FetchListener<List<NotificationModel>> fetchListener;
private final NewsService newsService;
private final boolean markAsSeen;
private boolean fetchedWeb = false;
public NotificationsFetcher(final FetchListener<List<NotificationModel>> fetchListener) {
public NotificationsFetcher(final boolean markAsSeen,
final FetchListener<List<NotificationModel>> fetchListener) {
this.markAsSeen = markAsSeen;
this.fetchListener = fetchListener;
newsService = NewsService.getInstance();
}
@Override
protected List<NotificationModel> doInBackground(final Void... voids) {
List<NotificationModel> result = new ArrayList<>();
final String url = "https://www.instagram.com/accounts/activity/?__a=1";
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.setRequestProperty("Accept-Language", LocaleUtils.getCurrentLocale().getLanguage() + ",en-US;q=0.8");
conn.connect();
List<NotificationModel> notificationModels = new ArrayList<>();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
final JSONObject page = new JSONObject(NetworkUtils.readFromConnection(conn))
.getJSONObject("graphql")
.getJSONObject("user");
final JSONObject ewaf = page.getJSONObject("activity_feed")
.optJSONObject("edge_web_activity_feed");
final JSONObject efr = page.optJSONObject("edge_follow_requests");
JSONObject data;
JSONArray media;
if (ewaf != null
&& (media = ewaf.optJSONArray("edges")) != null
&& media.length() > 0
&& media.optJSONObject(0).optJSONObject("node") != null) {
for (int i = 0; i < media.length(); ++i) {
data = media.optJSONObject(i).optJSONObject("node");
if (data == null) continue;
final String type = data.getString("__typename");
final NotificationType notificationType = NotificationType.valueOfType(type);
if (notificationType == null) continue;
final JSONObject user = data.getJSONObject("user");
result.add(new NotificationModel(
data.getString(Constants.EXTRAS_ID),
data.optString("text"), // comments or mentions
data.getLong("timestamp"),
user.getString("id"),
user.getString("username"),
user.getString("profile_pic_url"),
!data.isNull("media") ? data.getJSONObject("media").getString("shortcode") : null,
!data.isNull("media") ? data.getJSONObject("media").getString("thumbnail_src") : null, notificationType));
}
newsService.fetchAppInbox(markAsSeen, new ServiceCallback<List<NotificationModel>>() {
@Override
public void onSuccess(final List<NotificationModel> result) {
if (result == null) return;
notificationModels.addAll(result);
if (fetchedWeb) {
fetchListener.onResult(notificationModels);
}
if (efr != null
&& (media = efr.optJSONArray("edges")) != null
&& media.length() > 0
&& media.optJSONObject(0).optJSONObject("node") != null) {
for (int i = 0; i < media.length(); ++i) {
data = media.optJSONObject(i).optJSONObject("node");
if (data == null) continue;
result.add(new NotificationModel(
data.getString(Constants.EXTRAS_ID),
data.optString("full_name"),
0L,
data.getString(Constants.EXTRAS_ID),
data.getString("username"),
data.getString("profile_pic_url"),
null,
null, NotificationType.REQUEST));
}
else {
fetchedWeb = true;
newsService.fetchWebInbox(markAsSeen, this);
}
}
conn.disconnect();
} catch (final Exception e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_NOTIFICATION_FETCHER, "doInBackground");
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
return result;
@Override
public void onFailure(final Throwable t) {
// Log.e(TAG, "onFailure: ", t);
if (fetchListener != null) {
fetchListener.onFailure(t);
}
}
});
return notificationModels;
}
@Override
protected void onPreExecute() {
if (fetchListener != null) fetchListener.doBefore();
}
@Override
protected void onPostExecute(final List<NotificationModel> result) {
if (fetchListener != null) fetchListener.onResult(result);
}
}

View File

@ -65,6 +65,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, FeedModel> {
owner.optInt("edge_followed_by"),
-1,
owner.optBoolean("followed_by_viewer"),
false,
owner.optBoolean("restricted_by_viewer"),
owner.optBoolean("blocked_by_viewer"),
owner.optBoolean("requested_by_viewer")

View File

@ -72,6 +72,7 @@ public final class ProfileFetcher extends AsyncTask<Void, Void, ProfileModel> {
user.getJSONObject("edge_followed_by").getLong("count"),
user.getJSONObject("edge_follow").getLong("count"),
user.optBoolean("followed_by_viewer"),
user.optBoolean("follows_viewer"),
user.optBoolean("restricted_by_viewer"),
user.optBoolean("blocked_by_viewer"),
user.optBoolean("requested_by_viewer"));

View File

@ -40,51 +40,34 @@ public final class ProfilePictureFetcher extends AsyncTask<Void, Void, String> {
protected String doInBackground(final Void... voids) {
String out = null;
if (isHashtag) out = picUrl;
else try {
final HttpURLConnection conn =
(HttpURLConnection) new URL("https://i.instagram.com/api/v1/users/"+userId+"/info/").openConnection();
conn.setUseCaches(false);
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", Constants.USER_AGENT);
else if (Utils.settingsHelper.getBoolean(Constants.INSTADP)) try {
final HttpURLConnection backup =
(HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection();
backup.setUseCaches(false);
backup.setRequestMethod("GET");
backup.setRequestProperty("User-Agent", Constants.A_USER_AGENT);
final String result = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(conn) : null;
conn.disconnect();
final String instadp = backup.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(backup) : null;
backup.disconnect();
if (!TextUtils.isEmpty(result)) {
JSONObject data = new JSONObject(result).getJSONObject("user");
if (data.has("hd_profile_pic_url_info"))
out = data.getJSONObject("hd_profile_pic_url_info").optString("url");
}
if (!TextUtils.isEmpty(instadp)) {
final Document doc = Jsoup.parse(instadp);
boolean fallback = false;
if (TextUtils.isEmpty(out) && Utils.settingsHelper.getBoolean(Constants.INSTADP)) {
final HttpURLConnection backup =
(HttpURLConnection) new URL("https://instadp.com/fullsize/" + userName).openConnection();
backup.setUseCaches(false);
backup.setRequestMethod("GET");
backup.setRequestProperty("User-Agent", Constants.A_USER_AGENT);
final int imgIndex = instadp.indexOf("preloadImg('"), lastIndex;
final String instadp = backup.getResponseCode() == HttpURLConnection.HTTP_OK ? NetworkUtils.readFromConnection(backup) : null;
backup.disconnect();
if (!TextUtils.isEmpty(instadp)) {
final Document doc = Jsoup.parse(instadp);
boolean fallback = false;
final int imgIndex = instadp.indexOf("preloadImg('"), lastIndex;
Element element = doc.selectFirst(".instadp");
if (element != null && (element = element.selectFirst(".picture")) != null)
out = element.attr("src");
else if ((element = doc.selectFirst(".download-btn")) != null)
out = element.attr("href");
else if (imgIndex != -1 && (lastIndex = instadp.indexOf("')", imgIndex)) != -1)
out = instadp.substring(imgIndex + 12, lastIndex);
else {
final Elements imgs = doc.getElementsByTag("img");
for (final Element img : imgs) {
final String imgStr = img.toString();
if (imgStr.contains("cdninstagram.com")) out = img.attr("src");
}
Element element = doc.selectFirst(".instadp");
if (element != null && (element = element.selectFirst(".picture")) != null)
out = element.attr("src");
else if ((element = doc.selectFirst(".download-btn")) != null)
out = element.attr("href");
else if (imgIndex != -1 && (lastIndex = instadp.indexOf("')", imgIndex)) != -1)
out = instadp.substring(imgIndex + 12, lastIndex);
else {
final Elements imgs = doc.getElementsByTag("img");
for (final Element img : imgs) {
final String imgStr = img.toString();
if (imgStr.contains("cdninstagram.com")) out = img.attr("src");
}
}
}

View File

@ -7,29 +7,34 @@ import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
public class ProfilePostFetchService implements PostFetcher.PostFetchService {
private static final String TAG = "ProfilePostFetchService";
private final ProfileService profileService;
private final GraphQLService graphQLService;
private final ProfileModel profileModel;
private String nextCursor;
private boolean hasNextPage;
private final boolean isLoggedIn;
private String nextMaxId;
private boolean moreAvailable;
public ProfilePostFetchService(final ProfileModel profileModel) {
public ProfilePostFetchService(final ProfileModel profileModel, final boolean isLoggedIn) {
this.profileModel = profileModel;
profileService = ProfileService.getInstance();
this.isLoggedIn = isLoggedIn;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
profileService = isLoggedIn ? ProfileService.getInstance() : null;
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
profileService.fetchPosts(profileModel, 30, nextCursor, new ServiceCallback<PostsFetchResponse>() {
final ServiceCallback cb = new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextCursor = result.getNextCursor();
hasNextPage = result.hasNextPage();
nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getFeedModels());
}
@ -42,16 +47,18 @@ public class ProfilePostFetchService implements PostFetcher.PostFetchService {
fetchListener.onFailure(t);
}
}
});
};
if (isLoggedIn) profileService.fetchPosts(profileModel.getId(), nextMaxId, cb);
else graphQLService.fetchProfilePosts(profileModel.getId(), 30, nextMaxId, cb);
}
@Override
public void reset() {
nextCursor = null;
nextMaxId = null;
}
@Override
public boolean hasNextPage() {
return hasNextPage;
return moreAvailable;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -6,34 +6,39 @@ import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ProfileService.SavedPostsFetchResponse;
import awais.instagrabber.webservices.ServiceCallback;
public class SavedPostFetchService implements PostFetcher.PostFetchService {
private final ProfileService profileService;
private final GraphQLService graphQLService;
private final String profileId;
private final PostItemType type;
private final boolean isLoggedIn;
private String nextMaxId;
private boolean moreAvailable;
public SavedPostFetchService(final String profileId, final PostItemType type) {
public SavedPostFetchService(final String profileId, final PostItemType type, final boolean isLoggedIn) {
this.profileId = profileId;
this.type = type;
profileService = ProfileService.getInstance();
this.isLoggedIn = isLoggedIn;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
profileService = isLoggedIn ? ProfileService.getInstance() : null;
}
@Override
public void fetch(final FetchListener<List<FeedModel>> fetchListener) {
final ServiceCallback<SavedPostsFetchResponse> callback = new ServiceCallback<SavedPostsFetchResponse>() {
final ServiceCallback<PostsFetchResponse> callback = new ServiceCallback<PostsFetchResponse>() {
@Override
public void onSuccess(final SavedPostsFetchResponse result) {
public void onSuccess(final PostsFetchResponse result) {
if (result == null) return;
nextMaxId = result.getNextMaxId();
moreAvailable = result.isMoreAvailable();
nextMaxId = result.getNextCursor();
moreAvailable = result.hasNextPage();
if (fetchListener != null) {
fetchListener.onResult(result.getItems());
fetchListener.onResult(result.getFeedModels());
}
}
@ -50,7 +55,8 @@ public class SavedPostFetchService implements PostFetcher.PostFetchService {
profileService.fetchLiked(nextMaxId, callback);
break;
case TAGGED:
profileService.fetchTagged(profileId, nextMaxId, callback);
if (isLoggedIn) profileService.fetchTagged(profileId, nextMaxId, callback);
else graphQLService.fetchTaggedPosts(profileId, 30, nextMaxId, callback);
break;
case SAVED:
default:

View File

@ -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);
}
}

View File

@ -122,4 +122,131 @@
// public interface OnBroadcastCompleteListener {
// void onTaskComplete(DirectThreadBroadcastResponse response);
// }
//
// public enum ItemType {
// TEXT("text"),
// REACTION("reaction"),
// REELSHARE("reel_share"),
// IMAGE("configure_photo");
//
// private final String value;
//
// ItemType(final String value) {
// this.value = value;
// }
//
// public String getValue() {
// return value;
// }
// }
//
// public static abstract class BroadcastOptions {
// private final ItemType itemType;
//
// public BroadcastOptions(final ItemType itemType) {
// this.itemType = itemType;
// }
//
// public ItemType getItemType() {
// return itemType;
// }
//
// abstract Map<String, String> getFormMap();
// }
//
// public static class TextBroadcastOptions extends BroadcastOptions {
// private final String text;
//
// public TextBroadcastOptions(String text) throws UnsupportedEncodingException {
// super(ItemType.TEXT);
// this.text = URLEncoder.encode(text, "UTF-8")
// .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'").replaceAll("%22", "\\\"")
// .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~").replaceAll("%0A", "\n");
// }
//
// @Override
// Map<String, String> getFormMap() {
// return Collections.singletonMap("text", text);
// }
// }
//
// public static class ReactionBroadcastOptions extends BroadcastOptions {
// private final String itemId;
// private final boolean delete;
//
// public ReactionBroadcastOptions(String itemId, boolean delete) {
// super(ItemType.REACTION);
// this.itemId = itemId;
// this.delete = delete;
// }
//
// @Override
// Map<String, String> getFormMap() {
// final Map<String, String> form = new HashMap<>();
// form.put("item_id", itemId);
// form.put("reaction_status", delete ? "deleted" : "created");
// form.put("reaction_type", "like");
// return form;
// }
// }
//
// public static class StoryReplyBroadcastOptions extends BroadcastOptions {
// private final String text, mediaId, reelId;
//
// public StoryReplyBroadcastOptions(String text, String mediaId, String reelId) throws UnsupportedEncodingException {
// super(ItemType.REELSHARE);
// this.text = URLEncoder.encode(text, "UTF-8")
// .replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'")
// .replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~").replaceAll("%0A", "\n");
// this.mediaId = mediaId;
// this.reelId = reelId; // or user id, usually same
// }
//
// @Override
// Map<String, String> getFormMap() {
// final Map<String, String> form = new HashMap<>();
// form.put("text", text);
// form.put("media_id", mediaId);
// form.put("reel_id", reelId);
// form.put("entry", "reel");
// return form;
// }
// }
//
// public static class ImageBroadcastOptions extends BroadcastOptions {
// final boolean allowFullAspectRatio;
// final String uploadId;
//
// public ImageBroadcastOptions(final boolean allowFullAspectRatio, final String uploadId) {
// super(ItemType.IMAGE);
// this.allowFullAspectRatio = allowFullAspectRatio;
// this.uploadId = uploadId;
// }
//
// @Override
// Map<String, String> getFormMap() {
// final Map<String, String> form = new HashMap<>();
// form.put("allow_full_aspect_ratio", String.valueOf(allowFullAspectRatio));
// form.put("upload_id", uploadId);
// return form;
// }
// }
//
// public static class DirectThreadBroadcastResponse {
// private final int responseCode;
// private final JSONObject response;
//
// public DirectThreadBroadcastResponse(int responseCode, JSONObject response) {
// this.responseCode = responseCode;
// this.response = response;
// }
//
// public int getResponseCode() {
// return responseCode;
// }
//
// public JSONObject getResponse() {
// return response;
// }
// }
// }

View File

@ -183,11 +183,13 @@ public class PostsRecyclerView extends RecyclerView {
private void initSelf() {
feedViewModel = new ViewModelProvider(viewModelStoreOwner).get(FeedViewModel.class);
feedViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
if (!shouldScrollToTop) return;
smoothScrollToPosition(0);
shouldScrollToTop = false;
}));
feedViewModel.getList().observe(lifeCycleOwner, list -> {
if (list.size() > 0) feedAdapter.submitList(list, () -> {
if (!shouldScrollToTop) return;
smoothScrollToPosition(0);
shouldScrollToTop = false;
});
});
postFetcher = new PostFetcher(postFetchService, fetchListener);
if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration);

View File

@ -161,7 +161,7 @@ public abstract class AppDatabase extends RoomDatabase {
final FavoriteType type = favoriteTypeQueryPair.first;
final String query = favoriteTypeQueryPair.second;
oldModels.add(new Favorite(
-1,
0,
query,
type,
queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display"))

View File

@ -21,7 +21,7 @@ public interface FavoriteDao {
@Query("SELECT * FROM favorites WHERE query_text = :query and type = :type")
Favorite findFavoriteByQueryAndType(String query, FavoriteType type);
@Insert(onConflict = OnConflictStrategy.REPLACE)
@Insert
List<Long> insertFavorites(Favorite... favorites);
@Update

View File

@ -9,6 +9,7 @@ import android.graphics.drawable.ColorDrawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -19,6 +20,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.BaseControllerListener;
@ -30,9 +32,19 @@ import java.io.File;
import awais.instagrabber.R;
import awais.instagrabber.asyncs.ProfilePictureFetcher;
import awais.instagrabber.databinding.DialogProfilepicBinding;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.repositories.responses.UserInfo;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.webservices.ProfileService;
import awais.instagrabber.webservices.ServiceCallback;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfilePicDialogFragment extends DialogFragment {
private static final String TAG = "ProfilePicDlgFragment";
@ -41,9 +53,15 @@ public class ProfilePicDialogFragment extends DialogFragment {
private final String name;
private final String fallbackUrl;
private boolean isLoggedIn;
private DialogProfilepicBinding binding;
private String url;
private final FetchListener<String> fetchListener = profileUrl -> {
url = profileUrl;
setupPhoto();
};
public ProfilePicDialogFragment(final String id, final String name, final String fallbackUrl) {
this.id = id;
this.name = name;
@ -55,6 +73,8 @@ public class ProfilePicDialogFragment extends DialogFragment {
final ViewGroup container,
final Bundle savedInstanceState) {
binding = DialogProfilepicBinding.inflate(inflater, container, false);
final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
return binding.getRoot();
}
@ -83,7 +103,7 @@ public class ProfilePicDialogFragment extends DialogFragment {
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
fetchPhoto();
fetchAvatar();
}
private void init() {
@ -106,37 +126,55 @@ public class ProfilePicDialogFragment extends DialogFragment {
}
}
private void fetchPhoto() {
final FetchListener<String> fetchListener = profileUrl -> {
url = profileUrl;
if (TextUtils.isEmpty(url)) {
url = fallbackUrl;
}
final DraweeController controller = Fresco
.newDraweeControllerBuilder()
.setUri(url)
.setOldController(binding.imageViewer.getController())
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
super.onFailure(id, throwable);
binding.download.setVisibility(View.GONE);
binding.progressView.setVisibility(View.GONE);
}
private void fetchAvatar() {
if (isLoggedIn) {
final ProfileService profileService = ProfileService.getInstance();
profileService.getUserInfo(id, new ServiceCallback<UserInfo>() {
@Override
public void onSuccess(final UserInfo result) {
if (result != null) {
fetchListener.onResult(result.getHDProfilePicUrl());
}
}
@Override
public void onFinalImageSet(final String id,
final ImageInfo imageInfo,
final Animatable animatable) {
super.onFinalImageSet(id, imageInfo, animatable);
binding.download.setVisibility(View.VISIBLE);
binding.progressView.setVisibility(View.GONE);
}
})
.build();
binding.imageViewer.setController(controller);
};
new ProfilePictureFetcher(name, id, fetchListener, url, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@Override
public void onFailure(final Throwable t) {
final Context context = getContext();
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
getDialog().dismiss();
}
});
}
else new ProfilePictureFetcher(name, id, fetchListener, fallbackUrl, false).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void setupPhoto() {
if (TextUtils.isEmpty(url)) {
url = fallbackUrl;
}
final DraweeController controller = Fresco
.newDraweeControllerBuilder()
.setUri(url)
.setOldController(binding.imageViewer.getController())
.setControllerListener(new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
super.onFailure(id, throwable);
binding.download.setVisibility(View.GONE);
binding.progressView.setVisibility(View.GONE);
}
@Override
public void onFinalImageSet(final String id,
final ImageInfo imageInfo,
final Animatable animatable) {
super.onFinalImageSet(id, imageInfo, animatable);
binding.download.setVisibility(View.VISIBLE);
binding.progressView.setVisibility(View.GONE);
}
})
.build();
binding.imageViewer.setController(controller);
}
private void downloadProfilePicture() {

View File

@ -25,6 +25,7 @@ import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
@ -32,11 +33,18 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.asyncs.CommentsFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentCommentsBinding;
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants;
@ -56,17 +64,48 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
private CommentsAdapter commentsAdapter;
private FragmentCommentsBinding binding;
private String shortCode;
private String userId;
private LinearLayoutManager layoutManager;
private RecyclerLazyLoader lazyLoader;
private String shortCode, userId, endCursor = null;
private Resources resources;
private InputMethodManager imm;
private AppCompatActivity fragmentActivity;
private LinearLayoutCompat root;
private boolean shouldRefresh = true;
private boolean shouldRefresh = true, hasNextPage = false;
private MediaService mediaService;
private String postId;
private AsyncTask<Void, Void, List<CommentModel>> currentlyRunning;
private CommentsViewModel commentsViewModel;
private final FetchListener<List<CommentModel>> fetchListener = new FetchListener<List<CommentModel>>() {
@Override
public void doBefore() {
binding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final List<CommentModel> commentModels) {
if (commentModels != null && commentModels.size() > 0) {
endCursor = commentModels.get(0).getEndCursor();
hasNextPage = commentModels.get(0).hasNextPage();
List<CommentModel> list = commentsViewModel.getList().getValue();
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
// final int oldSize = list != null ? list.size() : 0;
list.addAll(commentModels);
commentsViewModel.getList().postValue(list);
}
binding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor();
}
@Override
public void onFailure(Throwable t) {
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
binding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor();
}
};
private final CommentsAdapter.CommentCallback commentCallback = new CommentsAdapter.CommentCallback() {
@Override
public void onClick(final CommentModel comment) {
@ -181,11 +220,11 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
@Override
public void onRefresh() {
binding.swipeRefreshLayout.setRefreshing(true);
new CommentsFetcher(shortCode, commentModels -> {
commentsViewModel.getList().postValue(commentModels);
binding.swipeRefreshLayout.setRefreshing(false);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
lazyLoader.resetState();
commentsViewModel.getList().postValue(Collections.emptyList());
stopCurrentExecutor();
currentlyRunning = new CommentsFetcher(shortCode, "", fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void init() {
@ -198,7 +237,8 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
binding.swipeRefreshLayout.setOnRefreshListener(this);
binding.swipeRefreshLayout.setRefreshing(true);
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
binding.rvComments.setLayoutManager(new LinearLayoutManager(getContext()));
layoutManager = new LinearLayoutManager(getContext());
binding.rvComments.setLayoutManager(layoutManager);
commentsAdapter = new CommentsAdapter(commentCallback);
binding.rvComments.setAdapter(commentsAdapter);
commentsViewModel.getList().observe(getViewLifecycleOwner(), commentsAdapter::submitList);
@ -226,6 +266,13 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
});
binding.commentField.setEndIconOnClickListener(newCommentListener);
}
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (hasNextPage && !TextUtils.isEmpty(endCursor))
currentlyRunning = new CommentsFetcher(shortCode, endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
binding.rvComments.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
onRefresh();
}
@ -249,30 +296,29 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
&& (userIdFromCookie.equals(commentModel.getProfileModel().getId()) || userIdFromCookie.equals(userId))) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
// resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_see_likers),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
resources.getString(R.string.comment_viewer_translate_comment),
resources.getString(R.string.comment_viewer_delete_comment)
};
} else if (!TextUtils.isEmpty(cookie)) {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
// resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_see_likers),
resources.getString(R.string.comment_viewer_reply_comment),
commentModel.getLiked() ? resources.getString(R.string.comment_viewer_unlike_comment)
: resources.getString(R.string.comment_viewer_like_comment),
resources.getString(R.string.comment_viewer_translate_comment)
};
} else {
commentDialogList = new String[]{
resources.getString(R.string.open_profile),
resources.getString(R.string.view_pfp),
// resources.getString(R.string.comment_viewer_copy_user),
resources.getString(R.string.comment_viewer_copy_comment)
resources.getString(R.string.comment_viewer_copy_comment),
resources.getString(R.string.comment_viewer_see_likers)
};
}
final Context context = getContext();
@ -284,25 +330,20 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
case 0: // open profile
openProfile("@" + profileModel.getUsername());
break;
case 1: // view profile pic
final FragmentManager fragmentManager = getParentFragmentManager();
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(),
profileModel.getUsername(),
profileModel.getHdProfilePic());
final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "profilePicDialog")
.commit();
break;
// case 2: // copy username
// Utils.copyText(context, profileModel.getUsername());
// break;
case 2: // copy comment
case 1: // copy comment
Utils.copyText(context, "@" + profileModel.getUsername() + ": " + commentModel.getText());
break;
case 2: // see comment likers, this is surprisingly available to anons
final NavController navController = getNavController();
if (navController != null) {
final Bundle bundle = new Bundle();
bundle.putString("postId", commentModel.getId());
bundle.putBoolean("isComment", true);
navController.navigate(R.id.action_global_likesViewerFragment, bundle);
}
else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
break;
case 3: // reply to comment
// final View focus = binding.rvComments.findViewWithTag(commentModel);
// focus.setBackgroundColor(0x80888888);
commentsAdapter.setSelected(commentModel);
String mention = "@" + profileModel.getUsername() + " ";
binding.commentText.setText(mention);
@ -326,7 +367,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
commentsAdapter.setLiked(commentModel, true);
}
@Override
@ -344,7 +385,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
onRefresh();
commentsAdapter.setLiked(commentModel, false);
}
@Override
@ -354,7 +395,29 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
}
});
break;
case 5: // delete comment
case 5: // translate comment
mediaService.translate(commentModel.getId(), "2", new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
if (TextUtils.isEmpty(result)) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
new AlertDialog.Builder(context)
.setTitle(username)
.setMessage(result)
.setPositiveButton(R.string.ok, null)
.show();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error translating comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
case 6: // delete comment
final String userId = CookieUtils.getUserIdFromCookie(cookie);
if (userId == null) return;
mediaService.deleteComment(
@ -389,4 +452,25 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment impl
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
}
@Nullable
private NavController getNavController() {
NavController navController = null;
try {
navController = NavHostFragment.findNavController(this);
} catch (IllegalStateException e) {
Log.e(TAG, "navigateToProfile", e);
}
return navController;
}
}

View File

@ -81,7 +81,6 @@ public class FavoritesFragment extends Fragment {
@Override
public void onSuccess(final List<Favorite> favorites) {
favoritesViewModel.getList().postValue(favorites);
fetchMissingInfo(favorites);
}
@Override
@ -156,109 +155,4 @@ public class FavoritesFragment extends Fragment {
binding.favoriteList.setAdapter(adapter);
}
private void fetchMissingInfo(final List<Favorite> allFavorites) {
final Runnable runnable = () -> {
final List<Favorite> updatedList = new ArrayList<>(allFavorites);
// cyclic barrier is to make the async calls synchronous
final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
// Log.d(TAG, "fetchMissingInfo: barrier action");
favoritesViewModel.getList().postValue(new ArrayList<>(updatedList));
});
try {
for (final Favorite model : allFavorites) {
cyclicBarrier.reset();
// if the model has missing pic or display name (for user and location), fetch those details
switch (model.getType()) {
case LOCATION:
if (TextUtils.isEmpty(model.getDisplayName())
|| TextUtils.isEmpty(model.getPicUrl())) {
new LocationFetcher(model.getQuery(), result -> {
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final Favorite updated = new Favorite(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
updatedList.add(i, updated);
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
@Override
public void onDataNotAvailable() {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
});
}).execute();
cyclicBarrier.await();
}
break;
case USER:
if (TextUtils.isEmpty(model.getDisplayName())
|| TextUtils.isEmpty(model.getPicUrl())) {
new ProfileFetcher(model.getQuery(), result -> {
if (result == null) return;
final int i = updatedList.indexOf(model);
updatedList.remove(i);
final Favorite updated = new Favorite(
model.getId(),
model.getQuery(),
model.getType(),
result.getName(),
result.getSdProfilePic(),
model.getDateAdded()
);
favoriteRepository.insertOrUpdateFavorite(updated, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
@Override
public void onDataNotAvailable() {
try {
cyclicBarrier.await();
} catch (BrokenBarrierException | InterruptedException e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
}
});
updatedList.add(i, updated);
}).execute();
cyclicBarrier.await();
}
break;
case HASHTAG:
default:
// hashtags don't require displayName or pic
// updatedList.add(model);
}
}
} catch (Exception e) {
Log.e(TAG, "fetchMissingInfo: ", e);
}
favoritesViewModel.getList().postValue(updatedList);
};
new Thread(runnable).start();
}
}

View File

@ -20,29 +20,22 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Arrays;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.FollowAdapter;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentFollowersViewerBinding;
import awais.instagrabber.models.FollowModel;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
import awais.instagrabber.repositories.responses.FriendshipRepoListFetchResponse;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.ServiceCallback;
import awaisomereport.LogCollector;
import thoughtbot.expandableadapter.ExpandableGroup;
import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class FollowViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "FollowViewerFragment";
@ -51,9 +44,11 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
private final ArrayList<FollowModel> followersModels = new ArrayList<>();
private final ArrayList<FollowModel> allFollowing = new ArrayList<>();
private boolean isFollowersList, isCompare = false, loading = false;
private String profileId, username, namePost, type;
private boolean moreAvailable = true, isFollowersList, isCompare = false, loading = false, shouldRefresh = true;
private String profileId, username, namePost, type, endCursor;
private Resources resources;
private LinearLayoutManager layoutManager;
private RecyclerLazyLoader lazyLoader;
private FollowModel model;
private FollowAdapter adapter;
private View.OnClickListener clickListener;
@ -61,9 +56,61 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
private AsyncTask<Void, Void, FollowModel[]> currentlyExecuting;
private SwipeRefreshLayout root;
private FriendshipService friendshipService;
private boolean shouldRefresh = true;
private AppCompatActivity fragmentActivity;
final ServiceCallback<FriendshipRepoListFetchResponse> followingFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@Override
public void onSuccess(final FriendshipRepoListFetchResponse result) {
if (result != null) {
followingModels.addAll(result.getItems());
if (!isFollowersList) followModels.addAll(result.getItems());
if (result.isMoreAvailable()) {
endCursor = result.getNextMaxId();
friendshipService.getList(false, profileId, endCursor, this);
} else if (followersModels.size() == 0) {
if (!isFollowersList) moreAvailable = false;
friendshipService.getList(true, profileId, null, followingFetchCb);
} else {
if (!isFollowersList) moreAvailable = false;
showCompare();
}
} else binding.swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error fetching list (double, following)", t);
}
};
final ServiceCallback<FriendshipRepoListFetchResponse> followersFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@Override
public void onSuccess(final FriendshipRepoListFetchResponse result) {
if (result != null) {
followersModels.addAll(result.getItems());
if (isFollowersList) followModels.addAll(result.getItems());
if (result.isMoreAvailable()) {
endCursor = result.getNextMaxId();
friendshipService.getList(true, profileId, endCursor, this);
} else if (followingModels.size() == 0) {
if (isFollowersList) moreAvailable = false;
friendshipService.getList(false, profileId, null, followingFetchCb);
} else {
if (isFollowersList) moreAvailable = false;
showCompare();
}
}
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error fetching list (double, follower)", t);
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -141,13 +188,13 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
public void onRefresh() {
if (isCompare) listCompare();
else listFollows();
endCursor = null;
lazyLoader.resetState();
}
private void listFollows() {
loading = true;
type = resources.getString(isFollowersList ? R.string.followers_type_followers : R.string.followers_type_following);
setSubtitle(type);
followModels.clear();
final ServiceCallback<FriendshipRepoListFetchResponse> cb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@Override
public void onSuccess(final FriendshipRepoListFetchResponse result) {
@ -156,82 +203,70 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
return;
}
else {
int oldSize = followModels.size() == 0 ? 0 : followModels.size() - 1;
followModels.addAll(result.getItems());
if (result.isMoreAvailable()) {
friendshipService.getList(isFollowersList, profileId, result.getNextMaxId(), this);
}
else {
binding.swipeRefreshLayout.setRefreshing(false);
if (isFollowersList) followersModels.addAll(followModels);
else followingModels.addAll(followModels);
refreshAdapter(followModels, null, null, null);
moreAvailable = true;
endCursor = result.getNextMaxId();
}
else moreAvailable = false;
binding.swipeRefreshLayout.setRefreshing(false);
if (isFollowersList) followersModels.addAll(result.getItems());
else followingModels.addAll(result.getItems());
refreshAdapter(followModels, null, null, null);
layoutManager.scrollToPosition(oldSize);
}
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error fetching list (single)", t);
}
};
binding.swipeRefreshLayout.setRefreshing(true);
friendshipService.getList(isFollowersList, profileId, null, cb);
layoutManager = new LinearLayoutManager(getContext());
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!TextUtils.isEmpty(endCursor)) {
binding.swipeRefreshLayout.setRefreshing(true);
layoutManager.setStackFromEnd(true);
friendshipService.getList(isFollowersList, profileId, endCursor, cb);
}
endCursor = null;
});
binding.rvFollow.addOnScrollListener(lazyLoader);
binding.rvFollow.setLayoutManager(layoutManager);
if (moreAvailable) {
binding.swipeRefreshLayout.setRefreshing(true);
friendshipService.getList(isFollowersList, profileId, endCursor, cb);
}
else {
refreshAdapter(followModels, null, null, null);
layoutManager.scrollToPosition(0);
}
}
private void listCompare() {
layoutManager.setStackFromEnd(false);
binding.rvFollow.clearOnScrollListeners();
loading = true;
setSubtitle(R.string.followers_compare);
allFollowing.clear();
followersModels.clear();
followingModels.clear();
final ServiceCallback<FriendshipRepoListFetchResponse> followingFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@Override
public void onSuccess(final FriendshipRepoListFetchResponse result) {
if (result != null) {
followingModels.addAll(result.getItems());
if (result.isMoreAvailable()) {
friendshipService.getList(false, profileId, result.getNextMaxId(), this);
} else {
showCompare();
}
} else binding.swipeRefreshLayout.setRefreshing(false);
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Log.e(TAG, "Error fetching list (double, following)", t);
}
};
final ServiceCallback<FriendshipRepoListFetchResponse> followersFetchCb = new ServiceCallback<FriendshipRepoListFetchResponse>() {
@Override
public void onSuccess(final FriendshipRepoListFetchResponse result) {
if (result != null) {
followersModels.addAll(result.getItems());
if (result.isMoreAvailable()) {
friendshipService.getList(true, profileId, result.getNextMaxId(), this);
} else if (followingModels.size() == 0) {
friendshipService.getList(false, profileId, null, followingFetchCb);
} else {
showCompare();
}
}
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Log.e(TAG, "Error fetching list (double, follower)", t);
}
};
binding.swipeRefreshLayout.setRefreshing(true);
if (followersModels.size() == 0) {
friendshipService.getList(true, profileId, null, followersFetchCb);
if (moreAvailable) {
binding.swipeRefreshLayout.setRefreshing(true);
Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show();
friendshipService.getList(isFollowersList,
profileId,
endCursor,
isFollowersList ? followersFetchCb : followingFetchCb);
}
else if (followingModels.size() == 0) {
friendshipService.getList(false, profileId, null, followingFetchCb);
else if (followersModels.size() == 0 || followingModels.size() == 0) {
binding.swipeRefreshLayout.setRefreshing(true);
Toast.makeText(getContext(), R.string.follower_start_compare, Toast.LENGTH_LONG).show();
friendshipService.getList(!isFollowersList,
profileId,
null,
isFollowersList ? followingFetchCb : followersFetchCb);
}
else showCompare();
}
@ -346,12 +381,12 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
final Context context = getContext();
if (loading) Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_LONG).show();
else if (isCompare) {
listFollows();
isCompare = !isCompare;
listFollows();
}
else {
listCompare();
isCompare = !isCompare;
listCompare();
}
return true;
}
@ -371,8 +406,7 @@ public final class FollowViewerFragment extends Fragment implements SwipeRefresh
if (allFollowing != null && allFollowing.size() > 0)
groups.add(new ExpandableGroup(resources.getString(R.string.followers_both_following), allFollowing));
} else {
final ExpandableGroup group = new ExpandableGroup(type, followModels);
groups.add(group);
groups.add(new ExpandableGroup(type, followModels));
}
adapter = new FollowAdapter(clickListener, groups);
adapter.toggleGroup(0);

View File

@ -54,6 +54,7 @@ import awais.instagrabber.db.entities.Favorite;
import awais.instagrabber.db.repositories.FavoriteRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HashtagModel;
import awais.instagrabber.models.PostsLayoutPreferences;
@ -363,19 +364,27 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void fetchHashtagModel() {
stopCurrentExecutor();
binding.swipeRefreshLayout.setRefreshing(true);
currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> {
hashtagModel = result;
binding.swipeRefreshLayout.setRefreshing(false);
final Context context = getContext();
if (context == null) return;
if (hashtagModel == null) {
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
currentlyExecuting = new HashtagFetcher(hashtag.substring(1), new FetchListener<HashtagModel>() {
@Override
public void onResult(final HashtagModel result) {
hashtagModel = result;
binding.swipeRefreshLayout.setRefreshing(false);
final Context context = getContext();
if (context == null) return;
if (hashtagModel == null) {
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return;
}
setTitle();
setHashtagDetails();
setupPosts();
fetchStories();
}
@Override
public void onFailure(Throwable t) {
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
setTitle();
setHashtagDetails();
setupPosts();
fetchStories();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ -410,9 +419,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
hashtagDetailsBinding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
onRefresh();
hashtagDetailsBinding.btnFollowTag.setText(R.string.unfollow);
hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_disabled_24);
}
@Override
@ -435,9 +447,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
hashtagDetailsBinding.btnFollowTag.setClickable(true);
if (!result) {
Log.e(TAG, "onSuccess: result is false");
Snackbar.make(root, R.string.downloader_unknown_error, BaseTransientBottomBar.LENGTH_LONG)
.show();
return;
}
onRefresh();
hashtagDetailsBinding.btnFollowTag.setText(R.string.follow);
hashtagDetailsBinding.btnFollowTag.setChipIconResource(R.drawable.ic_outline_person_add_24);
}
@Override
@ -462,8 +477,23 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
favoriteRepository.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
hashtagDetailsBinding.favChip.setText(R.string.favorite_short);
favoriteRepository.insertOrUpdateFavorite(new Favorite(
result.getId(),
hashtag.substring(1),
FavoriteType.HASHTAG,
hashtagModel.getName(),
hashtagModel.getSdProfilePic(),
result.getDateAdded()
), new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
hashtagDetailsBinding.favChip.setText(R.string.favorite_short);
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
@ -492,18 +522,18 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onDataNotAvailable() {
favoriteRepository.insertOrUpdateFavorite(new Favorite(
-1,
0,
hashtag.substring(1),
FavoriteType.HASHTAG,
hashtagModel.getName(),
null,
hashtagModel.getSdProfilePic(),
new Date()
), new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
hashtagDetailsBinding.favChip.setText(R.string.favorite_short);
hashtagDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
showSnackbar(getString(R.string.added_to_favs));
showSnackbar(getString(R.string.added_to_favs_short));
}
@Override
@ -513,7 +543,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
}));
hashtagDetailsBinding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic());
final String postCount = String.valueOf(hashtagModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount));
final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline,
hashtagModel.getPostCount() > 2000000000L ? 2000000000 : hashtagModel.getPostCount().intValue(),
postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
hashtagDetailsBinding.mainTagPostCount.setText(span);
@ -522,7 +554,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (!hasStories) return;
// show stories
final NavDirections action = HashTagFragmentDirections
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName(), false, false);
NavHostFragment.findNavController(this).navigate(action);
});
}

View File

@ -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();
}
}

View File

@ -400,8 +400,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
// binding.swipeRefreshLayout.setRefreshing(true);
locationDetailsBinding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline,
postCount));
final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline,
locationModel.getPostCount() > 2000000000L ? 2000000000 : locationModel.getPostCount().intValue(),
postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
locationDetailsBinding.mainLocPostCount.setText(span);
@ -455,6 +456,20 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
locationDetailsBinding.favChip.setText(R.string.favorite_short);
favoriteRepository.insertOrUpdateFavorite(new Favorite(
result.getId(),
locationId,
FavoriteType.LOCATION,
locationModel.getName(),
locationModel.getSdProfilePic(),
result.getDateAdded()
), new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {}
@Override
public void onDataNotAvailable() {}
});
}
@Override
@ -484,7 +499,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override
public void onDataNotAvailable() {
favoriteRepository.insertOrUpdateFavorite(new Favorite(
-1,
0,
locationId,
FavoriteType.LOCATION,
locationModel.getName(),
@ -495,7 +510,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
public void onSuccess(final Void result) {
locationDetailsBinding.favChip.setText(R.string.favorite_short);
locationDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
showSnackbar(getString(R.string.added_to_favs));
showSnackbar(getString(R.string.added_to_favs_short));
}
@Override
@ -508,7 +523,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
if (hasStories) {
// show stories
final NavDirections action = LocationFragmentDirections
.actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName());
.actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName(), false, false);
NavHostFragment.findNavController(this).navigate(action);
}
});

View File

@ -18,20 +18,28 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.adapters.NotificationsAdapter;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
import awais.instagrabber.asyncs.NotificationsFetcher;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.databinding.FragmentNotificationsViewerBinding;
import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.NotificationModel;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
import awais.instagrabber.utils.Constants;
@ -40,6 +48,7 @@ import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.NotificationViewModel;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.NewsService;
import awais.instagrabber.webservices.ServiceCallback;
@ -53,96 +62,146 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
private boolean shouldRefresh = true;
private NotificationViewModel notificationViewModel;
private FriendshipService friendshipService;
private String userId;
private String csrfToken;
private MediaService mediaService;
private NewsService newsService;
private String userId, csrfToken, type;
private Context context;
private final OnNotificationClickListener clickListener = model -> {
if (model == null) return;
final String username = model.getUsername();
final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText())));
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
String[] commentDialogList;
if (model.getShortCode() != null) {
commentDialogList = new String[]{
getString(R.string.open_profile),
getString(R.string.view_post)
};
} else if (model.getType() == NotificationType.REQUEST) {
commentDialogList = new String[]{
getString(R.string.open_profile),
getString(R.string.request_approve),
getString(R.string.request_reject)
};
} else {
commentDialogList = new String[]{getString(R.string.open_profile)};
private final OnNotificationClickListener clickListener = new OnNotificationClickListener() {
@Override
public void onProfileClick(final String username) {
openProfile(username);
}
final Context context = getContext();
if (context == null) return;
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
switch (which) {
case 0:
openProfile(model.getUsername());
break;
case 1:
if (model.getType() == NotificationType.REQUEST) {
friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
// Log.d(TAG, "onSuccess: " + result);
if (result.getStatus().equals("ok")) {
onRefresh();
return;
}
Log.e(TAG, "approve: status was not ok!");
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "approve: onFailure: ", t);
}
});
return;
}
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create();
alertDialog.show();
new PostFetcher(model.getShortCode(), feedModel -> {
@Override
public void onPreviewClick(final NotificationModel model) {
if (model.getType() == NotificationType.RESPONDED_STORY) {
final NavDirections action = NotificationsViewerFragmentDirections.actionNotificationsViewerFragmentToStoryViewerFragment(
-1, null, false, false, model.getPostId(), model.getUsername(), false, true);
NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action);
}
else {
mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() {
@Override
public void onSuccess(final FeedModel feedModel) {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog1 -> alertDialog.dismiss());
fragment.show(getChildFragmentManager(), "post_view");
}).execute();
break;
case 2:
friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
// Log.d(TAG, "onSuccess: " + result);
if (result.getStatus().equals("ok")) {
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
}
}
@Override
public void onNotificationClick(final NotificationModel model) {
if (model == null) return;
final String username = model.getUsername();
if (model.getType() == NotificationType.FOLLOW || model.getType() == NotificationType.AYML) {
openProfile(username);
}
else {
final SpannableString title = new SpannableString(username + (TextUtils.isEmpty(model.getText()) ? "" : (":\n" + model.getText())));
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
String[] commentDialogList;
if (model.getType() == NotificationType.RESPONDED_STORY) {
commentDialogList = new String[]{
getString(R.string.open_profile),
getString(R.string.view_story)
};
}
else if (model.getPostId() != null) {
commentDialogList = new String[]{
getString(R.string.open_profile),
getString(R.string.view_post)
};
}
else if (model.getType() == NotificationType.REQUEST) {
commentDialogList = new String[]{
getString(R.string.open_profile),
getString(R.string.request_approve),
getString(R.string.request_reject)
};
}
else commentDialogList = null; // shouldn't happen
final Context context = getContext();
if (context == null) return;
final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
switch (which) {
case 0:
openProfile(username);
break;
case 1:
if (model.getType() == NotificationType.REQUEST) {
friendshipService.approve(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
onRefresh();
Log.e(TAG, "approve: status was not ok!");
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "approve: onFailure: ", t);
}
});
return;
}
Log.e(TAG, "ignore: status was not ok!");
}
else if (model.getType() == NotificationType.RESPONDED_STORY) {
final NavDirections action = NotificationsViewerFragmentDirections.actionNotificationsViewerFragmentToStoryViewerFragment(
-1, null, false, false, model.getPostId(), model.getUsername(), false, true);
NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action);
return;
}
final AlertDialog alertDialog = new AlertDialog.Builder(context)
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create();
alertDialog.show();
mediaService.fetch(model.getPostId(), new ServiceCallback<FeedModel>() {
@Override
public void onSuccess(final FeedModel feedModel) {
final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog1 -> alertDialog.dismiss());
fragment.show(getChildFragmentManager(), "post_view");
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "ignore: onFailure: ", t);
}
});
break;
@Override
public void onFailure(final Throwable t) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
break;
case 2:
friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "ignore: onFailure: ", t);
}
});
break;
}
};
new AlertDialog.Builder(context)
.setTitle(title)
.setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null)
.show();
}
};
new AlertDialog.Builder(context)
.setTitle(title)
.setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null)
.show();
}
};
private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
if (getContext() == null) return;
@ -158,7 +217,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = getContext();
context = getContext();
if (context == null) return;
NotificationManagerCompat.from(context.getApplicationContext()).cancel(Constants.ACTIVITY_NOTIFICATION_ID);
final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
@ -166,7 +225,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
Toast.makeText(context, R.string.activity_notloggedin, Toast.LENGTH_SHORT).show();
}
friendshipService = FriendshipService.getInstance();
newsService = NewsService.getInstance();
mediaService = MediaService.getInstance();
userId = CookieUtils.getUserIdFromCookie(cookie);
csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
}
@ -191,6 +250,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
}
private void init() {
final NotificationsViewerFragmentArgs fragmentArgs = NotificationsViewerFragmentArgs.fromBundle(getArguments());
type = fragmentArgs.getType();
final Context context = getContext();
CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE));
binding.swipeRefreshLayout.setOnRefreshListener(this);
@ -205,23 +266,39 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
@Override
public void onRefresh() {
binding.swipeRefreshLayout.setRefreshing(true);
new NotificationsFetcher(notificationModels -> {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
final String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
newsService.markChecked(timestamp, csrfToken, new ServiceCallback<Boolean>() {
@Override
public void onSuccess(@NonNull final Boolean result) {
// Log.d(TAG, "onResponse: body: " + result);
if (!result) Log.e(TAG, "onSuccess: Error marking activity checked, response is false");
}
switch (type) {
case "notif":
new NotificationsFetcher(true, new FetchListener<List<NotificationModel>>() {
@Override
public void onResult(final List<NotificationModel> notificationModels) {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: Error marking activity checked", t);
}
});
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
@Override
public void onFailure(Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
case "ayml":
newsService = NewsService.getInstance();
newsService.fetchSuggestions(csrfToken, new ServiceCallback<List<NotificationModel>>() {
@Override
public void onSuccess(final List<NotificationModel> notificationModels) {
binding.swipeRefreshLayout.setRefreshing(false);
notificationViewModel.getList().postValue(notificationModels);
}
@Override
public void onFailure(final Throwable t) {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
}
}
private void openProfile(final String username) {

View File

@ -26,6 +26,7 @@ import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import android.widget.Toast;
@ -33,6 +34,7 @@ import android.widget.ViewSwitcher;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.PermissionChecker;
import androidx.core.view.ViewCompat;
@ -70,6 +72,7 @@ import awais.instagrabber.customviews.VideoPlayerCallbackAdapter;
import awais.instagrabber.customviews.VideoPlayerViewHelper;
import awais.instagrabber.customviews.drawee.AnimatedZoomableController;
import awais.instagrabber.databinding.DialogPostViewBinding;
import awais.instagrabber.fragments.main.ProfileFragment;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
@ -114,6 +117,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
private DialogInterface.OnShowListener onShowListener;
private boolean isLoggedIn;
private boolean hasBeenToggled = false;
private CharSequence postCaption = null;
private final VerticalDragHelper.OnVerticalDragListener onVerticalDragListener = new VerticalDragHelper.OnVerticalDragListener() {
@ -306,6 +310,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
@Override
public void onDestroyView() {
super.onDestroyView();
if (feedModel == null) return;
switch (feedModel.getItemType()) {
case MEDIA_TYPE_VIDEO:
if (videoPlayerViewHelper != null) {
@ -414,8 +419,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
private void init() {
if (feedModel == null) return;
final String cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
isLoggedIn = !TextUtils.isEmpty(COOKIE) && CookieUtils.getUserIdFromCookie(COOKIE) != null;
if (!wasPaused && (sharedProfilePicElement != null || sharedMainPostElement != null)) {
binding.getRoot().getBackground().mutate().setAlpha(0);
}
@ -534,7 +538,16 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
}
});
binding.like.setOnLongClickListener(v -> {
Utils.displayToastAboveView(context, v, getString(R.string.like_without_count));
final NavController navController = getNavController();
if (navController != null && isLoggedIn) {
final Bundle bundle = new Bundle();
bundle.putString("postId", feedModel.getPostId());
bundle.putBoolean("isComment", false);
navController.navigate(R.id.action_global_likesViewerFragment, bundle);
}
else {
Utils.displayToastAboveView(context, v, getString(R.string.like_without_count));
}
return true;
});
}
@ -701,13 +714,52 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
}
private void setupCaption() {
final CharSequence postCaption = feedModel.getPostCaption();
postCaption = feedModel.getPostCaption();
binding.date.setText(Utils.datetimeParser.format(new Date(feedModel.getTimestamp() * 1000L)));
if (TextUtils.isEmpty(postCaption)) {
if (!feedModel.getProfileModel().getId().equals(CookieUtils.getUserIdFromCookie(COOKIE)) && TextUtils.isEmpty(postCaption)) {
binding.caption.setVisibility(View.GONE);
binding.translateTitle.setVisibility(View.GONE);
binding.captionToggle.setVisibility(View.GONE);
return;
}
if (feedModel.getProfileModel().getId().equals(CookieUtils.getUserIdFromCookie(COOKIE))) {
binding.editCaption.setVisibility(View.VISIBLE);
binding.editCaption.setOnClickListener(v -> {
final EditText input = new EditText(context);
input.setText(postCaption);
new AlertDialog.Builder(context)
.setTitle(R.string.edit_caption)
.setView(input)
.setPositiveButton(R.string.confirm, (d, w) -> {
binding.editCaption.setVisibility(View.GONE);
mediaService.editCaption(
feedModel.getPostId(),
CookieUtils.getUserIdFromCookie(COOKIE),
input.getText().toString(),
CookieUtils.getCsrfTokenFromCookie(COOKIE),
new ServiceCallback<Boolean>() {
@Override
public void onSuccess(final Boolean result) {
binding.editCaption.setVisibility(View.VISIBLE);
if (result) {
feedModel.setPostCaption(input.getText().toString());
binding.caption.setText(input.getText().toString());
}
else Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error editing caption", t);
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
binding.editCaption.setVisibility(View.VISIBLE);
}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
});
}
binding.caption.addOnHashtagListener(autoLinkItem -> {
final NavController navController = NavHostFragment.findNavController(this);
final Bundle bundle = new Bundle();
@ -737,11 +789,33 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
binding.captionParent.getBackground().mutate().setAlpha((int) (128 + (128 * (slideOffset < 0 ? 0 : slideOffset))));
}
});
binding.caption.setOnClickListener(v -> {
binding.captionFrame.setOnClickListener(v -> {
if (bottomSheetBehavior == null) return;
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) return;
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
});
if (TextUtils.isEmpty(feedModel.getCaptionId()))
binding.translateTitle.setVisibility(View.GONE);
else binding.translateTitle.setOnClickListener(v -> {
mediaService.translate(feedModel.getCaptionId(), "1", new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
if (TextUtils.isEmpty(result)) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
binding.translateTitle.setOnClickListener(null);
binding.translatedCaption.setVisibility(View.VISIBLE);
binding.translatedCaption.setText(result);
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error translating comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
});
binding.captionToggle.setOnClickListener(v -> {
if (bottomSheetBehavior == null) return;
switch (bottomSheetBehavior.getState()) {
@ -894,6 +968,7 @@ public class PostViewV2Fragment extends SharedElementTransitionDialogFragment {
@Override
public void onItemClicked(final int position) {
toggleDetails();
}
@Override

View File

@ -40,24 +40,26 @@ import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private FragmentSavedBinding binding;
private String username;
private String username, cookie, profileId;
private ActionMode actionMode;
private SwipeRefreshLayout root;
private AppCompatActivity fragmentActivity;
private boolean shouldRefresh = true;
private boolean isLoggedIn, shouldRefresh = true;
private PostItemType type;
private String profileId;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
@ -225,6 +227,8 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
cookie = settingsHelper.getString(Constants.COOKIE);
isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
if (root != null) {
shouldRefresh = false;
return root;
@ -281,7 +285,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
private void setupPosts() {
binding.posts.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new SavedPostFetchService(profileId, type))
.setPostFetchService(new SavedPostFetchService(profileId, type, isLoggedIn))
.setLayoutPreferences(layoutPreferences)
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)

View File

@ -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);
}
}

View File

@ -11,6 +11,7 @@ import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@ -20,6 +21,9 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -54,6 +58,9 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -62,10 +69,7 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction;
import awais.instagrabber.asyncs.SeenAction;
import awais.instagrabber.asyncs.VoteAction;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.FragmentStoryViewerBinding;
@ -78,16 +82,27 @@ import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.models.stickers.SliderModel;
import awais.instagrabber.models.stickers.SwipeUpModel;
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
import awais.instagrabber.repositories.responses.StoryStickerResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadBroadcastResponse;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.ArchivesViewModel;
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.viewmodels.StoriesViewModel;
import awais.instagrabber.webservices.DirectMessagesService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
import awaisomereport.LogCollector;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
@ -100,8 +115,7 @@ public class StoryViewerFragment extends Fragment {
private AppCompatActivity fragmentActivity;
private View root;
private @NonNull
FragmentStoryViewerBinding binding;
private FragmentStoryViewerBinding binding;
private String currentStoryUsername;
private StoriesAdapter storiesAdapter;
private SwipeEvent swipeEvent;
@ -115,26 +129,32 @@ public class StoryViewerFragment extends Fragment {
private QuestionModel question;
private String[] mentions;
private QuizModel quiz;
private SliderModel slider;
private MenuItem menuDownload;
private MenuItem menuDm;
private SimpleExoPlayer player;
private boolean isHashtag, isLoc;
private String highlight;
private boolean fetching = false;
private boolean fetching = false, sticking = false, shouldRefresh = true;
private int currentFeedStoryIndex;
private double sliderValue;
private StoriesViewModel storiesViewModel;
private boolean shouldRefresh = true;
private StoryViewerFragmentArgs fragmentArgs;
private ViewModel viewModel;
private boolean isHighlight;
private boolean isHighlight, isArchive, isNotification;
private DirectMessagesService directMessagesService;
private final String cookie = settingsHelper.getString(Constants.COOKIE);
private final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
private final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
private final String deviceId = settingsHelper.getString(Constants.DEVICE_UUID);
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) requireActivity();
storiesService = StoriesService.getInstance();
directMessagesService = DirectMessagesService.getInstance(csrfToken, userIdFromCookie, deviceId);
setHasOptionsMenu(true);
}
@ -188,24 +208,33 @@ public class StoryViewerFragment extends Fragment {
new AlertDialog.Builder(context)
.setTitle(R.string.reply_story)
.setView(input)
.setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> {
// try {
// final StoryReplyBroadcastOptions options = new StoryReplyBroadcastOptions(
// threadId,
// input.getText().toString(),
// currentStory.getStoryMediaId(),
// currentStory.getUserId()
// );
// final DirectThreadBroadcaster broadcast = new DirectThreadBroadcaster(threadId);
// broadcast.setOnTaskCompleteListener(result -> Toast.makeText(
// context,
// result != null ? R.string.answered_story : R.string.downloader_unknown_error,
// Toast.LENGTH_SHORT
// ).show());
// broadcast.execute(options);
// } catch (UnsupportedEncodingException e) {
// Log.e(TAG, "Error", e);
// }
.setPositiveButton(R.string.confirm, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> {
try {
final Call<DirectThreadBroadcastResponse> request = directMessagesService
.broadcastStoryReply(BroadcastOptions.ThreadIdOrUserIds.of(threadId),
input.getText().toString(),
currentStory.getStoryMediaId(),
currentStory.getUserId());
request.enqueue(new Callback<DirectThreadBroadcastResponse>() {
@Override
public void onResponse(@NonNull final Call<DirectThreadBroadcastResponse> call,
@NonNull final Response<DirectThreadBroadcastResponse> response) {
if (!response.isSuccessful()) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(@NonNull final Call<DirectThreadBroadcastResponse> call, @NonNull final Throwable t) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Log.e(TAG, "onFailure: ", t);
}
});
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e);
}
}).execute())
.setNegativeButton(R.string.cancel, null)
.show();
@ -244,9 +273,13 @@ public class StoryViewerFragment extends Fragment {
currentFeedStoryIndex = fragmentArgs.getFeedStoryIndex();
highlight = fragmentArgs.getHighlight();
isHighlight = !TextUtils.isEmpty(highlight);
isArchive = fragmentArgs.getIsArchive();
isNotification = fragmentArgs.getIsNotification();
if (currentFeedStoryIndex >= 0) {
viewModel = isHighlight
? new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class)
? isArchive
? new ViewModelProvider(fragmentActivity).get(ArchivesViewModel.class)
: new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class)
: new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
}
// feedStoryModels = feedStoriesViewModel.getList().getValue();
@ -275,7 +308,10 @@ public class StoryViewerFragment extends Fragment {
final boolean hasFeedStories;
List<?> models = null;
if (currentFeedStoryIndex >= 0) {
if (isHighlight) {
if (isArchive) {
final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel;
models = archivesViewModel.getList().getValue();
} else if (isHighlight) {
final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel;
models = highlightsViewModel.getList().getValue();
// final HighlightModel model = models.get(currentFeedStoryIndex);
@ -296,15 +332,16 @@ public class StoryViewerFragment extends Fragment {
swipeEvent = isRightSwipe -> {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
final int storiesLen = storyModels == null ? 0 : storyModels.size();
if (sticking) {
Toast.makeText(context, R.string.follower_wait_to_load, Toast.LENGTH_SHORT).show();
return;
}
if (storiesLen <= 0) return;
final boolean isLeftSwipe = !isRightSwipe;
final boolean endOfCurrentStories = slidePos + 1 >= storiesLen;
final boolean swipingBeyondCurrentStories = (endOfCurrentStories && isLeftSwipe) || (slidePos == 0 && isRightSwipe);
if (swipingBeyondCurrentStories && hasFeedStories) {
final int index = currentFeedStoryIndex;
if (settingsHelper.getBoolean(MARK_AS_SEEN)) {
new SeenAction(cookie, currentStory).execute();
}
if ((isRightSwipe && index == 0) || (isLeftSwipe && index == finalModels.size() - 1)) {
Toast.makeText(context, R.string.no_more_stories, Toast.LENGTH_SHORT).show();
return;
@ -312,7 +349,7 @@ public class StoryViewerFragment extends Fragment {
final Object feedStoryModel = isRightSwipe
? finalModels.get(index - 1)
: finalModels.size() == index + 1 ? null : finalModels.get(index + 1);
paginateStories(feedStoryModel, context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2);
paginateStories(feedStoryModel, finalModels.get(index), context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2);
return;
}
if (isRightSwipe) {
@ -351,8 +388,12 @@ public class StoryViewerFragment extends Fragment {
if (hasFeedStories) {
binding.btnBackward.setVisibility(currentFeedStoryIndex == 0 ? View.INVISIBLE : View.VISIBLE);
binding.btnForward.setVisibility(currentFeedStoryIndex == finalModels.size() - 1 ? View.INVISIBLE : View.VISIBLE);
binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1), context, true, false));
binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1), context, false,
binding.btnBackward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex - 1),
finalModels.get(currentFeedStoryIndex),
context, true, false));
binding.btnForward.setOnClickListener(v -> paginateStories(finalModels.get(currentFeedStoryIndex + 1),
finalModels.get(currentFeedStoryIndex),
context, false,
currentFeedStoryIndex == finalModels.size() - 2));
}
@ -365,6 +406,14 @@ public class StoryViewerFragment extends Fragment {
startActivity(intent);
}
});
binding.swipeUp.setOnClickListener(v -> {
final Object tag = v.getTag();
if (tag instanceof CharSequence) {
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(tag.toString()));
startActivity(intent);
}
});
binding.viewStoryPost.setOnClickListener(v -> {
final Object tag = v.getTag();
if (!(tag instanceof CharSequence)) return;
@ -406,15 +455,30 @@ public class StoryViewerFragment extends Fragment {
poll.getLeftChoice() + " (" + poll.getLeftCount() + ")",
poll.getRightChoice() + " (" + poll.getRightCount() + ")"
}), (d, w) -> {
if (!TextUtils.isEmpty(cookie))
new VoteAction(currentStory, poll, cookie, choice -> {
if (choice > -1) {
poll.setMyChoice(choice);
Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).execute(w);
if (!TextUtils.isEmpty(cookie)) {
sticking = true;
storiesService.respondToPoll(
currentStory.getStoryMediaId().split("_")[0],
poll.getId(),
w,
userIdFromCookie,
csrfToken,
new ServiceCallback<StoryStickerResponse>() {
@Override
public void onSuccess(final StoryStickerResponse result) {
sticking = false;
poll.setMyChoice(w);
Toast.makeText(context, R.string.votef_story_poll, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(final Throwable t) {
sticking = false;
Log.e(TAG, "Error responding", t);
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
}
})
.setPositiveButton(R.string.cancel, null)
.show();
@ -426,21 +490,36 @@ public class StoryViewerFragment extends Fragment {
new AlertDialog.Builder(context)
.setTitle(question.getQuestion())
.setView(input)
.setPositiveButton(R.string.ok, (d, w) -> new RespondAction(currentStory, question, cookie, result -> {
if (result) {
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
} else
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).execute(input.getText().toString()))
.setPositiveButton(R.string.confirm, (d, w) -> {
sticking = true;
storiesService.respondToQuestion(
currentStory.getStoryMediaId().split("_")[0],
question.getId(),
input.getText().toString(),
userIdFromCookie,
csrfToken,
new ServiceCallback<StoryStickerResponse>() {
@Override
public void onSuccess(final StoryStickerResponse result) {
sticking = false;
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(final Throwable t) {
sticking = false;
Log.e(TAG, "Error responding", t);
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
} else if (tag instanceof String[]) {
mentions = (String[]) tag;
new AlertDialog.Builder(context)
.setTitle(R.string.story_mentions)
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> {
openProfile(mentions[w]);
})
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, mentions), (d, w) -> openProfile(mentions[w]))
.setPositiveButton(R.string.cancel, null)
.show();
} else if (tag instanceof QuizModel) {
@ -451,27 +530,122 @@ public class StoryViewerFragment extends Fragment {
new AlertDialog.Builder(context)
.setTitle(quiz.getMyChoice() > -1 ? getString(R.string.story_quizzed) : quiz.getQuestion())
.setAdapter(new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, choices), (d, w) -> {
if (quiz.getMyChoice() == -1 && !TextUtils.isEmpty(cookie))
new QuizAction(currentStory, quiz, cookie, choice -> {
if (choice > -1) {
quiz.setMyChoice(choice);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).execute(w);
if (quiz.getMyChoice() == -1 && !TextUtils.isEmpty(cookie)) {
sticking = true;
storiesService.respondToQuiz(
currentStory.getStoryMediaId().split("_")[0],
quiz.getId(),
w,
userIdFromCookie,
csrfToken,
new ServiceCallback<StoryStickerResponse>() {
@Override
public void onSuccess(final StoryStickerResponse result) {
sticking = false;
quiz.setMyChoice(w);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(final Throwable t) {
sticking = false;
Log.e(TAG, "Error responding", t);
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
}
})
.setPositiveButton(R.string.cancel, null)
.show();
} else if (tag instanceof SliderModel) {
slider = (SliderModel) tag;
NumberFormat percentage = NumberFormat.getPercentInstance();
percentage.setMaximumFractionDigits(2);
LinearLayout sliderView = new LinearLayout(context);
sliderView.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT));
sliderView.setOrientation(LinearLayout.VERTICAL);
TextView tv = new TextView(context);
tv.setGravity(Gravity.CENTER_HORIZONTAL);
final SeekBar input = new SeekBar(context);
double avg = slider.getAverage() * 100;
input.setProgress((int) avg);
sliderView.addView(input);
sliderView.addView(tv);
if (slider.getMyChoice().isNaN() && slider.canVote()) {
input.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
sliderValue = progress / 100.0;
tv.setText(percentage.format(sliderValue));
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
new AlertDialog.Builder(context)
.setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion())
.setMessage(getResources().getQuantityString(R.plurals.slider_info,
slider.getVoteCount(),
slider.getVoteCount(),
percentage.format(slider.getAverage())))
.setView(sliderView)
.setPositiveButton(R.string.confirm, (d, w) -> {
sticking = true;
storiesService.respondToSlider(
currentStory.getStoryMediaId().split("_")[0],
slider.getId(),
sliderValue,
userIdFromCookie,
csrfToken,
new ServiceCallback<StoryStickerResponse>() {
@Override
public void onSuccess(final StoryStickerResponse result) {
sticking = false;
slider.setMyChoice(sliderValue);
Toast.makeText(context, R.string.answered_story, Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(final Throwable t) {
sticking = false;
Log.e(TAG, "Error responding", t);
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}
});
})
.setNegativeButton(R.string.cancel, null)
.show();
} else {
input.setEnabled(false);
tv.setText(getString(R.string.slider_answer, percentage.format(slider.getMyChoice())));
new AlertDialog.Builder(context)
.setTitle(TextUtils.isEmpty(slider.getQuestion()) ? slider.getEmoji() : slider.getQuestion())
.setMessage(getResources().getQuantityString(R.plurals.slider_info,
slider.getVoteCount(),
slider.getVoteCount(),
percentage.format(slider.getAverage())))
.setView(sliderView)
.setPositiveButton(R.string.ok, null)
.show();
}
}
};
binding.poll.setOnClickListener(storyActionListener);
binding.answer.setOnClickListener(storyActionListener);
binding.mention.setOnClickListener(storyActionListener);
binding.quiz.setOnClickListener(storyActionListener);
binding.slider.setOnClickListener(storyActionListener);
}
private void resetView() {
final Context context = getContext();
slidePos = 0;
lastSlidePos = 0;
if (menuDownload != null) menuDownload.setVisible(false);
@ -480,10 +654,23 @@ public class StoryViewerFragment extends Fragment {
releasePlayer();
String currentStoryMediaId = null;
if (currentFeedStoryIndex >= 0) {
if (isHighlight) {
if (isArchive) {
final ArchivesViewModel archivesViewModel = (ArchivesViewModel) viewModel;
final List<HighlightModel> models = archivesViewModel.getList().getValue();
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
final HighlightModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId();
currentStoryUsername = model.getTitle();
} else if (isHighlight) {
final HighlightsViewModel highlightsViewModel = (HighlightsViewModel) viewModel;
final List<HighlightModel> models = highlightsViewModel.getList().getValue();
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) return;
if (models == null || models.isEmpty() || currentFeedStoryIndex >= models.size()) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
final HighlightModel model = models.get(currentFeedStoryIndex);
currentStoryMediaId = model.getId();
currentStoryUsername = model.getTitle();
@ -502,7 +689,12 @@ public class StoryViewerFragment extends Fragment {
isHashtag = fragmentArgs.getIsHashtag();
isLoc = fragmentArgs.getIsLoc();
final boolean hasUsername = !TextUtils.isEmpty(currentStoryUsername);
if (hasUsername) {
if (isHighlight) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(highlight);
}
} else if (hasUsername) {
currentStoryUsername = currentStoryUsername.replace("@", "");
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
@ -511,6 +703,31 @@ public class StoryViewerFragment extends Fragment {
}
storiesViewModel.getList().setValue(Collections.emptyList());
if (currentStoryMediaId == null) return;
if (isNotification) {
storiesService.fetch(currentStoryMediaId, new ServiceCallback<StoryModel>() {
@Override
public void onSuccess(final StoryModel storyModel) {
fetching = false;
binding.storiesList.setVisibility(View.GONE);
if (storyModel == null) {
storiesViewModel.getList().setValue(Collections.emptyList());
currentStory = null;
return;
}
storiesViewModel.getList().setValue(Collections.singletonList(storyModel));
currentStory = storyModel;
refreshStory();
}
@Override
public void onFailure(final Throwable t) {
final Context context = getContext();
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error", t);
}
});
return;
}
final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
@Override
public void onSuccess(final List<StoryModel> storyModels) {
@ -521,7 +738,9 @@ public class StoryViewerFragment extends Fragment {
binding.storiesList.setVisibility(View.GONE);
return;
}
binding.storiesList.setVisibility(View.VISIBLE);
binding.storiesList.setVisibility((storyModels.size() == 1 && currentFeedStoryIndex == -1) ? View.GONE : View.VISIBLE);
binding.btnBackward.setVisibility(currentFeedStoryIndex == -1 ? View.GONE : View.VISIBLE);
binding.btnForward.setVisibility(currentFeedStoryIndex == -1 ? View.GONE : View.VISIBLE);
storiesViewModel.getList().setValue(storyModels);
currentStory = storyModels.get(0);
refreshStory();
@ -529,6 +748,8 @@ public class StoryViewerFragment extends Fragment {
@Override
public void onFailure(final Throwable t) {
final Context context = getContext();
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error", t);
}
};
@ -543,7 +764,7 @@ public class StoryViewerFragment extends Fragment {
private void refreshStory() {
if (binding.storiesList.getVisibility() == View.VISIBLE) {
final List<StoryModel> storyModels = storiesViewModel.getList().getValue();
if (storyModels != null) {
if (storyModels != null && storyModels.size() > 0) {
StoryModel item = storyModels.get(lastSlidePos);
if (item != null) {
item.setCurrentSlide(false);
@ -587,6 +808,17 @@ public class StoryViewerFragment extends Fragment {
binding.quiz.setVisibility(quiz != null ? View.VISIBLE : View.GONE);
binding.quiz.setTag(quiz);
slider = currentStory.getSlider();
binding.slider.setVisibility(slider != null ? View.VISIBLE : View.GONE);
binding.slider.setTag(slider);
final SwipeUpModel swipeUp = currentStory.getSwipeUp();
if (swipeUp != null) {
binding.swipeUp.setVisibility(View.VISIBLE);
binding.swipeUp.setText(swipeUp.getText());
binding.swipeUp.setTag(swipeUp.getUrl());
}
releasePlayer();
if (isHashtag || isLoc) {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -670,8 +902,8 @@ public class StoryViewerFragment extends Fragment {
@Override
public void onLoadCompleted(final int windowIndex,
@Nullable final MediaSource.MediaPeriodId mediaPeriodId,
final LoadEventInfo loadEventInfo,
final MediaLoadData mediaLoadData) {
@NonNull final LoadEventInfo loadEventInfo,
@NonNull final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie))
menuDm.setVisible(true);
@ -681,8 +913,8 @@ public class StoryViewerFragment extends Fragment {
@Override
public void onLoadStarted(final int windowIndex,
@Nullable final MediaSource.MediaPeriodId mediaPeriodId,
final LoadEventInfo loadEventInfo,
final MediaLoadData mediaLoadData) {
@NonNull final LoadEventInfo loadEventInfo,
@NonNull final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null && !TextUtils.isEmpty(cookie))
menuDm.setVisible(true);
@ -692,17 +924,17 @@ public class StoryViewerFragment extends Fragment {
@Override
public void onLoadCanceled(final int windowIndex,
@Nullable final MediaSource.MediaPeriodId mediaPeriodId,
final LoadEventInfo loadEventInfo,
final MediaLoadData mediaLoadData) {
@NonNull final LoadEventInfo loadEventInfo,
@NonNull final MediaLoadData mediaLoadData) {
binding.progressView.setVisibility(View.GONE);
}
@Override
public void onLoadError(final int windowIndex,
@Nullable final MediaSource.MediaPeriodId mediaPeriodId,
final LoadEventInfo loadEventInfo,
final MediaLoadData mediaLoadData,
final IOException error,
@NonNull final LoadEventInfo loadEventInfo,
@NonNull final MediaLoadData mediaLoadData,
@NonNull final IOException error,
final boolean wasCanceled) {
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
@ -745,12 +977,25 @@ public class StoryViewerFragment extends Fragment {
player = null;
}
private void paginateStories(Object feedStory, Context context, boolean backward, boolean last) {
if (feedStory != null) {
private void paginateStories(Object newFeedStory, Object oldFeedStory, Context context, boolean backward, boolean last) {
if (newFeedStory != null) {
if (fetching) {
Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show();
return;
}
if (settingsHelper.getBoolean(MARK_AS_SEEN)
&& oldFeedStory instanceof FeedStoryModel
&& viewModel instanceof FeedStoriesViewModel) {
final FeedStoriesViewModel feedStoriesViewModel = (FeedStoriesViewModel) viewModel;
final FeedStoryModel oldFeedStoryModel = (FeedStoryModel) oldFeedStory;
if (!oldFeedStoryModel.isFullyRead()) {
oldFeedStoryModel.setFullyRead(true);
final List<FeedStoryModel> models = feedStoriesViewModel.getList().getValue();
final List<FeedStoryModel> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
modelsCopy.set(currentFeedStoryIndex, oldFeedStoryModel);
feedStoriesViewModel.getList().postValue(models);
}
}
fetching = true;
binding.btnBackward.setVisibility(currentFeedStoryIndex == 1 && backward ? View.INVISIBLE : View.VISIBLE);
binding.btnForward.setVisibility(last ? View.INVISIBLE : View.VISIBLE);

View File

@ -18,7 +18,9 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.Fragment;
@ -42,6 +44,7 @@ import awais.instagrabber.utils.Utils;
public class DirectMessageSettingsFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DirectMsgsSettingsFrag";
private AppCompatActivity fragmentActivity;
private RecyclerView userList;
private RecyclerView leftUserList;
private EditText titleText;
@ -82,6 +85,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) requireActivity();
basicClickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof ProfileModel) {
@ -147,6 +151,11 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
threadTitle = DirectMessageSettingsFragmentArgs.fromBundle(getArguments()).getTitle();
binding.swipeRefreshLayout.setEnabled(false);
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(threadTitle);
}
userList = binding.userList;
userList.setHasFixedSize(true);
userList.setLayoutManager(layoutManager);
@ -209,7 +218,7 @@ public class DirectMessageSettingsFragment extends Fragment implements SwipeRefr
class ChangeSettings extends AsyncTask<String, Void, Void> {
String action, argument;
boolean ok = false;
private String text;
private final String text;
public ChangeSettings(final String text) {
this.text = text;

View File

@ -12,6 +12,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
@ -47,6 +48,7 @@ import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedStoriesViewModel;
@ -55,6 +57,7 @@ import awais.instagrabber.webservices.StoriesService;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "FeedFragment";
@ -74,6 +77,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
private int downloadChildPosition = -1;
private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_POSTS_LAYOUT);
private RecyclerView storiesRecyclerView;
private MenuItem storyListMenu;
private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override
@ -272,26 +276,23 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
@Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.feed_menu, menu);
storyListMenu = menu.findItem(R.id.storyList);
storyListMenu.setVisible(!storiesFetching);
}
@Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
if (item.getItemId() == R.id.storyList) {
final NavDirections action = FeedFragmentDirections.actionGlobalStoryListViewerFragment("feed");
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
}
else if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onResume() {
super.onResume();
updateSwipeRefreshState();
// if (videoAwareRecyclerScroller != null && shouldAutoPlay) {
// videoAwareRecyclerScroller.startPlaying();
// }
}
@Override
public void onRefresh() {
binding.feedRecyclerView.refresh();
@ -309,9 +310,13 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
@Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final boolean granted = grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext();
if (context == null) return;
if (!granted) {
Toast.makeText(context, R.string.download_permission, Toast.LENGTH_SHORT).show();
return;
}
if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
if (downloadFeedModel == null) return;
DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
@ -346,11 +351,22 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
}
private void setupFeedStories() {
if (storyListMenu != null) storyListMenu.setVisible(false);
feedStoriesViewModel = new ViewModelProvider(fragmentActivity).get(FeedStoriesViewModel.class);
final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter((model, position) -> {
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null);
NavHostFragment.findNavController(this).navigate(action);
});
final FeedStoriesAdapter feedStoriesAdapter = new FeedStoriesAdapter(
new FeedStoriesAdapter.OnFeedStoryClickListener() {
@Override
public void onFeedStoryClick(FeedStoryModel model, int position) {
final NavDirections action = FeedFragmentDirections.actionFeedFragmentToStoryViewerFragment(position, null, false, false, null, null, false, false);
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
}
@Override
public void onFeedStoryLongClick(FeedStoryModel model, int position) {
navigateToProfile("@" + model.getProfileModel().getUsername());
}
}
);
final Context context = getContext();
if (context == null) return;
storiesRecyclerView = new RecyclerView(context);
@ -362,18 +378,20 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
storiesRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.HORIZONTAL, false));
storiesRecyclerView.setAdapter(feedStoriesAdapter);
fragmentActivity.setCollapsingView(storiesRecyclerView);
feedStoriesViewModel.getList().observe(fragmentActivity, feedStoriesAdapter::submitList);
feedStoriesViewModel.getList().observe(getViewLifecycleOwner(), feedStoriesAdapter::submitList);
fetchStories();
}
private void fetchStories() {
final String cookie = settingsHelper.getString(Constants.COOKIE);
storiesFetching = true;
updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
storiesService.getFeedStories(CookieUtils.getCsrfTokenFromCookie(cookie), new ServiceCallback<List<FeedStoryModel>>() {
@Override
public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result);
storiesFetching = false;
if (storyListMenu != null) storyListMenu.setVisible(true);
updateSwipeRefreshState();
}

View File

@ -54,7 +54,6 @@ import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.asyncs.UsernameFetcher;
@ -75,6 +74,7 @@ import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.HighlightModel;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
@ -89,6 +89,7 @@ import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
@ -113,6 +114,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private Handler usernameSettingHandler;
private FriendshipService friendshipService;
private StoriesService storiesService;
private MediaService mediaService;
private boolean shouldRefresh = true;
private boolean hasStories = false;
private HighlightsAdapter highlightsAdapter;
@ -298,6 +300,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
fragmentActivity = (MainActivity) requireActivity();
friendshipService = FriendshipService.getInstance();
storiesService = StoriesService.getInstance();
mediaService = MediaService.getInstance();
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext()));
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
setHasOptionsMenu(true);
@ -370,10 +373,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
if (item.getItemId() == R.id.restrict) {
if (!isLoggedIn) return false;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
final String action = profileModel.isRestricted() ? "Unrestrict" : "Restrict";
friendshipService.toggleRestrict(
profileModel.getId(),
!profileModel.getRestricted(),
!profileModel.isRestricted(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoRestrictRootResponse>() {
@Override
@ -392,7 +395,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (item.getItemId() == R.id.block) {
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
if (!isLoggedIn) return false;
if (profileModel.getBlocked()) {
if (profileModel.isBlocked()) {
friendshipService.unblock(
userIdFromCookie,
profileModel.getId(),
@ -434,6 +437,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override
public void onRefresh() {
profileDetailsBinding.mainProfileImage.setVisibility(View.INVISIBLE);
fetchProfileDetails();
}
@ -571,44 +575,99 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
fetchStoryAndHighlights(profileId);
}
setupButtons(profileId, myId);
if (!profileId.equals(myId)) {
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
favoriteRepository.getFavorite(username.substring(1), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
profileDetailsBinding.favCb.setChecked(true);
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
}
profileDetailsBinding.favChip.setVisibility(View.VISIBLE);
final FavoriteRepository favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
profileDetailsBinding.favChip.setText(R.string.added_to_favs_short);
favoriteRepository.insertOrUpdateFavorite(new Favorite(
result.getId(),
profileModel.getUsername(),
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
result.getDateAdded()
), new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {}
@Override
public void onDataNotAvailable() {
profileDetailsBinding.favCb.setChecked(false);
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
}
});
} else {
profileDetailsBinding.favCb.setVisibility(View.GONE);
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {
profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
profileDetailsBinding.favChip.setText(R.string.add_to_favorites);
}
});
profileDetailsBinding.favChip.setOnClickListener(
v -> favoriteRepository.getFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
favoriteRepository.deleteFavorite(profileModel.getUsername(), FavoriteType.USER, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
profileDetailsBinding.favChip.setText(R.string.add_to_favorites);
profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24);
showSnackbar(getString(R.string.removed_from_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {
favoriteRepository.insertOrUpdateFavorite(new Favorite(
0,
profileModel.getUsername(),
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
new Date()
), new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
profileDetailsBinding.favChip.setText(R.string.added_to_favs);
profileDetailsBinding.favChip.setChipIconResource(R.drawable.ic_star_check_24);
showSnackbar(getString(R.string.added_to_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
}));
profileDetailsBinding.mainProfileImage.setImageURI(profileModel.getHdProfilePic());
profileDetailsBinding.mainProfileImage.setVisibility(View.VISIBLE);
final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount();
final Long followersCount = profileModel.getFollowersCount();
final Long followingCount = profileModel.getFollowingCount();
final String postCount = String.valueOf(profileModel.getPostCount());
SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count,
postCount));
SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline,
profileModel.getPostCount() > 2000000000L ? 2000000000 : profileModel.getPostCount().intValue(),
postCount));
span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0);
profileDetailsBinding.mainPostCount.setText(span);
profileDetailsBinding.mainPostCount.setVisibility(View.VISIBLE);
final String followersCountStr = String.valueOf(followersCount);
final int followersCountStrLen = followersCountStr.length();
span = new SpannableStringBuilder(getString(R.string.main_posts_followers,
followersCountStr));
span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_followers,
followersCount > 2000000000L ? 2000000000 : followersCount.intValue(),
followersCountStr));
span.setSpan(new RelativeSizeSpan(1.2f), 0, followersCountStrLen, 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followersCountStrLen, 0);
profileDetailsBinding.mainFollowers.setText(span);
profileDetailsBinding.mainFollowers.setVisibility(View.VISIBLE);
final String followingCountStr = String.valueOf(followingCount);
final int followingCountStrLen = followingCountStr.length();
@ -617,6 +676,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
span.setSpan(new RelativeSizeSpan(1.2f), 0, followingCountStrLen, 0);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, followingCountStrLen, 0);
profileDetailsBinding.mainFollowing.setText(span);
profileDetailsBinding.mainFollowing.setVisibility(View.VISIBLE);
profileDetailsBinding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername()
: profileModel.getName());
@ -640,6 +700,51 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
.trim()));
profileDetailsBinding.mainBiography
.addOnURLClickListener(autoLinkItem -> Utils.openURL(getContext(), autoLinkItem.getOriginalText().trim()));
profileDetailsBinding.mainBiography.setOnClickListener(v -> {
String[] commentDialogList;
if (!TextUtils.isEmpty(cookie)) {
commentDialogList = new String[]{
getResources().getString(R.string.bio_copy),
getResources().getString(R.string.bio_translate)
};
} else {
commentDialogList = new String[]{
getResources().getString(R.string.bio_copy)
};
}
new AlertDialog.Builder(context)
.setItems(commentDialogList, (d,w) -> {
switch (w) {
case 0:
Utils.copyText(context, biography);
break;
case 1:
mediaService.translate(profileModel.getId(), "3", new ServiceCallback<String>() {
@Override
public void onSuccess(final String result) {
if (TextUtils.isEmpty(result)) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
new AlertDialog.Builder(context)
.setTitle(profileModel.getUsername())
.setMessage(result)
.setPositiveButton(R.string.ok, null)
.show();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error translating bio", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
}
});
break;
}
})
.setNegativeButton(R.string.cancel, null)
.show();
});
profileDetailsBinding.mainBiography.setOnLongClickListener(v -> {
Utils.copyText(context, biography);
return true;
@ -687,6 +792,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
private void setupButtons(final String profileId, final String myId) {
profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (isLoggedIn) {
if (profileId.equals(myId)) {
profileDetailsBinding.btnTagged.setVisibility(View.VISIBLE);
@ -696,15 +802,29 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
profileDetailsBinding.btnSaved.setText(R.string.saved);
return;
}
profileDetailsBinding.btnTagged.setVisibility(View.GONE);
profileDetailsBinding.btnSaved.setVisibility(View.GONE);
profileDetailsBinding.btnLiked.setVisibility(View.GONE);
profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism?
profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE);
if (profileModel.getFollowing()) {
if (profileModel.isFollowing() || profileModel.isFollower()) {
profileDetailsBinding.mainStatus.setVisibility(View.VISIBLE);
if (!profileModel.isFollowing()) {
profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.blue_800));
profileDetailsBinding.mainStatus.setText(R.string.status_follower);
}
else if (!profileModel.isFollower()) {
profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.deep_orange_800));
profileDetailsBinding.mainStatus.setText(R.string.status_following);
}
else {
profileDetailsBinding.mainStatus.setChipBackgroundColor(getResources().getColorStateList(R.color.green_800));
profileDetailsBinding.mainStatus.setText(R.string.status_mutual);
}
}
if (profileModel.isFollowing()) {
profileDetailsBinding.btnFollow.setText(R.string.unfollow);
profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else if (profileModel.getRequested()) {
} else if (profileModel.isRequested()) {
profileDetailsBinding.btnFollow.setText(R.string.cancel);
profileDetailsBinding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24);
} else {
@ -713,16 +833,15 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
if (restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
if (profileModel.isRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
}
}
profileDetailsBinding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE);
if (blockMenuItem != null) {
blockMenuItem.setVisible(true);
if (profileModel.getBlocked()) {
if (profileModel.isBlocked()) {
blockMenuItem.setTitle(R.string.unblock);
} else {
blockMenuItem.setTitle(R.string.block);
@ -732,7 +851,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
if (!profileModel.isReallyPrivate() && restrictMenuItem != null) {
restrictMenuItem.setVisible(true);
if (profileModel.getRestricted()) {
if (profileModel.isRestricted()) {
restrictMenuItem.setTitle(R.string.unrestrict);
} else {
restrictMenuItem.setTitle(R.string.restrict);
@ -760,20 +879,55 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
Log.e(TAG, "Error", t);
}
});
new HighlightsFetcher(profileId,
result -> {
highlightsFetching = false;
if (result != null) {
profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
} else profileDetailsBinding.highlightsList.setVisibility(View.GONE);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
storiesService.fetchHighlights(profileId,
new ServiceCallback<List<HighlightModel>>() {
@Override
public void onSuccess(final List<HighlightModel> result) {
highlightsFetching = false;
if (result != null) {
profileDetailsBinding.highlightsList.setVisibility(View.VISIBLE);
highlightsViewModel.getList().postValue(result);
}
else profileDetailsBinding.highlightsList.setVisibility(View.GONE);
}
@Override
public void onFailure(final Throwable t) {
profileDetailsBinding.highlightsList.setVisibility(View.GONE);
Log.e(TAG, "Error", t);
}
});
}
private void setupCommonListeners() {
final Context context = getContext();
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
profileDetailsBinding.btnFollow.setOnClickListener(v -> {
if (profileModel.getFollowing() || profileModel.getRequested()) {
if (profileModel.isFollowing() && profileModel.isPrivate()) {
new AlertDialog.Builder(context)
.setTitle(R.string.priv_acc)
.setMessage(R.string.priv_acc_confirm)
.setPositiveButton(R.string.confirm, (d, w) ->
friendshipService.unfollow(
userIdFromCookie,
profileModel.getId(),
CookieUtils.getCsrfTokenFromCookie(cookie),
new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) {
// Log.d(TAG, "Unfollow success: " + result);
onRefresh();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "Error unfollowing", t);
}
}))
.setNegativeButton(R.string.cancel, null)
.show();
}
else if (profileModel.isFollowing() || profileModel.isRequested()) {
friendshipService.unfollow(
userIdFromCookie,
profileModel.getId(),
@ -854,72 +1008,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (which == 1) {
// show stories
final NavDirections action = ProfileFragmentDirections
.actionProfileFragmentToStoryViewerFragment(-1, null, false, false, profileModel.getId(), username);
.actionProfileFragmentToStoryViewerFragment(-1, null, false, false, profileModel.getId(), username, false, false);
NavHostFragment.findNavController(this).navigate(action);
return;
}
showProfilePicDialog();
};
final Context context = getContext();
if (context == null) return;
new AlertDialog.Builder(context)
.setItems(options, profileDialogListener)
.setNegativeButton(R.string.cancel, null)
.show();
});
profileDetailsBinding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> {
// do not do anything if state matches the db, as listener is set before profile details are set
final Context context = getContext();
if (context == null) return;
final String finalUsername = username.startsWith("@") ? username.substring(1) : username;
favoriteRepository.getFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
if (isChecked) return; // already a fav
buttonView.setVisibility(View.GONE);
profileDetailsBinding.favProgress.setVisibility(View.VISIBLE);
favoriteRepository.deleteFavorite(finalUsername, FavoriteType.USER, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24);
profileDetailsBinding.favProgress.setVisibility(View.GONE);
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
showSnackbar(getString(R.string.removed_from_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
@Override
public void onDataNotAvailable() {
if (!isChecked) return; // not in fav already
buttonView.setVisibility(View.GONE);
profileDetailsBinding.favProgress.setVisibility(View.VISIBLE);
final Favorite model = new Favorite(
-1,
finalUsername,
FavoriteType.USER,
profileModel.getName(),
profileModel.getSdProfilePic(),
new Date()
);
favoriteRepository.insertOrUpdateFavorite(model, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
profileDetailsBinding.favCb.setButtonDrawable(R.drawable.ic_star_check_24);
profileDetailsBinding.favProgress.setVisibility(View.GONE);
profileDetailsBinding.favCb.setVisibility(View.VISIBLE);
showSnackbar(getString(R.string.added_to_favs));
}
@Override
public void onDataNotAvailable() {}
});
}
});
});
}
private void showSnackbar(final String message) {
@ -951,7 +1051,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void setupPosts() {
binding.postsRecyclerView.setViewModelStoreOwner(this)
.setLifeCycleOwner(this)
.setPostFetchService(new ProfilePostFetchService(profileModel))
.setPostFetchService(new ProfilePostFetchService(profileModel, isLoggedIn))
.setLayoutPreferences(layoutPreferences)
.addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
.setFeedItemCallback(feedItemCallback)
@ -969,7 +1069,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
highlightsViewModel = new ViewModelProvider(fragmentActivity).get(HighlightsViewModel.class);
highlightsAdapter = new HighlightsAdapter((model, position) -> {
final NavDirections action = ProfileFragmentDirections
.actionProfileFragmentToStoryViewerFragment(position, model.getTitle(), false, false, null, null);
.actionProfileFragmentToStoryViewerFragment(position, model.getTitle(), false, false, null, null, false, false);
NavHostFragment.findNavController(this).navigate(action);
});
final Context context = getContext();

View File

@ -88,9 +88,10 @@ public class AboutFragment extends BasePreferencesFragment {
preference.setSummary(R.string.about_feedback_summary);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse(getString(R.string.about_feedback_summary)
+ "?body=Please%20note%20that%20your%20email%20address%20and%20the%20entire%20content%20will%20be%20published%20onto%20GitHub%20issues.%20If%20you%20do%20not%20wish%20to%20do%20that%2C%20use%20other%20contact%20methods%20instead."));
final Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("message/rfc822")
.putExtra(Intent.EXTRA_EMAIL, getString(R.string.about_feedback_summary))
.putExtra(Intent.EXTRA_TEXT, "Please note that your email address and the entire content will be published onto GitHub issues. If you do not wish to do that, use other contact methods instead.");
if (intent.resolveActivity(context.getPackageManager()) != null) startActivity(intent);
return true;
});

View File

@ -26,10 +26,30 @@ public class BackupPreferencesFragment extends BasePreferencesFragment {
if (context == null) {
return;
}
screen.addPreference(getAboutPreference(context));
screen.addPreference(getWarningPreference(context));
screen.addPreference(getCreatePreference(context));
screen.addPreference(getRestorePreference(context));
}
private Preference getAboutPreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setSummary(R.string.backup_summary);
preference.setEnabled(false);
preference.setIcon(R.drawable.ic_outline_info_24);
preference.setIconSpaceReserved(true);
return preference;
}
private Preference getWarningPreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setSummary(R.string.backup_warning);
preference.setEnabled(false);
preference.setIcon(R.drawable.ic_warning);
preference.setIconSpaceReserved(true);
return preference;
}
private Preference getCreatePreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.create_backup);

View File

@ -134,7 +134,18 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
screen.addPreference(getDivider(context));
if (isLoggedIn) {
screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment);
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif");
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml");
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive");
NavHostFragment.findNavController(this).navigate(navDirections);
return true;
}));
}

View File

@ -79,6 +79,7 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
screen.addPreference(loggedInUsersPreferenceCategory);
loggedInUsersPreferenceCategory.setIconSpaceReserved(false);
loggedInUsersPreferenceCategory.setTitle(R.string.login_settings);
loggedInUsersPreferenceCategory.addPreference(getStorySortPreference());
loggedInUsersPreferenceCategory.addPreference(getMarkStoriesSeenPreference());
loggedInUsersPreferenceCategory.addPreference(getMarkDMSeenPreference());
loggedInUsersPreferenceCategory.addPreference(getEnableActivityNotificationsPreference());
@ -204,6 +205,25 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment {
return preference;
}
private Preference getStorySortPreference() {
final Context context = getContext();
if (context == null) return null;
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final int length = getResources().getStringArray(R.array.story_sorts).length;
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
values[i] = String.valueOf(i);
}
preference.setKey(Constants.STORY_SORT);
preference.setTitle(R.string.story_sort_setting);
preference.setDialogTitle(R.string.story_sort_setting);
preference.setEntries(R.array.story_sorts);
preference.setIconSpaceReserved(false);
preference.setEntryValues(values);
return preference;
}
private Preference getMarkStoriesSeenPreference() {
final Context context = getContext();
if (context == null) return null;

View File

@ -11,16 +11,12 @@ import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils;
public abstract class BasePostModel implements Serializable, Selectable {
protected String postId;
protected String displayUrl;
protected String shortCode;
protected String postId, displayUrl, shortCode, captionId;
protected CharSequence postCaption;
protected MediaItemType itemType;
protected boolean isSelected;
protected boolean isDownloaded;
protected boolean isSelected, isDownloaded;
protected long timestamp;
boolean liked;
boolean saved;
boolean liked, saved;
public boolean getLike() {
return liked;
@ -46,6 +42,10 @@ public abstract class BasePostModel implements Serializable, Selectable {
return postCaption;
}
public final String getCaptionId() {
return captionId;
}
public final String getShortCode() {
return shortCode;
}

View File

@ -11,11 +11,10 @@ public class CommentModel {
private final ProfileModel profileModel;
private final String id;
private final String text;
private final long likes;
private long likes;
private final long timestamp;
private List<CommentModel> childCommentModels;
private final boolean liked;
private boolean hasNextPage;
private boolean liked, hasNextPage;
private String endCursor;
public CommentModel(final String id,
@ -53,6 +52,11 @@ public class CommentModel {
return liked;
}
public void setLiked(boolean liked) {
this.likes = liked ? likes + 1 : likes - 1;
this.liked = liked;
}
public ProfileModel getProfileModel() {
return profileModel;
}

View File

@ -24,7 +24,7 @@ public final class FeedModel extends PostModel {
private String displayUrl;
private String thumbnailUrl;
private String shortCode;
private String postCaption;
private String postCaption, captionId;
private long commentsCount;
private long timestamp;
private boolean liked;
@ -76,6 +76,11 @@ public final class FeedModel extends PostModel {
return this;
}
public Builder setCaptionId(final String captionId) {
this.captionId = captionId;
return this;
}
public Builder setCommentsCount(final long commentsCount) {
this.commentsCount = commentsCount;
return this;
@ -127,8 +132,8 @@ public final class FeedModel extends PostModel {
}
public FeedModel build() {
return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, commentsCount,
timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth);
return new FeedModel(profileModel, itemType, viewCount, postId, displayUrl, thumbnailUrl, shortCode, postCaption, captionId,
commentsCount, timestamp, liked, bookmarked, likesCount, locationName, locationId, sliderItems, imageHeight, imageWidth);
}
}
@ -140,6 +145,7 @@ public final class FeedModel extends PostModel {
final String thumbnailUrl,
final String shortCode,
final String postCaption,
final String captionId,
final long commentsCount,
final long timestamp,
final boolean liked,
@ -150,7 +156,7 @@ public final class FeedModel extends PostModel {
final List<PostChild> sliderItems,
final int imageHeight,
final int imageWidth) {
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, timestamp, liked, bookmarked);
super(itemType, postId, displayUrl, thumbnailUrl, shortCode, postCaption, captionId, timestamp, liked, bookmarked);
this.profileModel = profileModel;
this.commentsCount = commentsCount;
this.likesCount = likesCount;

View File

@ -1,36 +1,66 @@
package awais.instagrabber.models;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Date;
import awais.instagrabber.utils.Utils;
public final class FeedStoryModel implements Serializable {
private final String storyMediaId;
private final ProfileModel profileModel;
private StoryModel[] storyModels;
private boolean fullyRead;
private final StoryModel firstStoryModel;
private Boolean fullyRead;
private final long timestamp;
private final int mediaCount;
public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, final boolean fullyRead) {
public FeedStoryModel(final String storyMediaId, final ProfileModel profileModel, final boolean fullyRead,
final long timestamp, final StoryModel firstStoryModel, final int mediaCount) {
this.storyMediaId = storyMediaId;
this.profileModel = profileModel;
this.fullyRead = fullyRead;
this.timestamp = timestamp;
this.firstStoryModel = firstStoryModel;
this.mediaCount = mediaCount;
}
public String getStoryMediaId() {
return storyMediaId;
}
public long getTimestamp() {
return timestamp;
}
@NonNull
public String getDateTime() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
}
public int getMediaCount() {
return mediaCount;
}
public ProfileModel getProfileModel() {
return profileModel;
}
public void setStoryModels(final StoryModel[] storyModels) {
this.storyModels = storyModels;
// public void setFirstStoryModel(final StoryModel firstStoryModel) {
// this.firstStoryModel = firstStoryModel;
// }
public StoryModel getFirstStoryModel() {
return firstStoryModel;
}
public StoryModel[] getStoryModels() {
return storyModels;
}
public boolean getFullyRead() {
public Boolean isFullyRead() {
return fullyRead;
}
public void setFullyRead(final boolean fullyRead) {
this.fullyRead = fullyRead;
}
}

View File

@ -29,7 +29,7 @@ public final class HashtagModel implements Serializable {
return sdProfilePic;
}
public long getPostCount() { return postCount; }
public Long getPostCount() { return postCount; }
public boolean getFollowing() { return following; }
}

View File

@ -1,16 +1,28 @@
package awais.instagrabber.models;
import androidx.annotation.NonNull;
import java.util.Date;
import awais.instagrabber.utils.Utils;
public final class HighlightModel {
private final String title;
private final String id;
private final String thumbnailUrl;
private final long timestamp;
private final int mediaCount;
public HighlightModel(final String title,
final String id,
final String thumbnailUrl) {
final String thumbnailUrl,
final long timestamp,
final int mediaCount) {
this.title = title;
this.id = id;
this.thumbnailUrl = thumbnailUrl;
this.timestamp = timestamp;
this.mediaCount = mediaCount;
}
public String getTitle() {
@ -24,4 +36,17 @@ public final class HighlightModel {
public String getThumbnailUrl() {
return thumbnailUrl;
}
public long getTimestamp() {
return timestamp;
}
@NonNull
public String getDateTime() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
}
public int getMediaCount() {
return mediaCount;
}
}

View File

@ -59,5 +59,5 @@ public final class LocationModel implements Serializable {
return sdProfilePic;
}
public long getPostCount() { return postCount; }
public Long getPostCount() { return postCount; }
}

View File

@ -13,7 +13,7 @@ public final class NotificationModel {
private final String userId;
private final String username;
private final String profilePicUrl;
private final String shortCode;
private final String postId;
private final String previewUrl;
private final NotificationType type;
private final CharSequence text;
@ -25,7 +25,7 @@ public final class NotificationModel {
final String userId,
final String username,
final String profilePicUrl,
final String shortCode,
final String postId,
final String previewUrl,
final NotificationType type) {
this.id = id;
@ -34,7 +34,7 @@ public final class NotificationModel {
this.userId = userId;
this.username = username;
this.profilePicUrl = profilePicUrl;
this.shortCode = shortCode;
this.postId = postId;
this.previewUrl = previewUrl;
this.type = type;
}
@ -47,6 +47,10 @@ public final class NotificationModel {
return text;
}
public long getTimestamp() {
return timestamp;
}
@NonNull
public String getDateTime() {
return Utils.datetimeParser.format(new Date(timestamp * 1000L));
@ -64,8 +68,8 @@ public final class NotificationModel {
return profilePicUrl;
}
public String getShortCode() {
return shortCode;
public String getPostId() {
return postId;
}
public String getPreviewPic() {

View File

@ -19,6 +19,7 @@ public class PostModel extends BasePostModel {
final String thumbnailUrl,
final String shortCode,
final CharSequence postCaption,
final String captionId,
long timestamp,
boolean liked,
boolean bookmarked) {
@ -28,6 +29,7 @@ public class PostModel extends BasePostModel {
this.thumbnailUrl = thumbnailUrl;
this.shortCode = shortCode;
this.postCaption = postCaption;
this.captionId = captionId;
this.timestamp = timestamp;
this.liked = liked;
this.saved = bookmarked;

View File

@ -3,15 +3,15 @@ package awais.instagrabber.models;
import java.io.Serializable;
public final class ProfileModel implements Serializable {
private final boolean isPrivate, reallyPrivate, isVerified, following, restricted, blocked, requested;
private final boolean isPrivate, reallyPrivate, isVerified, following, follower, restricted, blocked, requested;
private final long postCount, followersCount, followingCount;
private final String id, username, name, biography, url, sdProfilePic, hdProfilePic;
public ProfileModel(final boolean isPrivate, final boolean reallyPrivate,
final boolean isVerified, final String id, final String username, final String name, final String biography,
final String url, final String sdProfilePic, final String hdProfilePic, final long postCount,
final long followersCount, final long followingCount, final boolean following, final boolean restricted,
final boolean blocked, final boolean requested) {
final long followersCount, final long followingCount, final boolean following, final boolean follower,
final boolean restricted, final boolean blocked, final boolean requested) {
this.isPrivate = isPrivate;
this.reallyPrivate = reallyPrivate;
this.isVerified = isVerified;
@ -26,21 +26,22 @@ public final class ProfileModel implements Serializable {
this.followersCount = followersCount;
this.followingCount = followingCount;
this.following = following;
this.follower = follower;
this.restricted = restricted;
this.blocked = blocked;
this.requested = requested;
}
public static ProfileModel getDefaultProfileModel() {
return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false, false);
}
public static ProfileModel getDefaultProfileModel(final String userId) {
return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false, false);
}
public static ProfileModel getDefaultProfileModel(final String userId, final String username) {
return new ProfileModel(false, false, false, userId, username, null, null, null, null, null, 0, 0, 0, false, false, false, false);
return new ProfileModel(false, false, false, userId, username, null, null, null, null, null, 0, 0, 0, false, false, false, false, false);
}
public boolean isPrivate() {
@ -83,31 +84,35 @@ public final class ProfileModel implements Serializable {
return hdProfilePic;
}
public long getPostCount() {
public Long getPostCount() {
return postCount;
}
public long getFollowersCount() {
public Long getFollowersCount() {
return followersCount;
}
public long getFollowingCount() {
public Long getFollowingCount() {
return followingCount;
}
public boolean getFollowing() {
public boolean isFollowing() {
return following;
}
public boolean getRestricted() {
public boolean isFollower() {
return follower;
}
public boolean isRestricted() {
return restricted;
}
public boolean getBlocked() {
public boolean isBlocked() {
return blocked;
}
public boolean getRequested() {
public boolean isRequested() {
return requested;
}
}

View File

@ -6,11 +6,13 @@ import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.models.stickers.SliderModel;
import awais.instagrabber.models.stickers.SwipeUpModel;
public final class StoryModel implements Serializable {
private final String storyMediaId;
private final String storyUrl;
private String thumbnail;
private final String username;
private final String userId;
private final MediaItemType itemType;
@ -21,6 +23,7 @@ public final class StoryModel implements Serializable {
private String spotify;
private PollModel poll;
private QuestionModel question;
private SliderModel slider;
private QuizModel quiz;
private SwipeUpModel swipeUp;
private String[] mentions;
@ -28,10 +31,11 @@ public final class StoryModel implements Serializable {
private boolean isCurrentSlide = false;
private final boolean canReply;
public StoryModel(final String storyMediaId, final String storyUrl, final MediaItemType itemType,
public StoryModel(final String storyMediaId, final String storyUrl, final String thumbnail, final MediaItemType itemType,
final long timestamp, final String username, final String userId, final boolean canReply) {
this.storyMediaId = storyMediaId;
this.storyUrl = storyUrl;
this.thumbnail = thumbnail;
this.itemType = itemType;
this.timestamp = timestamp;
this.username = username;
@ -43,6 +47,10 @@ public final class StoryModel implements Serializable {
return storyUrl;
}
public String getThumbnail() {
return thumbnail;
}
public String getStoryMediaId() {
return storyMediaId;
}
@ -71,6 +79,10 @@ public final class StoryModel implements Serializable {
return question;
}
public SliderModel getSlider() {
return slider;
}
public QuizModel getQuiz() {
return quiz;
}
@ -85,6 +97,10 @@ public final class StoryModel implements Serializable {
return position;
}
public void setThumbnail(final String thumbnail) {
this.thumbnail = thumbnail;
}
public void setVideoUrl(final String videoUrl) {
this.videoUrl = videoUrl;
}
@ -109,6 +125,10 @@ public final class StoryModel implements Serializable {
this.question = question;
}
public void setSlider(final SliderModel slider) {
this.slider = slider;
}
public void setQuiz(final QuizModel quiz) {
this.quiz = quiz;
}

View File

@ -5,12 +5,20 @@ import java.util.HashMap;
import java.util.Map;
public enum NotificationType implements Serializable {
// web
LIKE("GraphLikeAggregatedStory"),
FOLLOW("GraphFollowAggregatedStory"),
COMMENT("GraphCommentMediaStory"),
MENTION("GraphMentionStory"),
TAGGED("GraphUserTaggedStory"),
REQUEST("REQUEST");
// app story_type
COMMENT_LIKE("13"),
TAGGED_COMMENT("14"),
RESPONDED_STORY("213"),
// efr - random value
REQUEST("REQUEST"),
// ayml - random value
AYML("AYML");
private final String itemType;
private static final Map<String, NotificationType> map = new HashMap<>();

View 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;
}
}

View File

@ -3,10 +3,12 @@ package awais.instagrabber.repositories;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;
public interface FeedRepository {
@GET("/graphql/query/")
Call<String> fetch(@QueryMap(encoded = true) Map<String, String> queryParams);
@FormUrlEncoded
@POST("/api/v1/feed/timeline/")
Call<String> fetch(@FieldMap final Map<String, String> signedForm);
}

View File

@ -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);
}

View File

@ -12,7 +12,4 @@ public interface LocationRepository {
@GET("/api/v1/feed/location/{location}/")
Call<String> fetchPosts(@Path("location") final String locationId,
@QueryMap Map<String, String> queryParams);
@GET("/graphql/query/")
Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams);
}

View File

@ -5,48 +5,57 @@ import java.util.Map;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.QueryMap;
public interface MediaRepository {
@GET("/api/v1/media/{mediaId}/info/")
Call<String> fetch(@Path("mediaId") final String mediaId);
@GET("/api/v1/media/{mediaId}/{action}/")
Call<String> fetchLikes(@Path("mediaId") final String mediaId,
@Path("action") final String action); // one of "likers" or "comment_likers"
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/{action}/")
Call<String> action(@Header("User-Agent") final String userAgent,
@Path("action") final String action,
Call<String> action(@Path("action") final String action,
@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/")
Call<String> comment(@Header("User-Agent") final String userAgent,
@Path("mediaId") final String mediaId,
Call<String> comment(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/comment/bulk_delete/")
Call<String> commentsBulkDelete(@Header("User-Agent") final String userAgent,
@Path("mediaId") final String mediaId,
Call<String> commentsBulkDelete(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_like/")
Call<String> commentLike(@Header("User-Agent") final String userAgent,
@Path("commentId") final String commentId,
Call<String> commentLike(@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{commentId}/comment_unlike/")
Call<String> commentUnlike(@Header("User-Agent") final String userAgent,
@Path("commentId") final String commentId,
Call<String> commentUnlike(@Path("commentId") final String commentId,
@FieldMap final Map<String, String> signedForm);
@FormUrlEncoded
@POST("/api/v1/media/{mediaId}/edit_media/")
Call<String> editCaption(@Path("mediaId") final String mediaId,
@FieldMap final Map<String, String> signedForm);
@GET("/api/v1/language/translate/")
Call<String> translate(@QueryMap final Map<String, String> form);
@FormUrlEncoded
@POST("/api/v1/media/upload_finish/")
Call<String> uploadFinish(// @Header("User-Agent") final String userAgent,
@Header("retry_context") final String retryContext,
Call<String> uploadFinish(@Header("retry_context") final String retryContext,
@QueryMap Map<String, String> queryParams,
@FieldMap final Map<String, String> signedForm);
}

View File

@ -6,16 +6,24 @@ import awais.instagrabber.utils.Constants;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.Query;
public interface NewsRepository {
Call<String> inbox();
@Headers("User-Agent: " + Constants.USER_AGENT)
@GET("https://www.instagram.com/accounts/activity/?__a=1")
Call<String> webInbox();
@Headers("User-Agent: " + Constants.I_USER_AGENT)
@GET("/api/v1/news/inbox/")
Call<String> appInbox(@Query(value = "mark_as_seen", encoded = true) boolean markAsSeen);
@FormUrlEncoded
@Headers("User-Agent: " + Constants.USER_AGENT)
@POST("https://www.instagram.com/web/activity/mark_checked/")
Call<String> markChecked(@Header("x-csrftoken") String csrfToken, @FieldMap Map<String, String> map);
@Headers("User-Agent: " + Constants.I_USER_AGENT)
@POST("/api/v1/discover/ayml/")
Call<String> getAyml(@FieldMap final Map<String, String> form);
}

View File

@ -9,11 +9,11 @@ import retrofit2.http.QueryMap;
public interface ProfileRepository {
@GET("api/v1/users/{uid}/info/")
@GET("/api/v1/users/{uid}/info/")
Call<String> getUserInfo(@Path("uid") final String uid);
@GET("/graphql/query/")
Call<String> fetch(@QueryMap Map<String, String> queryMap);
@GET("/api/v1/feed/user/{uid}/")
Call<String> fetch(@Path("uid") final String uid, @QueryMap Map<String, String> queryParams);
@GET("/api/v1/feed/saved/")
Call<String> fetchSaved(@QueryMap Map<String, String> queryParams);

View File

@ -2,17 +2,40 @@ package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.StoryStickerResponse;
import retrofit2.Call;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.Path;
import retrofit2.http.POST;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;
public interface StoriesRepository {
@GET("/api/v1/media/{mediaId}/info/")
Call<String> fetch(@Path("mediaId") final String mediaId);
// this one is the same as MediaRepository.fetch BUT you need to make sure it's a story
@GET("graphql/query/")
Call<String> getStories(@QueryMap(encoded = true) Map<String, String> variables);
@GET("/api/v1/feed/reels_tray/")
Call<String> getFeedStories();
@GET("/api/v1/highlights/{uid}/highlights_tray/")
Call<String> fetchHighlights(@Path("uid") final String uid);
@GET("/api/v1/archive/reel/day_shells/")
Call<String> fetchArchive(@QueryMap Map<String, String> queryParams);
@GET
Call<String> getUserStory(@Header("User-Agent") String userAgent, @Url String url);
@FormUrlEncoded
@POST("/api/v1/media/{storyId}/{stickerId}/{action}/")
Call<StoryStickerResponse> respondToSticker(@Header("User-Agent") String userAgent,
@Path("storyId") String storyId,
@Path("stickerId") String stickerId,
@Path("action") String action,
// story_poll_vote, story_question_response, story_slider_vote, story_quiz_answer
@FieldMap Map<String, String> form);
}

View File

@ -24,7 +24,4 @@ public interface TagsRepository {
@GET("/api/v1/feed/tag/{tag}/")
Call<String> fetchPosts(@Path("tag") final String tag,
@QueryMap Map<String, String> queryParams);
@GET("/graphql/query/")
Call<String> fetchGraphQLPosts(@QueryMap(encoded = true) Map<String, String> queryParams);
}

View File

@ -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 +
'}';
}
}

View File

@ -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 + '\'' +
'}';
}
}

View File

@ -2,15 +2,18 @@ package awais.instagrabber.repositories.responses;
public class UserInfo {
private final String pk;
private final String username;
private final String fullName;
private final String profilePicUrl;
private final String username, fullName, profilePicUrl, hdProfilePicUrl;
public UserInfo(final String pk, final String username, final String fullName, final String profilePicUrl) {
public UserInfo(final String pk,
final String username,
final String fullName,
final String profilePicUrl,
final String hdProfilePicUrl) {
this.pk = pk;
this.username = username;
this.fullName = fullName;
this.profilePicUrl = profilePicUrl;
this.hdProfilePicUrl = hdProfilePicUrl;
}
public String getPk() {
@ -29,6 +32,10 @@ public class UserInfo {
return profilePicUrl;
}
public String getHDProfilePicUrl() {
return hdProfilePicUrl;
}
@Override
public String toString() {
return "UserInfo{" +
@ -36,6 +43,7 @@ public class UserInfo {
", username='" + username + '\'' +
", fullName='" + fullName + '\'' +
", profilePicUrl='" + profilePicUrl + '\'' +
", hdProfilePicUrl='" + hdProfilePicUrl + '\'' +
'}';
}
}

View File

@ -99,11 +99,11 @@ public class DirectUser {
profileModel.getSdProfilePic(),
null,
new DirectUserFriendshipStatus(
profileModel.getFollowing(),
profileModel.getBlocked(),
profileModel.isFollowing(),
profileModel.isBlocked(),
profileModel.isPrivate(),
false,
profileModel.getRequested(),
profileModel.isRequested(),
false
),
profileModel.isVerified(),

View File

@ -8,6 +8,7 @@ public final class Constants {
public static final String CUSTOM_DATE_TIME_FORMAT = "date_time_custom_format";
public static final String APP_THEME = "app_theme_v19";
public static final String APP_LANGUAGE = "app_language_v19";
public static final String STORY_SORT = "story_sort";
// int prefs
public static final String PREV_INSTALL_VERSION = "prevVersion";
// boolean prefs
@ -56,9 +57,9 @@ public final class Constants {
// spoof
public static final String USER_AGENT = "Mozilla/5.0 (Linux; Android 8.1.0; motorola one Build/OPKS28.63-18-3; wv) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 " +
"Instagram 166.1.0.42.245 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 256099205)";
"Instagram 169.1.0.29.135 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 262886998)";
public static final String I_USER_AGENT =
"Instagram 166.1.0.42.245 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 256099205)";
"Instagram 169.1.0.29.135 Android (27/8.1.0; 320dpi; 720x1362; motorola; motorola one; deen_sprout; qcom; pt_BR; 262886998)";
public static final String A_USER_AGENT = "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me";
// see https://github.com/dilame/instagram-private-api/blob/master/src/core/constants.ts
public static final String SUPPORTED_CAPABILITIES = "[ { \"name\": \"SUPPORTED_SDK_VERSIONS\", \"value\":" +

View File

@ -152,7 +152,7 @@ public final class ExportImportUtils {
continue;
}
final Favorite favorite = new Favorite(
-1,
0,
query,
favoriteType,
favsObject.optString("s"),

View File

@ -73,6 +73,10 @@ public final class LocaleUtils {
if (appLanguageIndex == 14) return "zh_TW";
if (appLanguageIndex == 15) return "ca";
if (appLanguageIndex == 16) return "ru";
if (appLanguageIndex == 17) return "hi";
if (appLanguageIndex == 18) return "nl";
if (appLanguageIndex == 19) return "sk";
if (appLanguageIndex == 20) return "ja";
return null;
}

View File

@ -20,11 +20,17 @@ import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadDirectStory;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadLastSeenAt;
import awais.instagrabber.repositories.responses.directmessages.ImageVersions2;
import awais.instagrabber.repositories.responses.directmessages.MediaCandidate;
import awais.instagrabber.models.stickers.PollModel;
import awais.instagrabber.models.stickers.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.models.stickers.SliderModel;
import awais.instagrabber.models.stickers.SwipeUpModel;
import awaisomereport.LogCollector;
public final class ResponseBodyUtils {
@ -186,8 +192,8 @@ public final class ResponseBodyUtils {
return null;
}
// public static DirectItemMediaModel getDirectMediaModel(final JSONObject mediaObj) throws Exception {
// final DirectItemMediaModel mediaModel;
// public static DirectItemModel.DirectItemMediaModel getDirectMediaModel(final JSONObject mediaObj) throws Exception {
// final DirectItemModel.DirectItemMediaModel mediaModel;
// if (mediaObj == null) mediaModel = null;
// else {
// final JSONObject userObj = mediaObj.optJSONObject("user");
@ -203,7 +209,7 @@ public final class ResponseBodyUtils {
// userObj.getString("full_name"),
// null, null,
// userObj.getString("profile_pic_url"),
// null, 0, 0, 0, false, false, false, false);
// null, 0, 0, 0, false, false, false, false, false);
// }
//
// final MediaItemType mediaType = getMediaItemType(mediaObj.optInt("media_type", -1));
@ -212,7 +218,7 @@ public final class ResponseBodyUtils {
// if (TextUtils.isEmpty(id)) id = null;
//
// final ThumbnailDetails thumbnailDetails = getThumbnailUrl(mediaObj, mediaType);
// mediaModel = new DirectItemMediaModel(
// mediaModel = new DirectItemModel.DirectItemMediaModel(
// mediaType,
// mediaObj.optLong("expiring_at"),
// mediaObj.optLong("pk"),
@ -226,7 +232,7 @@ public final class ResponseBodyUtils {
// }
// return mediaModel;
// }
//
// private static DirectItemType getDirectItemType(final String itemType) {
// if ("placeholder".equals(itemType)) return DirectItemType.PLACEHOLDER;
// if ("media".equals(itemType)) return DirectItemType.MEDIA;
@ -245,81 +251,99 @@ public final class ResponseBodyUtils {
// if ("felix_share".equals(itemType)) return DirectItemType.FELIX_SHARE;
// return DirectItemType.TEXT;
// }
//
// @NonNull
// public static InboxThreadModel createInboxThreadModel(@NonNull final JSONObject data, final boolean inThreadView) throws Exception {
// final InboxReadState readState = data.getInt("read_state") == 0 ? InboxReadState.STATE_READ : InboxReadState.STATE_UNREAD;
// final String threadType = data.getString("thread_type"); // they're all "private", group is identified by boolean "is_group"
//
// final JSONArray leftUsersJsonArray = data.optJSONArray("left_users");
// final String threadId = data.getString("thread_id");
// final String threadV2Id = data.getString("thread_v2_id");
// final String threadTitle = data.getString("thread_title");
//
// final JSONArray usersJsonArray = data.getJSONArray("users");
// final List<ProfileModel> users = new ArrayList<>();
// for (int j = 0; j < usersJsonArray.length(); ++j) {
// final JSONObject userObject = usersJsonArray.getJSONObject(j);
// users.add(new ProfileModel(userObject.optBoolean("is_private"),
// false,
// userObject.optBoolean("is_verified"),
// String.valueOf(userObject.get("pk")),
// userObject.optString("username"), // optional cuz fb users
// userObject.getString("full_name"),
// null,
// null,
// userObject.getString("profile_pic_url"),
// null,
// 0,
// 0,
// 0,
// false,
// false,
// false,
// false));
// }
// final String threadNewestCursor = data.optString("newest_cursor");
// final String threadOldestCursor = data.optString("oldest_cursor");
// final String threadNextCursor = data.has("next_cursor") ? data.getString("next_cursor") : null;
// final String threadPrevCursor = data.has("prev_cursor") ? data.getString("prev_cursor") : null;
//
// final List<ProfileModel> leftUsers = new ArrayList<>();
// for (int j = 0; j < leftUsersJsonArray.length(); ++j) {
// final JSONObject userObject = leftUsersJsonArray.getJSONObject(j);
// leftUsers.add(new ProfileModel(userObject.getBoolean("is_private"),
// false,
// userObject.optBoolean("is_verified"),
// String.valueOf(userObject.get("pk")),
// userObject.getString("username"),
// userObject.getString("full_name"),
// null,
// null,
// userObject.getString("profile_pic_url"),
// null,
// 0,
// 0,
// 0,
// false,
// false,
// false,
// false));
// }
// final boolean threadHasOlder = data.getBoolean("has_older");
// final long unreadCount = data.optLong("read_state", 0);
//
// final long lastActivityAt = data.optLong("last_activity_at");
// final boolean named = data.optBoolean("named");
// final boolean muted = data.optBoolean("muted");
// final boolean isPin = data.optBoolean("is_pin");
// final boolean isSpam = data.optBoolean("is_spam");
// final boolean isGroup = data.optBoolean("is_group");
// final boolean pending = data.optBoolean("pending");
// final boolean archived = data.optBoolean("archived");
// final boolean canonical = data.optBoolean("canonical");
//
// final JSONArray users = data.getJSONArray("users");
// final int usersLen = users.length();
// final JSONArray leftusers = data.optJSONArray("left_users");
// final int leftusersLen = leftusers == null ? 0 : leftusers.length();
// final JSONArray admins = data.getJSONArray("admin_user_ids");
// final List<Long> adminIDs = new ArrayList<>();
// for (int j = 0; j < admins.length(); ++j) {
// adminIDs.add(admins.getLong(j));
// final int adminsLen = admins.length();
//
// final ProfileModel[] userModels = new ProfileModel[usersLen];
// for (int j = 0; j < usersLen; ++j) {
// final JSONObject userObject = users.getJSONObject(j);
// userModels[j] = new ProfileModel(userObject.optBoolean("is_private"),
// false,
// userObject.optBoolean("is_verified"),
// String.valueOf(userObject.get("pk")),
// userObject.optString("username"), // optional cuz fb users
// userObject.getString("full_name"),
// null, null,
// userObject.getString("profile_pic_url"),
// null, 0, 0, 0, false, false, false, false, false);
// }
//
// final ProfileModel[] leftuserModels = new ProfileModel[leftusersLen];
// for (int j = 0; j < leftusersLen; ++j) {
// final JSONObject userObject = leftusers.getJSONObject(j);
// leftuserModels[j] = new ProfileModel(userObject.getBoolean("is_private"),
// false,
// userObject.optBoolean("is_verified"),
// String.valueOf(userObject.get("pk")),
// userObject.getString("username"),
// userObject.getString("full_name"),
// null, null,
// userObject.getString("profile_pic_url"),
// null, 0, 0, 0, false, false, false, false, false);
// }
//
// final Long[] adminIDs = new Long[adminsLen];
// for (int j = 0; j < adminsLen; ++j) {
// adminIDs[j] = admins.getLong(j);
// }
//
// final JSONArray items = data.getJSONArray("items");
// final int itemsLen = items.length();
//
// final List<DirectItemModel> itemModels = new ArrayList<>(itemsLen);
// final ArrayList<DirectItemModel> itemModels = new ArrayList<>(itemsLen);
// for (int i = 0; i < itemsLen; ++i) {
// final JSONObject itemObject = items.getJSONObject(i);
//
// CharSequence text = null;
// ProfileModel profileModel = null;
// DMItemMediaModel mediaModel = null;
// DirectItemModel.DirectItemLinkModel linkModel = null;
// DirectItemModel.DirectItemMediaModel directMedia = null;
// DirectItemModel.DirectItemReelShareModel reelShareModel = null;
// DirectItemModel.DirectItemActionLogModel actionLogModel = null;
// DirectItemModel.DirectItemAnimatedMediaModel animatedMediaModel = null;
// DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = null;
// DirectItemModel.DirectItemRavenMediaModel ravenMediaModel = null;
// DirectItemModel.DirectItemVideoCallEventModel videoCallEventModel = null;
//
// final DirectItemType itemType = getDirectItemType(itemObject.getString("item_type"));
// switch (itemType) {
// case ANIMATED_MEDIA: {
// final JSONObject animatedMedia = itemObject.getJSONObject("animated_media");
// final JSONObject stickerImage = animatedMedia.getJSONObject("images").getJSONObject("fixed_height");
// mediaModel = new DirectItemAnimatedMediaModel(
//
// animatedMediaModel = new DirectItemModel.DirectItemAnimatedMediaModel(
// animatedMedia.getBoolean("is_random"),
// animatedMedia.getBoolean("is_sticker"),
// animatedMedia.getString("id"),
@ -330,9 +354,11 @@ public final class ResponseBodyUtils {
// stickerImage.getInt("width"));
// }
// break;
//
// case VOICE_MEDIA: {
// final JSONObject voiceMedia = itemObject.getJSONObject("voice_media").getJSONObject("media");
// final JSONObject audio = voiceMedia.getJSONObject("audio");
//
// int[] waveformData = null;
// final JSONArray waveformDataArray = audio.optJSONArray("waveform_data");
// if (waveformDataArray != null) {
@ -343,35 +369,40 @@ public final class ResponseBodyUtils {
// waveformData[j] = (int) (waveformDataArray.optDouble(j) * 10);
// }
// }
// mediaModel = new DirectItemVoiceMediaModel(
//
// voiceMediaModel = new DirectItemModel.DirectItemVoiceMediaModel(
// voiceMedia.getString("id"),
// audio.getString("audio_src"),
// audio.getLong("duration"),
// waveformData);
// }
// break;
//
// case LINK: {
// final JSONObject linkObj = itemObject.getJSONObject("link");
// DirectItemLinkContext itemLinkContext = null;
//
// DirectItemModel.DirectItemLinkContext itemLinkContext = null;
// final JSONObject linkContext = linkObj.optJSONObject("link_context");
// if (linkContext != null) {
// itemLinkContext = new DirectItemLinkContext(
// itemLinkContext = new DirectItemModel.DirectItemLinkContext(
// linkContext.getString("link_url"),
// linkContext.optString("link_title"),
// linkContext.optString("link_summary"),
// linkContext.optString("link_image_url")
// );
// }
// mediaModel = new DirectItemLinkModel(
//
// linkModel = new DirectItemModel.DirectItemLinkModel(
// linkObj.getString("text"),
// linkObj.getString("client_context"),
// linkObj.optString("mutation_token"),
// itemLinkContext);
// }
// break;
//
// case REEL_SHARE: {
// final JSONObject reelShare = itemObject.getJSONObject("reel_share");
// mediaModel = new DirectItemReelShareModel(
// reelShareModel = new DirectItemModel.DirectItemReelShareModel(
// reelShare.optBoolean("is_reel_persisted"),
// reelShare.getLong("reel_owner_id"),
// reelShare.getJSONObject("media").getJSONObject("user").getString("username"),
@ -383,34 +414,39 @@ public final class ResponseBodyUtils {
// getDirectMediaModel(reelShare.optJSONObject("media")));
// }
// break;
//
// case RAVEN_MEDIA: {
// final JSONObject visualMedia = itemObject.getJSONObject("visual_media");
//
// final JSONArray seenUserIdsArray = visualMedia.getJSONArray("seen_user_ids");
// final int seenUsersLen = seenUserIdsArray.length();
// final String[] seenUserIds = new String[seenUsersLen];
// for (int j = 0; j < seenUsersLen; j++)
// seenUserIds[j] = seenUserIdsArray.getString(j);
// RavenExpiringMediaActionSummary expiringSummaryModel = null;
//
// DirectItemModel.RavenExpiringMediaActionSummaryModel expiringSummaryModel = null;
// final JSONObject actionSummary = visualMedia.optJSONObject("expiring_media_action_summary");
// if (actionSummary != null)
// expiringSummaryModel = new RavenExpiringMediaActionSummary(
// expiringSummaryModel = new DirectItemModel.RavenExpiringMediaActionSummaryModel(
// actionSummary.getLong("timestamp"), actionSummary.getInt("count"),
// getExpiringMediaType(actionSummary.getString("type")));
// final RavenMediaViewMode viewType;
//
// final RavenMediaViewType viewType;
// final String viewMode = visualMedia.getString("view_mode");
// switch (viewMode) {
// case "replayable":
// viewType = RavenMediaViewMode.REPLAYABLE;
// viewType = RavenMediaViewType.REPLAYABLE;
// break;
// case "permanent":
// viewType = RavenMediaViewMode.PERMANENT;
// viewType = RavenMediaViewType.PERMANENT;
// break;
// case "once":
// default:
// viewType = RavenMediaViewMode.ONCE;
// viewType = RavenMediaViewType.ONCE;
// }
// mediaModel = new DirectItemRavenMediaModel(
// visualMedia.optLong(viewType == RavenMediaViewMode.PERMANENT ? "url_expire_at_secs" : "replay_expiring_at_us"),
//
// ravenMediaModel = new DirectItemModel.DirectItemRavenMediaModel(
// visualMedia.optLong(viewType == RavenMediaViewType.PERMANENT ? "url_expire_at_secs" : "replay_expiring_at_us"),
// visualMedia.optInt("playback_duration_secs"),
// visualMedia.getInt("seen_count"),
// seenUserIds,
@ -420,14 +456,16 @@ public final class ResponseBodyUtils {
//
// }
// break;
//
// case VIDEO_CALL_EVENT: {
// final JSONObject videoCallEvent = itemObject.getJSONObject("video_call_event");
// mediaModel = new DirectItemVideoCallEventModel(videoCallEvent.getLong("vc_id"),
// videoCallEvent.optBoolean("thread_has_audio_only_call"),
// videoCallEvent.getString("action"),
// videoCallEvent.getString("description"));
// videoCallEventModel = new DirectItemModel.DirectItemVideoCallEventModel(videoCallEvent.optLong("vc_id"),
// videoCallEvent.optBoolean("thread_has_audio_only_call"),
// videoCallEvent.getString("action"),
// videoCallEvent.getString("description"));
// }
// break;
//
// case PROFILE: {
// final JSONObject profile = itemObject.getJSONObject("profile");
// profileModel = new ProfileModel(profile.getBoolean("is_private"),
@ -438,13 +476,15 @@ public final class ResponseBodyUtils {
// profile.getString("full_name"),
// null, null,
// profile.getString("profile_pic_url"),
// null, 0, 0, 0, false, false, false, false);
// null, 0, 0, 0, false, false, false, false, false);
// }
// break;
//
// case PLACEHOLDER:
// final JSONObject placeholder = itemObject.getJSONObject("placeholder");
// text = placeholder.getString("title") + "<br><small>" + placeholder.getString("message") + "</small>";
// break;
//
// case ACTION_LOG:
// if (inThreadView && itemObject.optInt("hide_in_thread", 0) != 0)
// continue;
@ -457,29 +497,35 @@ public final class ResponseBodyUtils {
// + desc.substring(boldItem.getInt("start") + q * 7, boldItem.getInt("end") + q * 7)
// + "</b>" + desc.substring(boldItem.getInt("end") + q * 7);
// }
// mediaModel = new DirectItemActionLogModel(desc);
// actionLogModel = new DirectItemModel.DirectItemActionLogModel(desc);
// break;
//
// case MEDIA_SHARE:
// mediaModel = getDirectMediaModel(itemObject.getJSONObject("media_share"));
// directMedia = getDirectMediaModel(itemObject.getJSONObject("media_share"));
// break;
//
// case CLIP:
// mediaModel = getDirectMediaModel(itemObject.getJSONObject("clip").getJSONObject("clip"));
// directMedia = getDirectMediaModel(itemObject.getJSONObject("clip").getJSONObject("clip"));
// break;
//
// case FELIX_SHARE:
// mediaModel = getDirectMediaModel(itemObject.getJSONObject("felix_share").getJSONObject("video"));
// directMedia = getDirectMediaModel(itemObject.getJSONObject("felix_share").getJSONObject("video"));
// break;
//
// case MEDIA:
// mediaModel = getDirectMediaModel(itemObject.optJSONObject("media"));
// directMedia = getDirectMediaModel(itemObject.optJSONObject("media"));
// break;
//
// case LIKE:
// text = itemObject.getString("like");
// break;
//
// case STORY_SHARE:
// final JSONObject storyShare = itemObject.getJSONObject("story_share");
// if (!storyShare.has("media"))
// text = "<small>" + storyShare.optString("message") + "</small>";
// else {
// mediaModel = new DirectItemReelShareModel(
// reelShareModel = new DirectItemModel.DirectItemReelShareModel(
// storyShare.optBoolean("is_reel_persisted"),
// storyShare.getJSONObject("media").getJSONObject("user").getLong("pk"),
// storyShare.getJSONObject("media").getJSONObject("user").getString("username"),
@ -491,6 +537,7 @@ public final class ResponseBodyUtils {
// getDirectMediaModel(storyShare.optJSONObject("media")));
// }
// break;
//
// case TEXT:
// if (!itemObject.has("text"))
// Log.d("AWAISKING_APP", "itemObject: " + itemObject); // todo
@ -514,49 +561,40 @@ public final class ResponseBodyUtils {
// liked,
// itemType,
// text,
// linkModel,
// profileModel,
// mediaModel));
// reelShareModel,
// directMedia,
// actionLogModel,
// voiceMediaModel,
// ravenMediaModel,
// videoCallEventModel,
// animatedMediaModel));
// }
//
// return new InboxThreadModel(readState,
// data.getString("thread_id"),
// data.getString("thread_v2_id"),
// data.getString("thread_type"),
// data.getString("thread_title"),
// data.optString("newest_cursor"),
// data.optString("oldest_cursor"),
// data.has("next_cursor") ? data.getString("next_cursor") : null,
// data.has("prev_cursor") ? data.getString("prev_cursor") : null,
// itemModels.trimToSize();
//
// return new InboxThreadModel(readState, threadId, threadV2Id, threadType, threadTitle,
// threadNewestCursor, threadOldestCursor, threadNextCursor, threadPrevCursor,
// null, // todo
// users,
// leftUsers,
// adminIDs,
// itemModels,
// data.optBoolean("muted"),
// data.optBoolean("is_pin"),
// data.optBoolean("named"),
// data.optBoolean("canonical"),
// data.optBoolean("pending"),
// data.getBoolean("has_older"),
// data.optLong("read_state", 0),
// data.optBoolean("is_spam"),
// data.optBoolean("is_group"),
// data.optBoolean("archived"),
// data.optLong("last_activity_at"));
// userModels, leftuserModels, adminIDs,
// itemModels.toArray(new DirectItemModel[0]),
// muted, isPin, named, canonical,
// pending, threadHasOlder, unreadCount, isSpam, isGroup, archived, lastActivityAt);
// }
// private static ActionType getExpiringMediaType(final String type) {
// if ("raven_sent".equals(type)) return ActionType.SENT;
// if ("raven_opened".equals(type)) return ActionType.OPENED;
// if ("raven_blocked".equals(type)) return ActionType.BLOCKED;
// if ("raven_sending".equals(type)) return ActionType.SENDING;
// if ("raven_replayed".equals(type)) return ActionType.REPLAYED;
// if ("raven_delivered".equals(type)) return ActionType.DELIVERED;
// if ("raven_suggested".equals(type)) return ActionType.SUGGESTED;
// if ("raven_screenshot".equals(type)) return ActionType.SCREENSHOT;
// if ("raven_cannot_deliver".equals(type)) return ActionType.CANNOT_DELIVER;
//
// private static RavenExpiringMediaType getExpiringMediaType(final String type) {
// if ("raven_sent".equals(type)) return RavenExpiringMediaType.RAVEN_SENT;
// if ("raven_opened".equals(type)) return RavenExpiringMediaType.RAVEN_OPENED;
// if ("raven_blocked".equals(type)) return RavenExpiringMediaType.RAVEN_BLOCKED;
// if ("raven_sending".equals(type)) return RavenExpiringMediaType.RAVEN_SENDING;
// if ("raven_replayed".equals(type)) return RavenExpiringMediaType.RAVEN_REPLAYED;
// if ("raven_delivered".equals(type)) return RavenExpiringMediaType.RAVEN_DELIVERED;
// if ("raven_suggested".equals(type)) return RavenExpiringMediaType.RAVEN_SUGGESTED;
// if ("raven_screenshot".equals(type)) return RavenExpiringMediaType.RAVEN_SCREENSHOT;
// if ("raven_cannot_deliver".equals(type)) return RavenExpiringMediaType.RAVEN_CANNOT_DELIVER;
// //if ("raven_unknown".equals(type)) [default?]
// return ActionType.UNKNOWN;
// return RavenExpiringMediaType.RAVEN_UNKNOWN;
// }
public static FeedModel parseItem(final JSONObject itemJson) throws JSONException {
@ -590,6 +628,7 @@ public final class ResponseBodyUtils {
0,
0,
following,
false,
restricted,
false,
requested);
@ -606,7 +645,8 @@ public final class ResponseBodyUtils {
.setPostId(itemJson.getString(Constants.EXTRAS_ID))
.setThumbnailUrl(mediaType != MediaItemType.MEDIA_TYPE_SLIDER ? ResponseBodyUtils.getLowQualityImage(itemJson) : null)
.setShortCode(itemJson.getString("code"))
.setPostCaption(captionJson != null ? captionJson.optString("text") : null)
.setPostCaption(captionJson != null ? captionJson.optString("text") : "")
.setCaptionId(captionJson != null ? captionJson.optString("pk") : null)
.setCommentsCount(itemJson.optInt("comment_count"))
.setTimestamp(itemJson.optLong("taken_at", -1))
.setLiked(itemJson.optBoolean("has_liked"))
@ -627,7 +667,9 @@ public final class ResponseBodyUtils {
break;
case MEDIA_TYPE_SLIDER:
final List<PostChild> childPosts = getChildPosts(itemJson);
feedModelBuilder.setSliderItems(childPosts);
feedModelBuilder.setSliderItems(childPosts)
.setImageHeight(childPosts.get(0).getHeight())
.setImageWidth(childPosts.get(0).getWidth());
break;
}
return feedModelBuilder.build();
@ -673,6 +715,7 @@ public final class ResponseBodyUtils {
false,
false,
false,
false,
false);
}
JSONObject tempJsonObject = feedItem.optJSONObject("edge_media_preview_comment");
@ -743,7 +786,9 @@ public final class ResponseBodyUtils {
final JSONArray children = sidecar.optJSONArray("edges");
if (children != null) {
final List<PostChild> sliderItems = getSliderItems(children);
feedModelBuilder.setSliderItems(sliderItems);
feedModelBuilder.setSliderItems(sliderItems)
.setImageHeight(sliderItems.get(0).getHeight())
.setImageWidth(sliderItems.get(0).getWidth());
}
}
}
@ -832,6 +877,134 @@ public final class ResponseBodyUtils {
return sliderItems;
}
public static StoryModel parseStoryItem(final JSONObject data,
final boolean isLoc,
final boolean isHashtag,
final String localUsername) throws JSONException {
final boolean isVideo = data.has("video_duration");
final StoryModel model = new StoryModel(data.getString("id"),
data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(0)
.getString("url"), null,
isVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE,
data.optLong("taken_at", 0),
(isLoc || isHashtag)
? data.getJSONObject("user").getString("username")
: localUsername,
data.getJSONObject("user").getString("pk"),
data.optBoolean("can_reply"));
if (data.getJSONObject("image_versions2").getJSONArray("candidates").length() > 1) {
model.setThumbnail(data.getJSONObject("image_versions2").getJSONArray("candidates").getJSONObject(1)
.getString("url"));
}
final JSONArray videoResources = data.optJSONArray("video_versions");
if (isVideo && videoResources != null)
model.setVideoUrl(ResponseBodyUtils.getHighQualityPost(videoResources, true, true, false));
if (data.has("story_feed_media")) {
model.setTappableShortCode(data.getJSONArray("story_feed_media").getJSONObject(0).optString("media_code"));
}
// TODO: this may not be limited to spotify
if (!data.isNull("story_app_attribution"))
model.setSpotify(data.getJSONObject("story_app_attribution").optString("content_url").split("\\?")[0]);
if (data.has("story_polls")) {
final JSONArray storyPolls = data.optJSONArray("story_polls");
JSONObject tappableObject = null;
if (storyPolls != null) {
tappableObject = storyPolls.getJSONObject(0).optJSONObject("poll_sticker");
}
if (tappableObject != null) model.setPoll(new PollModel(
String.valueOf(tappableObject.getLong("poll_id")),
tappableObject.getString("question"),
tappableObject.getJSONArray("tallies").getJSONObject(0).getString("text"),
tappableObject.getJSONArray("tallies").getJSONObject(0).getInt("count"),
tappableObject.getJSONArray("tallies").getJSONObject(1).getString("text"),
tappableObject.getJSONArray("tallies").getJSONObject(1).getInt("count"),
tappableObject.optInt("viewer_vote", -1)
));
}
if (data.has("story_questions")) {
final JSONObject tappableObject = data.getJSONArray("story_questions").getJSONObject(0)
.optJSONObject("question_sticker");
if (tappableObject != null && !tappableObject.getString("question_type").equals("music"))
model.setQuestion(new QuestionModel(
String.valueOf(tappableObject.getLong("question_id")),
tappableObject.getString("question")
));
}
if (data.has("story_quizs")) {
JSONObject tappableObject = data.getJSONArray("story_quizs").getJSONObject(0).optJSONObject("quiz_sticker");
if (tappableObject != null) {
String[] choices = new String[tappableObject.getJSONArray("tallies").length()];
Long[] counts = new Long[choices.length];
for (int q = 0; q < choices.length; ++q) {
JSONObject tempchoice = tappableObject.getJSONArray("tallies").getJSONObject(q);
choices[q] = (q == tappableObject.getInt("correct_answer") ? "*** " : "")
+ tempchoice.getString("text");
counts[q] = tempchoice.getLong("count");
}
model.setQuiz(new QuizModel(
String.valueOf(tappableObject.getLong("quiz_id")),
tappableObject.getString("question"),
choices,
counts,
tappableObject.optInt("viewer_answer", -1)
));
}
}
if (data.has("story_cta") && data.has("link_text")) {
JSONObject tappableObject = data.getJSONArray("story_cta").getJSONObject(0).getJSONArray("links").getJSONObject(0);
String swipeUpUrl = tappableObject.getString("webUri");
if (swipeUpUrl.startsWith("http")) {
model.setSwipeUp(new SwipeUpModel(swipeUpUrl, data.getString("link_text")));
}
}
if (data.has("story_sliders")) {
final JSONObject tappableObject = data.getJSONArray("story_sliders").getJSONObject(0)
.optJSONObject("slider_sticker");
if (tappableObject != null)
model.setSlider(new SliderModel(
String.valueOf(tappableObject.getLong("slider_id")),
tappableObject.getString("question"),
tappableObject.getString("emoji"),
tappableObject.getBoolean("viewer_can_vote"),
tappableObject.getDouble("slider_vote_average"),
tappableObject.getInt("slider_vote_count"),
tappableObject.optDouble("viewer_vote")
));
}
JSONArray hashtags = data.optJSONArray("story_hashtags");
JSONArray locations = data.optJSONArray("story_locations");
JSONArray atmarks = data.optJSONArray("reel_mentions");
String[] mentions = new String[(hashtags == null ? 0 : hashtags.length())
+ (atmarks == null ? 0 : atmarks.length())
+ (locations == null ? 0 : locations.length())];
if (hashtags != null) {
for (int h = 0; h < hashtags.length(); ++h) {
mentions[h] = "#" + hashtags.getJSONObject(h).getJSONObject("hashtag").getString("name");
}
}
if (atmarks != null) {
for (int h = 0; h < atmarks.length(); ++h) {
mentions[h + (hashtags == null ? 0 : hashtags.length())] =
"@" + atmarks.getJSONObject(h).getJSONObject("user").getString("username");
}
}
if (locations != null) {
for (int h = 0; h < locations.length(); ++h) {
mentions[h + (hashtags == null ? 0 : hashtags.length()) + (atmarks == null ? 0 : atmarks.length())] =
locations.getJSONObject(h).getJSONObject("location").getString("short_name")
+ " (" + locations.getJSONObject(h).getJSONObject("location").getLong("pk") + ")";
}
}
if (mentions.length != 0) model.setMentions(mentions);
return model;
}
public static String getThumbUrl(final ImageVersions2 imageVersions2) {
if (imageVersions2 == null) return null;
final List<MediaCandidate> candidates = imageVersions2.getCandidates();

View File

@ -41,6 +41,7 @@ import static awais.instagrabber.utils.Constants.PREF_TOPIC_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREV_INSTALL_VERSION;
import static awais.instagrabber.utils.Constants.SHOW_QUICK_ACCESS_DIALOG;
import static awais.instagrabber.utils.Constants.SKIPPED_VERSION;
import static awais.instagrabber.utils.Constants.STORY_SORT;
import static awais.instagrabber.utils.Constants.SWAP_DATE_TIME_FORMAT_ENABLED;
public final class SettingsHelper {
@ -123,7 +124,7 @@ public final class SettingsHelper {
{APP_LANGUAGE, APP_THEME, COOKIE, FOLDER_PATH, DATE_TIME_FORMAT, DATE_TIME_SELECTION, CUSTOM_DATE_TIME_FORMAT,
DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT,
PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_POSTS_LAYOUT,
PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, PREF_EMOJI_VARIANTS})
PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, STORY_SORT, PREF_EMOJI_VARIANTS})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,

View File

@ -94,12 +94,9 @@ public final class Utils {
if (signed == null) {
return null;
}
final String[] parts = signed.split("&");
final Map<String, String> map = new HashMap<>();
for (final String part : parts) {
final String[] partSplit = part.split("=");
map.put(partSplit[0], partSplit[1]);
}
map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION);
map.put("signed_body", signed.split("&signed_body=")[1]);
return map;
}

View File

@ -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;
}
}

View File

@ -16,4 +16,12 @@ public class BasePostViewModel<T extends BasePostModel> extends ViewModel {
}
return list;
}
public void edit(int index, T post) {
}
public void delete(int index) {
}
}

View File

@ -3,6 +3,7 @@ package awais.instagrabber.viewmodels;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.models.FeedStoryModel;

View File

@ -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;
}
}

View File

@ -6,16 +6,19 @@ import com.google.common.collect.ImmutableMap;
import org.json.JSONArray;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import awais.instagrabber.repositories.DirectMessagesRepository;
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.PhotoBroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.StoryReplyBroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.TextBroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.VideoBroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.VoiceBroadcastOptions;
@ -146,6 +149,12 @@ public class DirectMessagesService extends BaseService {
return broadcast(new VoiceBroadcastOptions(clientContext, threadIdOrUserIds, uploadId, waveform, samplingFreq));
}
public Call<DirectThreadBroadcastResponse> broadcastStoryReply(final ThreadIdOrUserIds threadIdOrUserIds,
final String text,
final String mediaId, final String reelId) throws UnsupportedEncodingException {
return broadcast(new StoryReplyBroadcastOptions(UUID.randomUUID().toString(), threadIdOrUserIds, text, mediaId, reelId));
}
private Call<DirectThreadBroadcastResponse> broadcast(@NonNull final BroadcastOptions broadcastOptions) {
if (TextUtils.isEmpty(broadcastOptions.getClientContext())) {
throw new IllegalArgumentException("Broadcast requires a valid client context value");

View File

@ -165,6 +165,7 @@ public class DiscoverService extends BaseService {
false,
false,
false,
false,
false);
}
final String resourceUrl = ResponseBodyUtils.getHighQualityImage(coverMediaJson);

View File

@ -19,6 +19,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
@ -36,7 +38,6 @@ import retrofit2.Retrofit;
public class FeedService extends BaseService {
private static final String TAG = "FeedService";
private static final boolean loadFromMock = false;
private final FeedRepository repository;
@ -44,7 +45,7 @@ public class FeedService extends BaseService {
private FeedService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(FeedRepository.class);
}
@ -56,41 +57,26 @@ public class FeedService extends BaseService {
return instance;
}
public void fetch(final int maxItemsToLoad,
public void fetch(final String csrfToken,
final String cursor,
final ServiceCallback<PostsFetchResponse> callback) {
if (loadFromMock) {
final Handler handler = new Handler();
handler.postDelayed(() -> {
final ClassLoader classLoader = getClass().getClassLoader();
if (classLoader == null) {
Log.e(TAG, "fetch: classLoader is null!");
return;
}
try (InputStream resourceAsStream = classLoader.getResourceAsStream("feed_response.json");
Reader in = new InputStreamReader(resourceAsStream, StandardCharsets.UTF_8)) {
final int bufferSize = 1024;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
int charsRead;
while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) {
out.append(buffer, 0, charsRead);
}
callback.onSuccess(parseResponseBody(out.toString()));
} catch (IOException | JSONException e) {
Log.e(TAG, "fetch: ", e);
}
}, 1000);
return;
final Map<String, String> form = new HashMap<>();
form.put("_uuid", UUID.randomUUID().toString());
form.put("_csrftoken", csrfToken);
form.put("phone_id", UUID.randomUUID().toString());
form.put("device_id", UUID.randomUUID().toString());
form.put("client_session_id", UUID.randomUUID().toString());
form.put("is_prefetch", "0");
form.put("timezone_offset", String.valueOf(TimeZone.getDefault().getRawOffset() / 1000));
if (!TextUtils.isEmpty(cursor)) {
form.put("max_id", cursor);
form.put("reason", "pagination");
}
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "c699b185975935ae2a457f24075de8c7");
queryMap.put("variables", "{" +
"\"fetch_media_item_count\":" + maxItemsToLoad + "," +
"\"fetch_like\":3,\"has_stories\":false,\"has_stories\":false,\"has_threaded_comments\":true," +
"\"fetch_media_item_cursor\":\"" + (cursor == null ? "" : cursor) + "\"" +
"}");
final Call<String> request = repository.fetch(queryMap);
else {
form.put("is_pull_to_refresh", "1");
form.put("reason", "pull_to_refresh");
}
final Call<String> request = repository.fetch(form);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -130,35 +116,45 @@ public class FeedService extends BaseService {
@NonNull
private PostsFetchResponse parseResponseBody(@NonNull final String body)
throws JSONException {
final JSONObject root = new JSONObject(body);
final boolean moreAvailable = root.optBoolean("more_available");
String nextMaxId = root.optString("next_max_id");
final boolean needNewMaxId = nextMaxId.equals("feed_recs_head_load");
final JSONArray feedItems = root.optJSONArray("items");
final List<FeedModel> feedModels = new ArrayList<>();
final JSONObject timelineFeed = new JSONObject(body)
.getJSONObject("data")
.getJSONObject(Constants.EXTRAS_USER)
.getJSONObject("edge_web_feed_timeline");
final String endCursor;
final boolean hasNextPage;
final JSONObject pageInfo = timelineFeed.getJSONObject("page_info");
if (pageInfo.has("has_next_page")) {
hasNextPage = pageInfo.getBoolean("has_next_page");
endCursor = hasNextPage ? pageInfo.getString("end_cursor") : null;
} else {
hasNextPage = false;
endCursor = null;
}
final JSONArray feedItems = timelineFeed.getJSONArray("edges");
for (int i = 0; i < feedItems.length(); ++i) {
final JSONObject itemJson = feedItems.optJSONObject(i);
if (itemJson == null) {
if (itemJson == null || itemJson.has("injected")) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
else if (itemJson.has("end_of_feed_demarcator") && needNewMaxId) {
final JSONArray groups = itemJson.getJSONObject("end_of_feed_demarcator").getJSONObject("group_set").getJSONArray("groups");
for (int j = 0; j < groups.length(); ++j) {
final JSONObject groupJson = groups.optJSONObject(j);
if (groupJson.getString("id").equals("past_posts")) {
nextMaxId = groupJson.optString("next_max_id");
final JSONArray miniFeedItems = groupJson.optJSONArray("feed_items");
for (int k = 0; k < miniFeedItems.length(); ++k) {
final JSONObject miniItemJson = miniFeedItems.optJSONObject(k);
if (miniItemJson == null || miniItemJson.has("injected")) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseItem(miniItemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
}
else continue;
}
}
else {
final FeedModel feedModel = ResponseBodyUtils.parseItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
}
return new PostsFetchResponse(feedModels, hasNextPage, endCursor);
return new PostsFetchResponse(feedModels, moreAvailable, nextMaxId);
}
}
}

View File

@ -153,13 +153,12 @@ public class FriendshipService extends BaseService {
});
}
// log in required
public void getList(final boolean follower,
final String targetUserId,
final String maxId,
final ServiceCallback<FriendshipRepoListFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("max_id", maxId == null ? "" : maxId);
if (maxId != null) queryMap.put("max_id", maxId);
final Call<String> request = repository.getList(Constants.I_USER_AGENT,
targetUserId,
follower ? "followers" : "following",
@ -173,7 +172,6 @@ public class FriendshipService extends BaseService {
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}

View File

@ -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);
}
}
});
}
}

View File

@ -19,6 +19,7 @@ import java.util.Objects;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.repositories.LocationRepository;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
@ -29,7 +30,7 @@ import retrofit2.Retrofit;
public class LocationService extends BaseService {
private static final String TAG = "LocationService";
private final LocationRepository repository, webRepository;
private final LocationRepository repository;
private static LocationService instance;
@ -38,10 +39,6 @@ public class LocationService extends BaseService {
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(LocationRepository.class);
final Retrofit webRetrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
webRepository = webRetrofit.create(LocationRepository.class);
}
public static LocationService getInstance() {
@ -53,7 +50,7 @@ public class LocationService extends BaseService {
public void fetchPosts(@NonNull final String locationId,
final String maxId,
final ServiceCallback<LocationPostsFetchResponse> callback) {
final ServiceCallback<PostsFetchResponse> callback) {
final ImmutableMap.Builder<String, String> builder = ImmutableMap.builder();
if (!TextUtils.isEmpty(maxId)) {
builder.put("max_id", maxId);
@ -71,7 +68,7 @@ public class LocationService extends BaseService {
callback.onSuccess(null);
return;
}
final LocationPostsFetchResponse tagPostsFetchResponse = parseResponse(body);
final PostsFetchResponse tagPostsFetchResponse = parseResponse(body);
callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
@ -88,20 +85,16 @@ public class LocationService extends BaseService {
});
}
private LocationPostsFetchResponse parseResponse(@NonNull final String body) throws JSONException {
private PostsFetchResponse parseResponse(@NonNull final String body) throws JSONException {
final JSONObject root = new JSONObject(body);
final boolean moreAvailable = root.optBoolean("more_available");
final String nextMaxId = root.optString("next_max_id");
final int numResults = root.optInt("num_results");
final String status = root.optString("status");
final JSONArray itemsJson = root.optJSONArray("items");
final List<FeedModel> items = parseItems(itemsJson);
return new LocationPostsFetchResponse(
return new PostsFetchResponse(
items,
moreAvailable,
nextMaxId,
numResults,
status,
items
nextMaxId
);
}
@ -122,174 +115,4 @@ public class LocationService extends BaseService {
}
return feedModels;
}
public void fetchGraphQLPosts(@NonNull final String locationId,
final String maxId,
final ServiceCallback<LocationPostsFetchResponse> callback) {
final Map<String, String> queryMap = new HashMap<>();
queryMap.put("query_hash", "36bd0f2bf5911908de389b8ceaa3be6d");
queryMap.put("variables", "{" +
"\"id\":\"" + locationId + "\"," +
"\"first\":25," +
"\"after\":\"" + (maxId == null ? "" : maxId) + "\"" +
"}");
final Call<String> request = webRepository.fetchGraphQLPosts(queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
try {
if (callback == null) {
return;
}
final String body = response.body();
if (TextUtils.isEmpty(body)) {
callback.onSuccess(null);
return;
}
final LocationPostsFetchResponse tagPostsFetchResponse = parseGraphQLResponse(body);
callback.onSuccess(tagPostsFetchResponse);
} catch (JSONException e) {
Log.e(TAG, "onResponse", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
private LocationPostsFetchResponse parseGraphQLResponse(@NonNull final String body) throws JSONException {
final JSONObject rootroot = new JSONObject(body);
final JSONObject root = rootroot.getJSONObject("data").getJSONObject("location").getJSONObject("edge_location_to_media");
final boolean moreAvailable = root.getJSONObject("page_info").optBoolean("has_next_page");
final String nextMaxId = root.getJSONObject("page_info").optString("end_cursor");
final int numResults = root.optInt("count");
final String status = rootroot.optString("status");
final JSONArray itemsJson = root.optJSONArray("edges");
final List<FeedModel> items = parseGraphQLItems(itemsJson);
return new LocationPostsFetchResponse(
moreAvailable,
nextMaxId,
numResults,
status,
items
);
}
private List<FeedModel> parseGraphQLItems(final JSONArray items) throws JSONException {
if (items == null) {
return Collections.emptyList();
}
final List<FeedModel> feedModels = new ArrayList<>();
for (int i = 0; i < items.length(); i++) {
final JSONObject itemJson = items.optJSONObject(i);
if (itemJson == null) {
continue;
}
final FeedModel feedModel = ResponseBodyUtils.parseGraphQLItem(itemJson);
if (feedModel != null) {
feedModels.add(feedModel);
}
}
return feedModels;
}
public static class LocationPostsFetchResponse {
private boolean moreAvailable;
private String nextMaxId;
private int numResults;
private String status;
private List<FeedModel> items;
public LocationPostsFetchResponse(final boolean moreAvailable,
final String nextMaxId,
final int numResults,
final String status,
final List<FeedModel> items) {
this.moreAvailable = moreAvailable;
this.nextMaxId = nextMaxId;
this.numResults = numResults;
this.status = status;
this.items = items;
}
public boolean isMoreAvailable() {
return moreAvailable;
}
public LocationPostsFetchResponse setMoreAvailable(final boolean moreAvailable) {
this.moreAvailable = moreAvailable;
return this;
}
public String getNextMaxId() {
return nextMaxId;
}
public LocationPostsFetchResponse setNextMaxId(final String nextMaxId) {
this.nextMaxId = nextMaxId;
return this;
}
public int getNumResults() {
return numResults;
}
public LocationPostsFetchResponse setNumResults(final int numResults) {
this.numResults = numResults;
return this;
}
public String getStatus() {
return status;
}
public LocationPostsFetchResponse setStatus(final String status) {
this.status = status;
return this;
}
public List<FeedModel> getItems() {
return items;
}
public LocationPostsFetchResponse setItems(final List<FeedModel> items) {
this.items = items;
return this;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final LocationPostsFetchResponse that = (LocationPostsFetchResponse) o;
return moreAvailable == that.moreAvailable &&
numResults == that.numResults &&
Objects.equals(nextMaxId, that.nextMaxId) &&
Objects.equals(status, that.status) &&
Objects.equals(items, that.items);
}
@Override
public int hashCode() {
return Objects.hash(moreAvailable, nextMaxId, numResults, status, items);
}
@NonNull
@Override
public String toString() {
return "LocationPostsFetchResponse{" +
"moreAvailable=" + moreAvailable +
", nextMaxId='" + nextMaxId + '\'' +
", numResults=" + numResults +
", status='" + status + '\'' +
", items=" + items +
'}';
}
}
}

View File

@ -7,20 +7,25 @@ import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.repositories.MediaRepository;
import awais.instagrabber.repositories.requests.UploadFinishOptions;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DateUtils;
import awais.instagrabber.utils.MediaUploadHelper;
import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
@ -48,6 +53,37 @@ public class MediaService extends BaseService {
return instance;
}
public void fetch(final String mediaId,
final ServiceCallback<FeedModel> callback) {
final Call<String> request = repository.fetch(mediaId);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call,
@NonNull final Response<String> response) {
if (callback == null) return;
final String body = response.body();
if (body == null) {
callback.onSuccess(null);
return;
}
try {
final JSONObject itemJson = new JSONObject(body).getJSONArray("items").getJSONObject(0);
callback.onSuccess(ResponseBodyUtils.parseItem(itemJson));
} catch (JSONException e) {
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call,
@NonNull final Throwable t) {
if (callback != null) {
callback.onFailure(t);
}
}
});
}
public void like(final String mediaId,
final String userId,
final String csrfToken,
@ -88,7 +124,7 @@ public class MediaService extends BaseService {
form.put("_uuid", UUID.randomUUID().toString());
// form.put("radio_type", "wifi-none");
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> request = repository.action(Constants.I_USER_AGENT, action, mediaId, signedForm);
final Call<String> request = repository.action(action, mediaId, signedForm);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call,
@ -137,7 +173,7 @@ public class MediaService extends BaseService {
form.put("replied_to_comment_id", replyToCommentId);
}
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentRequest = repository.comment(Constants.I_USER_AGENT, mediaId, signedForm);
final Call<String> commentRequest = repository.comment(mediaId, signedForm);
commentRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -183,7 +219,7 @@ public class MediaService extends BaseService {
form.put("_uid", userId);
form.put("_uuid", UUID.randomUUID().toString());
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(Constants.USER_AGENT, mediaId, signedForm);
final Call<String> bulkDeleteRequest = repository.commentsBulkDelete(mediaId, signedForm);
bulkDeleteRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -219,7 +255,7 @@ public class MediaService extends BaseService {
// form.put("_uid", userId);
// form.put("_uuid", UUID.randomUUID().toString());
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentLikeRequest = repository.commentLike(Constants.USER_AGENT, commentId, signedForm);
final Call<String> commentLikeRequest = repository.commentLike(commentId, signedForm);
commentLikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -255,7 +291,7 @@ public class MediaService extends BaseService {
// form.put("_uid", userId);
// form.put("_uuid", UUID.randomUUID().toString());
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> commentUnlikeRequest = repository.commentUnlike(Constants.USER_AGENT, commentId, signedForm);
final Call<String> commentUnlikeRequest = repository.commentUnlike(commentId, signedForm);
commentUnlikeRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
@ -283,6 +319,126 @@ public class MediaService extends BaseService {
});
}
public void editCaption(final String postId,
final String userId,
final String newCaption,
@NonNull final String csrfToken,
@NonNull final ServiceCallback<Boolean> callback) {
final Map<String, Object> form = new HashMap<>();
form.put("_csrftoken", csrfToken);
form.put("_uid", userId);
form.put("_uuid", UUID.randomUUID().toString());
form.put("igtv_feed_preview", "false");
form.put("media_id", postId);
form.put("caption_text", newCaption);
final Map<String, String> signedForm = Utils.sign(form);
final Call<String> request = repository.editCaption(postId, signedForm);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while editing caption");
callback.onSuccess(false);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String status = jsonObject.optString("status");
callback.onSuccess(status.equals("ok"));
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error editing caption", t);
callback.onFailure(t);
}
});
}
public void fetchLikes(final String mediaId,
final boolean isComment,
@NonNull final ServiceCallback<List<ProfileModel>> callback) {
final Call<String> likesRequest = repository.fetchLikes(mediaId, isComment ? "comment_likers" : "likers");
likesRequest.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while fetching likes of "+mediaId);
callback.onSuccess(null);
return;
}
try {
final JSONObject data = new JSONObject(body);
final JSONArray users = data.getJSONArray("users");
final int usersLen = users.length();
final List<ProfileModel> userModels = new ArrayList<>();
for (int j = 0; j < usersLen; ++j) {
final JSONObject userObject = users.getJSONObject(j);
userModels.add(new ProfileModel(userObject.optBoolean("is_private"),
false,
userObject.optBoolean("is_verified"),
String.valueOf(userObject.get("pk")),
userObject.getString("username"),
userObject.optString("full_name"),
null, null,
userObject.getString("profile_pic_url"),
null, 0, 0, 0, false, false, false, false, false));
}
callback.onSuccess(userModels);
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error getting likes", t);
callback.onFailure(t);
}
});
}
public void translate(final String id,
final String type, // 1 caption 2 comment 3 bio
@NonNull final ServiceCallback<String> callback) {
final Map<String, String> form = new HashMap<>();
form.put("id", id);
form.put("type", type);
final Call<String> request = repository.translate(form);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {
final String body = response.body();
if (body == null) {
Log.e(TAG, "Error occurred while translating");
callback.onSuccess(null);
return;
}
try {
final JSONObject jsonObject = new JSONObject(body);
final String translation = jsonObject.optString("translation");
callback.onSuccess(translation);
} catch (JSONException e) {
// Log.e(TAG, "Error parsing body", e);
callback.onFailure(e);
}
}
@Override
public void onFailure(@NonNull final Call<String> call, @NonNull final Throwable t) {
Log.e(TAG, "Error translating", t);
callback.onFailure(t);
}
});
}
public Call<String> uploadFinish(final long userId,
@NonNull final String csrfToken,
@NonNull final UploadFinishOptions options) {

Some files were not shown because too many files have changed in this diff Show More