Merge pull request #57 from ammargitham/feature/share-photos-dm

Feature/share photos dm
This commit is contained in:
Austin Huang 2020-08-18 11:24:24 -04:00 committed by GitHub
commit e5c0b376f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 1910 additions and 1122 deletions

2
.gitignore vendored
View File

@ -14,4 +14,4 @@
/captures
.externalNativeBuild
.cxx
app/release/
app/release

View File

@ -1,4 +1,5 @@
apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
android {
compileSdkVersion 29
@ -37,10 +38,14 @@ android {
dependencies {
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.recyclerview:recyclerview:1.2.0-alpha03@aar') { transitive true }
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { transitive true }
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation('org.jsoup:jsoup:1.13.1') { transitive true }

View File

@ -215,7 +215,7 @@
</activity>
<activity
android:name=".activities.DirectMessages"
android:name=".activities.DirectMessagesActivity"
android:parentActivityName=".activities.Main">
<meta-data
@ -223,15 +223,6 @@
android:value=".activities.Main" />
</activity>
<activity
android:name=".activities.DirectMessagesUserInbox"
android:parentActivityName=".activities.DirectMessages">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.DirectMessages" />
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"

View File

@ -2,6 +2,7 @@ package awais.instagrabber;
import android.content.ClipboardManager;
import android.content.Context;
import android.util.Log;
import androidx.core.app.NotificationManagerCompat;
import androidx.multidex.MultiDexApplication;
@ -32,6 +33,16 @@ public final class InstaApp extends MultiDexApplication {
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
try {
Class.forName("dalvik.system.CloseGuard")
.getMethod("setEnabled", boolean.class)
.invoke(null, true);
} catch (Exception e) {
Log.e("InstaApp", "Error", e);
}
}
if (!BuildConfig.DEBUG) CrashReporter.get(this).start();
logCollector = new LogCollector(this);

View File

@ -1,118 +0,0 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import awais.instagrabber.R;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.adapters.DirectMessagesAdapter;
import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.direct_messages.InboxModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class DirectMessages extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
private final ArrayList<InboxThreadModel> inboxThreadModelList = new ArrayList<>();
private final DirectMessagesAdapter messagesAdapter = new DirectMessagesAdapter(inboxThreadModelList, v -> {
final Object tag = v.getTag();
if (tag instanceof InboxThreadModel) {
startActivity(new Intent(this, DirectMessagesUserInbox.class)
.putExtra(Constants.EXTRAS_THREAD_MODEL, (InboxThreadModel) tag)
);
}
});
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@Override
public void doBefore() {
dmsBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxModel inboxModel) {
if (inboxModel != null) {
endCursor = inboxModel.getOldestCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null;
// todo get request / unseen count from inboxModel
final InboxThreadModel[] threads = inboxModel.getThreads();
if (threads != null && threads.length > 0) {
final int oldSize = inboxThreadModelList.size();
inboxThreadModelList.addAll(Arrays.asList(threads));
messagesAdapter.notifyItemRangeInserted(oldSize, threads.length);
}
}
dmsBinding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor();
}
};
private String endCursor;
private RecyclerLazyLoader lazyLoader;
private AsyncTask<Void, Void, InboxModel> currentlyRunning;
private ActivityDmsBinding dmsBinding;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater());
setContentView(dmsBinding.getRoot());
dmsBinding.swipeRefreshLayout.setOnRefreshListener(this);
dmsBinding.toolbar.toolbar.setTitle(R.string.action_dms);
dmsBinding.commentText.setVisibility(View.GONE);
dmsBinding.commentSend.setVisibility(View.GONE);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager);
dmsBinding.rvDirectMessages.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
dmsBinding.rvDirectMessages.setAdapter(messagesAdapter);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor))
currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
dmsBinding.rvDirectMessages.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onRefresh() {
endCursor = null;
lazyLoader.resetState();
inboxThreadModelList.clear();
messagesAdapter.notifyDataSetChanged();
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
}
}

View File

@ -0,0 +1,73 @@
package awais.instagrabber.activities;
import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ActivityDirectMessagesBinding;
import awais.instagrabber.fragments.directmessages.DirectMessageThreadFragmentArgs;
public class DirectMessagesActivity extends BaseLanguageActivity implements NavController.OnDestinationChangedListener {
private TextView toolbarTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityDirectMessagesBinding binding = ActivityDirectMessagesBinding.inflate(getLayoutInflater());
final CoordinatorLayout root = binding.getRoot();
setContentView(root);
toolbarTitle = binding.toolbarTitle;
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
final NavController navController = Navigation.findNavController(this, R.id.direct_messages_nav_host_fragment);
navController.addOnDestinationChangedListener(this);
final AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration);
}
@Override
public void onDestinationChanged(@NonNull final NavController controller,
@NonNull final NavDestination destination,
@Nullable final Bundle arguments) {
switch (destination.getId()) {
case R.id.directMessagesInboxFragment:
setToolbarTitle(R.string.action_dms);
return;
case R.id.directMessagesThreadFragment:
if (arguments == null) {
return;
}
final String title = DirectMessageThreadFragmentArgs.fromBundle(arguments).getTitle();
setToolbarTitle(title);
return;
}
}
private void setToolbarTitle(final String text) {
if (toolbarTitle == null) {
return;
}
toolbarTitle.setText(text);
}
private void setToolbarTitle(final int resourceId) {
if (toolbarTitle == null) {
return;
}
toolbarTitle.setText(resourceId);
}
}

View File

@ -1,269 +0,0 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import awais.instagrabber.R;
import awais.instagrabber.adapters.MessageItemsAdapter;
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class DirectMessagesUserInbox extends BaseLanguageActivity {
private DirectItemModel directItemModel;
private final ProfileModel myProfileHolder =
new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
private final ArrayList<ProfileModel> users = new ArrayList<>(), leftusers = new ArrayList<>();
private final ArrayList<DirectItemModel> directItemModels = new ArrayList<>();
private String threadid;
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override
public void doBefore() {
dmsBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxThreadModel result) {
if (result == null && ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor) || Utils.isEmpty(endCursor)))
Toast.makeText(DirectMessagesUserInbox.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (result != null) {
endCursor = result.getPrevCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null;
users.clear();
users.addAll(Arrays.asList(result.getUsers()));
leftusers.clear();
leftusers.addAll(Arrays.asList(result.getLeftUsers()));
threadid = result.getThreadId();
dmsBinding.toolbar.toolbar.setTitle(result.getThreadTitle());
String[] users = new String[result.getUsers().length];
for (int i = 0; i < users.length; ++i) {
users[i] = result.getUsers()[i].getUsername();
}
dmsBinding.toolbar.toolbar.setSubtitle(String.join(", ", users));
final int oldSize = directItemModels.size();
final List<DirectItemModel> itemModels = Arrays.asList(result.getItems());
directItemModels.addAll(itemModels);
messageItemsAdapter.notifyItemRangeInserted(oldSize, itemModels.size());
}
dmsBinding.swipeRefreshLayout.setRefreshing(false);
}
};
private String endCursor;
private ActivityDmsBinding dmsBinding;
private MessageItemsAdapter messageItemsAdapter;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater());
setContentView(dmsBinding.getRoot());
final InboxThreadModel threadModel;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_THREAD_MODEL) ||
(threadModel = (InboxThreadModel) intent.getSerializableExtra(Constants.EXTRAS_THREAD_MODEL)) == null) {
Utils.errorFinish(this);
return;
}
dmsBinding.swipeRefreshLayout.setEnabled(false);
dmsBinding.commentText.setVisibility(View.VISIBLE);
dmsBinding.commentSend.setVisibility(View.VISIBLE);
dmsBinding.commentSend.setOnClickListener(newCommentListener);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, true);
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager);
dmsBinding.rvDirectMessages.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor)) {
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER,
endCursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
}
}));
messageItemsAdapter = new MessageItemsAdapter(directItemModels, users, leftusers, v -> {
Object tag = v.getTag();
if (tag instanceof DirectItemModel) {
directItemModel = (DirectItemModel) tag;
final DirectItemType itemType = directItemModel.getItemType();
switch (itemType) {
case MEDIA_SHARE:
startActivity(new Intent(this, PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(directItemModel.getMediaModel().getCode(), false)));
break;
case LINK:
Intent linkIntent = new Intent(Intent.ACTION_VIEW);
linkIntent.setData(Uri.parse(directItemModel.getLinkModel().getLinkContext().getLinkUrl()));
startActivity(linkIntent);
break;
case TEXT:
case REEL_SHARE:
Utils.copyText(v.getContext(), directItemModel.getText());
Toast.makeText(v.getContext(), R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
break;
case RAVEN_MEDIA:
case MEDIA:
Utils.dmDownload(this, getUser(directItemModel.getUserId()).getUsername(), DownloadMethod.DOWNLOAD_DIRECT,
Collections.singletonList(itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia()));
Toast.makeText(v.getContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
StoryModel sm = new StoryModel(
directItemModel.getReelShare().getReelId(),
directItemModel.getReelShare().getMedia().getVideoUrl(),
directItemModel.getReelShare().getMedia().getMediaType(),
directItemModel.getTimestamp(),
directItemModel.getReelShare().getReelOwnerName(),
String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
false
);
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm};
startActivity(new Intent(this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
.putExtra(Constants.EXTRAS_STORIES, sms)
);
}
else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
}
break;
case PLACEHOLDER:
if (directItemModel.getText().toString().contains("@"))
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
break;
default:
Log.d("austin_debug", "unsupported type "+itemType);
}
}
},
(view, text, isHashtag) -> {
searchUsername(text);
});
dmsBinding.rvDirectMessages.setAdapter(messageItemsAdapter);
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
if (leftusers != null)
for (final ProfileModel leftuser : leftusers) {
if (Long.toString(userId).equals(leftuser.getId())) result = leftuser;
}
return result;
}
return null;
}
private void searchUsername(final String text) {
startActivity(new Intent(getApplicationContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text));
}
private final View.OnClickListener newCommentListener = v -> {
if (Utils.isEmpty(dmsBinding.commentText.getText().toString()) && v == dmsBinding.commentSend)
Toast.makeText(getApplicationContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
else if (v == dmsBinding.commentSend) new CommentAction().execute();
};
class CommentAction extends AsyncTask<Void, Void, Void> {
boolean ok = false;
protected Void doInBackground(Void... lmao) {
final String url2 = "https://i.instagram.com/api/v1/direct_v2/threads/broadcast/text/";
final String cookie = settingsHelper.getString(Constants.COOKIE);
try {
final HttpURLConnection urlConnection2 = (HttpURLConnection) new URL(url2).openConnection();
urlConnection2.setRequestMethod("POST");
urlConnection2.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection2.setUseCaches(false);
final String commentText = URLEncoder.encode(dmsBinding.commentText.getText().toString(), "UTF-8")
.replaceAll("\\+", "%20").replaceAll("\\%21", "!").replaceAll("\\%27", "'")
.replaceAll("\\%28", "(").replaceAll("\\%29", ")").replaceAll("\\%7E", "~");
final String cc = UUID.randomUUID().toString();
final String urlParameters2 = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+"\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+"\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+"\",\"client_context\":\"" + cc
+"\",\"mutation_token\":\"" + cc
+"\",\"text\":\"" + commentText
+"\",\"thread_ids\":\"["+threadid
+"]\",\"action\":\"send_item\"}");
urlConnection2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection2.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters2.getBytes().length));
urlConnection2.setDoOutput(true);
DataOutputStream wr2 = new DataOutputStream(urlConnection2.getOutputStream());
wr2.writeBytes(urlParameters2);
wr2.flush();
wr2.close();
urlConnection2.connect();
Log.d("austin_debug", urlConnection2.getResponseCode() + " " + urlParameters2 + " " + cookie);
if (urlConnection2.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = true;
}
urlConnection2.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "dm send: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (!ok) Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else {
dmsBinding.commentText.setText("");
dmsBinding.commentText.clearFocus();
directItemModels.clear();
messageItemsAdapter.notifyDataSetChanged();
new UserInboxFetcher(threadid, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
}

View File

@ -85,7 +85,8 @@ public final class Main extends BaseLanguageActivity {
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result)
);
else Toast.makeText(Main.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else
Toast.makeText(Main.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
@ -142,7 +143,7 @@ public final class Main extends BaseLanguageActivity {
if (mainHelper != null && !Utils.isEmpty(result)) {
closeAnyOpenDrawer();
addToStack();
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@"+result);
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@" + result);
mainHelper.onRefresh();
}
};
@ -153,7 +154,8 @@ public final class Main extends BaseLanguageActivity {
if (!Utils.isEmpty(username)) {
if (!BuildConfig.DEBUG) {
userQuery = username;
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing()) mainHelper.onRefresh();
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing())
mainHelper.onRefresh();
}
// adds cookies to database for quick access
cookieModel = Utils.dataBox.getCookie(uid);
@ -167,7 +169,7 @@ public final class Main extends BaseLanguageActivity {
final String username = cookieModel.getUsername();
if (username != null) {
found = true;
fetchListener.onResult("@"+username);
fetchListener.onResult("@" + username);
}
}
@ -198,10 +200,10 @@ public final class Main extends BaseLanguageActivity {
intent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
}
else intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
} else
intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(intent);
};
@ -240,7 +242,8 @@ public final class Main extends BaseLanguageActivity {
mainBinding.profileView.privatePage2.setText(mainHelper.isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
}
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null) mainHelper.onRefresh();
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null)
mainHelper.onRefresh();
mainHelper.onIntent(getIntent());
}
@ -300,7 +303,7 @@ public final class Main extends BaseLanguageActivity {
if (item == downloadAction)
downloadSelectedItems();
else if (item == dmsAction)
startActivity(new Intent(this, DirectMessages.class));
startActivity(new Intent(this, DirectMessagesActivity.class));
else if (item == notifAction)
startActivity(new Intent(this, NotificationsViewer.class));
else if (item == settingsAction)
@ -335,7 +338,7 @@ public final class Main extends BaseLanguageActivity {
searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setOnSearchClickListener(v -> {
searchView.setQuery((cookieModel != null && userQuery != null && userQuery.equals("@"+cookieModel.getUsername())) ? "" : userQuery, false);
searchView.setQuery((cookieModel != null && userQuery != null && userQuery.equals("@" + cookieModel.getUsername())) ? "" : userQuery, false);
menu.findItem(R.id.action_about).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_dms).setVisible(false);
@ -399,7 +402,10 @@ public final class Main extends BaseLanguageActivity {
private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null)
try { prevSuggestionAsync.cancel(true); } catch (final Exception ignored) { }
try {
prevSuggestionAsync.cancel(true);
} catch (final Exception ignored) {
}
}
@Override
@ -410,7 +416,7 @@ public final class Main extends BaseLanguageActivity {
closeAnyOpenDrawer();
addToStack();
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@"+query);
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@" + query);
searchAction.collapseActionView();
searchView.setIconified(true);
searchView.setIconified(true);
@ -467,8 +473,7 @@ public final class Main extends BaseLanguageActivity {
mainHelper.onRefresh();
return;
}
}
else {
} else {
finish();
}
}

View File

@ -0,0 +1,54 @@
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.DirectMessageInboxItemViewHolder;
import awais.instagrabber.databinding.LayoutIncludeSimpleItemBinding;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
public final class DirectMessageInboxAdapter extends ListAdapter<InboxThreadModel, DirectMessageInboxItemViewHolder> {
private final OnItemClickListener onClickListener;
private static final DiffUtil.ItemCallback<InboxThreadModel> diffCallback = new DiffUtil.ItemCallback<InboxThreadModel>() {
@Override
public boolean areItemsTheSame(@NonNull final InboxThreadModel oldItem, @NonNull final InboxThreadModel newItem) {
return oldItem.getThreadId().equals(newItem.getThreadId());
}
@Override
public boolean areContentsTheSame(@NonNull final InboxThreadModel oldItem, @NonNull final InboxThreadModel newItem) {
return oldItem.equals(newItem);
}
};
public DirectMessageInboxAdapter(final OnItemClickListener onClickListener) {
super(diffCallback);
this.onClickListener = onClickListener;
}
@NonNull
@Override
public DirectMessageInboxItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final LayoutIncludeSimpleItemBinding binding = LayoutIncludeSimpleItemBinding.inflate(layoutInflater, parent, false);
return new DirectMessageInboxItemViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final DirectMessageInboxItemViewHolder holder, final int position) {
final InboxThreadModel threadModel = getItem(position);
if (onClickListener != null) {
holder.itemView.setOnClickListener((v) -> onClickListener.onItemClick(threadModel));
}
holder.bind(threadModel);
}
public interface OnItemClickListener {
void onItemClick(final InboxThreadModel inboxThreadModel);
}
}

View File

@ -1,131 +0,0 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.DirectMessageViewHolder;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemActionLogModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
public final class DirectMessagesAdapter extends RecyclerView.Adapter<DirectMessageViewHolder> {
private final ArrayList<InboxThreadModel> inboxThreadModels;
private final View.OnClickListener onClickListener;
private LayoutInflater layoutInflater;
public DirectMessagesAdapter(final ArrayList<InboxThreadModel> inboxThreadModels, final View.OnClickListener onClickListener) {
this.inboxThreadModels = inboxThreadModels;
this.onClickListener = onClickListener;
}
@NonNull
@Override
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new DirectMessageViewHolder(layoutInflater.inflate(R.layout.layout_include_simple_item, parent, false),
onClickListener);
}
@Override
public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) {
final InboxThreadModel threadModel = inboxThreadModels.get(position);
final DirectItemModel[] itemModels;
holder.itemView.setTag(threadModel);
final RequestManager glideRequestManager = Glide.with(holder.itemView);
if (threadModel != null && (itemModels = threadModel.getItems()) != null) {
final ProfileModel[] users = threadModel.getUsers();
if (users.length > 1) {
holder.ivProfilePic.setVisibility(View.GONE);
holder.multipleProfilePicsContainer.setVisibility(View.VISIBLE);
for (int i = 0; i < Math.min(3, users.length); ++i)
glideRequestManager.load(users[i].getSdProfilePic()).into(holder.multipleProfilePics[i]);
} else {
holder.ivProfilePic.setVisibility(View.VISIBLE);
holder.multipleProfilePicsContainer.setVisibility(View.GONE);
glideRequestManager.load(users.length == 1 ? users[0].getSdProfilePic() : null).into(holder.ivProfilePic);
}
holder.tvUsername.setText(threadModel.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1];
final DirectItemType itemType = lastItemModel.getItemType();
holder.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = layoutInflater.getContext();
final CharSequence messageText;
switch (itemType) {
case TEXT:
case LIKE:
messageText = lastItemModel.getText();
break;
case LINK:
messageText = context.getString(R.string.direct_messages_sent_link);
break;
case MEDIA:
case MEDIA_SHARE:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
case ACTION_LOG:
final DirectItemActionLogModel logModel = lastItemModel.getActionLogModel();
messageText = logModel != null ? logModel.getDescription() : "...";
break;
case REEL_SHARE:
final DirectItemReelShareModel reelShare = lastItemModel.getReelShare();
if (reelShare == null)
messageText = context.getString(R.string.direct_messages_sent_media);
else {
final String reelType = reelShare.getType();
final int textRes;
if ("reply".equals(reelType))
textRes = R.string.direct_messages_replied_story;
else if ("mention".equals(reelType))
textRes = R.string.direct_messages_mention_story;
else if ("reaction".equals(reelType))
textRes = R.string.direct_messages_reacted_story;
else textRes = R.string.direct_messages_sent_media;
messageText = context.getString(textRes) + " : " + reelShare.getText();
}
break;
case RAVEN_MEDIA:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
default:
messageText = "<i>Unsupported message</i>";
}
holder.tvMessage.setText(HtmlCompat.fromHtml(messageText.toString(), 63));
holder.tvDate.setText(lastItemModel.getDateTime());
}
}
@Override
public int getItemCount() {
return inboxThreadModels == null ? 0 : inboxThreadModels.size();
}
}

View File

@ -1,410 +1,66 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.content.res.Resources;
import android.text.Spanned;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.List;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.activities.Main;
import awais.instagrabber.adapters.viewholder.directmessages.TextMessageViewHolder;
import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageViewHolder;
import awais.instagrabber.databinding.ItemMessageItemBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemMediaModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemRavenMediaModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkContext;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemVoiceMediaModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.RavenExpiringMediaActionSummaryModel;
public final class MessageItemsAdapter extends RecyclerView.Adapter<TextMessageViewHolder> {
private static final int MESSAGE_INCOMING = 69, MESSAGE_OUTGOING = 420;
private final ProfileModel myProfileHolder =
new ProfileModel(false, false, false,
Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE)),
null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
private final ArrayList<DirectItemModel> directItemModels;
private final ArrayList<ProfileModel> users, leftusers;
public final class MessageItemsAdapter extends ListAdapter<DirectItemModel, DirectMessageViewHolder> {
private final List<ProfileModel> users;
private final List<ProfileModel> leftUsers;
private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener;
private final View.OnClickListener openProfileClickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof ProfileModel) {
// todo do profile stuff
final ProfileModel profileModel = (ProfileModel) tag;
Log.d("AWAISKING_APP", "--> " + profileModel);
private static final DiffUtil.ItemCallback<DirectItemModel> diffCallback = new DiffUtil.ItemCallback<DirectItemModel>() {
@Override
public boolean areItemsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
return oldItem.getItemId().equals(newItem.getItemId());
}
@Override
public boolean areContentsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
return oldItem.getItemId().equals(newItem.getItemId());
}
};
private final int itemMargin;
private DirectItemVoiceMediaModel prevVoiceModel;
private ImageView prevPlayIcon;
private final View.OnClickListener voicePlayClickListener = v -> {
final Object tag = v.getTag();
if (v instanceof ViewGroup && tag instanceof DirectItemVoiceMediaModel) {
final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0);
final DirectItemVoiceMediaModel voiceMediaModel = (DirectItemVoiceMediaModel) tag;
final boolean voicePlaying = voiceMediaModel.isPlaying();
voiceMediaModel.setPlaying(!voicePlaying);
if (voiceMediaModel == prevVoiceModel) {
// todo pause / resume
} else {
// todo release prev audio, start new voice
if (prevVoiceModel != null) prevVoiceModel.setPlaying(false);
if (prevPlayIcon != null) prevPlayIcon.setImageResource(android.R.drawable.ic_media_play);
}
if (voicePlaying) {
playIcon.setImageResource(android.R.drawable.ic_media_play);
} else {
playIcon.setImageResource(android.R.drawable.ic_media_pause);
}
prevVoiceModel = voiceMediaModel;
prevPlayIcon = playIcon;
}
};
private Context context;
private LayoutInflater layoutInflater;
private String strDmYou;
public MessageItemsAdapter(final ArrayList<DirectItemModel> directItemModels, final ArrayList<ProfileModel> users,
final ArrayList<ProfileModel> leftusers, final View.OnClickListener onClickListener,
public MessageItemsAdapter(final List<ProfileModel> users,
final List<ProfileModel> leftUsers,
final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) {
super(diffCallback);
this.users = users;
this.leftusers = leftusers;
this.directItemModels = directItemModels;
this.leftUsers = leftUsers;
this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
}
@NonNull
@Override
public TextMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (context == null) context = parent.getContext();
if (strDmYou == null) strDmYou = context.getString(R.string.direct_messages_you);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
return new TextMessageViewHolder(layoutInflater.inflate(R.layout.item_message_item, parent, false),
onClickListener, mentionClickListener);
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemMessageItemBinding binding = ItemMessageItemBinding.inflate(layoutInflater, parent, false);
return new DirectMessageViewHolder(binding, users, leftUsers);
}
@Override
public void onBindViewHolder(@NonNull final TextMessageViewHolder holder, final int position) {
final DirectItemModel directItemModel = directItemModels.get(position);
holder.itemView.setTag(directItemModel);
if (directItemModel != null) {
final DirectItemType itemType = directItemModel.getItemType();
final ProfileModel user = getUser(directItemModel.getUserId());
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams();
layoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0,
type == MESSAGE_INCOMING ? itemMargin : 0, 0);
holder.tvMessage.setVisibility(View.GONE);
holder.voiceMessageContainer.setVisibility(View.GONE);
holder.ivAnimatedMessage.setVisibility(View.GONE);
holder.linkMessageContainer.setVisibility(View.GONE);
holder.mediaMessageContainer.setVisibility(View.GONE);
holder.mediaTypeIcon.setVisibility(View.GONE);
holder.mediaExpiredIcon.setVisibility(View.GONE);
holder.profileMessageContainer.setVisibility(View.GONE);
holder.isVerified.setVisibility(View.GONE);
holder.btnOpenProfile.setVisibility(View.GONE);
holder.btnOpenProfile.setOnClickListener(null);
holder.btnOpenProfile.setTag(null);
CharSequence text = "?";
if (user != null && user != myProfileHolder) text = user.getUsername();
else if (user == myProfileHolder) text = strDmYou;
text = text + " - " + directItemModel.getDateTime();
holder.tvUsername.setText(text);
holder.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
final RequestManager glideRequestManager = Glide.with(holder.itemView);
if (type == MESSAGE_INCOMING && user != null)
glideRequestManager.load(user.getSdProfilePic()).into(holder.ivProfilePic);
DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
switch (itemType) {
case PLACEHOLDER:
holder.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), 63));
holder.tvMessage.setVisibility(View.VISIBLE);
break;
case TEXT:
case LIKE:
text = directItemModel.getText();
text = Utils.getSpannableUrl(text.toString()); // for urls
if (Utils.hasMentions(text)) text = Utils.getMentionText(text); // for mentions
if (text instanceof Spanned) holder.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
else if (text == "") holder.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
else holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
break;
case LINK: {
final DirectItemLinkModel link = directItemModel.getLinkModel();
final DirectItemLinkContext linkContext = link.getLinkContext();
final String linkImageUrl = linkContext.getLinkImageUrl();
if (!Utils.isEmpty(linkImageUrl)) {
glideRequestManager.load(linkImageUrl).into(holder.ivLinkPreview);
holder.tvLinkTitle.setText(linkContext.getLinkTitle());
holder.tvLinkSummary.setText(linkContext.getLinkSummary());
holder.ivLinkPreview.setVisibility(View.VISIBLE);
holder.linkMessageContainer.setVisibility(View.VISIBLE);
}
holder.tvMessage.setText(Utils.getSpannableUrl(link.getText()));
holder.tvMessage.setVisibility(View.VISIBLE);
}
break;
case MEDIA_SHARE:
{
final ProfileModel modelUser = mediaModel.getUser();
if (modelUser != null) {
holder.tvMessage.setText(HtmlCompat.fromHtml("<small>"+context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername())+"</small>", 63));
holder.tvMessage.setVisibility(View.VISIBLE);
}
}
case MEDIA: {
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview);
final MediaItemType modelMediaType = mediaModel.getMediaType();
holder.mediaTypeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
break;
case RAVEN_MEDIA: {
final DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel();
final boolean isExpired = ravenMediaModel == null || (mediaModel = ravenMediaModel.getMedia()) == null ||
Utils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
final RavenExpiringMediaActionSummaryModel mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary();
holder.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
int textRes = R.string.dms_inbox_raven_media_unknown;
if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
if (!isExpired) {
if (mediaActionSummary != null) {
final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType();
if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED)
textRes = R.string.dms_inbox_raven_media_delivered;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT)
textRes = R.string.dms_inbox_raven_media_sent;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED)
textRes = R.string.dms_inbox_raven_media_opened;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED)
textRes = R.string.dms_inbox_raven_media_replayed;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING)
textRes = R.string.dms_inbox_raven_media_sending;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED)
textRes = R.string.dms_inbox_raven_media_blocked;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED)
textRes = R.string.dms_inbox_raven_media_suggested;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT)
textRes = R.string.dms_inbox_raven_media_screenshot;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER)
textRes = R.string.dms_inbox_raven_media_cant_deliver;
}
final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType();
if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) {
final MediaItemType mediaType = mediaModel.getMediaType();
textRes = -1;
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
if (textRes != -1) {
holder.tvMessage.setText(context.getText(textRes));
holder.tvMessage.setVisibility(View.VISIBLE);
}
}
break;
case REEL_SHARE: {
final DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (!Utils.isEmpty(text = reelShare.getText())) {
holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
if (mediaType == null)
holder.mediaExpiredIcon.setVisibility(View.VISIBLE);
else {
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case STORY_SHARE: {
final DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (reelShare == null) {
holder.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), 63));
holder.tvMessage.setVisibility(View.VISIBLE);
}
else {
if (!Utils.isEmpty(text = reelShare.getText())) {
holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case VOICE_MEDIA: {
final DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel();
if (voiceMediaModel != null) {
final int[] waveformData = voiceMediaModel.getWaveformData();
if (waveformData != null) holder.waveformSeekBar.setSample(waveformData);
final long durationMs = voiceMediaModel.getDurationMs();
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs));
holder.waveformSeekBar.setProgress(voiceMediaModel.getProgress());
holder.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> {
// todo progress audio player
voiceMediaModel.setProgress(progress);
if (fromUser)
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs * progress / 100));
});
holder.btnPlayVoice.setTag(voiceMediaModel);
holder.btnPlayVoice.setOnClickListener(voicePlayClickListener);
} else {
holder.waveformSeekBar.setProgress(0);
}
holder.voiceMessageContainer.setVisibility(View.VISIBLE);
}
break;
case ANIMATED_MEDIA: {
glideRequestManager.asGif().load(directItemModel.getAnimatedMediaModel().getGifUrl())
.into(holder.ivAnimatedMessage);
holder.ivAnimatedMessage.setVisibility(View.VISIBLE);
}
break;
case PROFILE: {
final ProfileModel profileModel = directItemModel.getProfileModel();
Glide.with(holder.ivMessageProfilePic).load(profileModel.getSdProfilePic())
.into(holder.ivMessageProfilePic);
holder.btnOpenProfile.setTag(profileModel);
holder.btnOpenProfile.setOnClickListener(openProfileClickListener);
holder.tvProfileName.setText(profileModel.getName());
holder.tvProfileUsername.setText(profileModel.getUsername());
holder.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
holder.btnOpenProfile.setVisibility(View.VISIBLE);
holder.profileMessageContainer.setVisibility(View.VISIBLE);
}
break;
case VIDEO_CALL_EVENT: {
// todo add call event info
holder.tvMessage.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(0xFF_1F90E6);
}
break;
case ACTION_LOG: {
text = directItemModel.getActionLogModel().getDescription();
holder.tvMessage.setText(HtmlCompat.fromHtml("<small>"+text+"</small>", 63));
holder.tvMessage.setVisibility(View.VISIBLE);
}
break;
}
}
public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) {
final DirectItemModel directItemModel = getItem(position);
holder.bind(directItemModel);
}
@Override
public int getItemViewType(final int position) {
return directItemModels.get(position).getItemType().ordinal();
}
@Override
public int getItemCount() {
return directItemModels == null ? 0 : directItemModels.size();
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
if (leftusers != null)
for (final ProfileModel leftuser : leftusers) {
if (Long.toString(userId).equals(leftuser.getId())) result = leftuser;
}
return result;
}
return null;
return getItem(position).getItemType().ordinal();
}
}

View File

@ -0,0 +1,107 @@
package awais.instagrabber.adapters.viewholder;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import awais.instagrabber.R;
import awais.instagrabber.databinding.LayoutIncludeSimpleItemBinding;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout multipleProfilePicsContainer;
private final ImageView[] multipleProfilePics;
private final LayoutIncludeSimpleItemBinding binding;
public DirectMessageInboxItemViewHolder(@NonNull final LayoutIncludeSimpleItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.tvLikes.setVisibility(View.GONE);
multipleProfilePicsContainer = binding.container;
final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1);
multipleProfilePics = new ImageView[]{
(ImageView) multipleProfilePicsContainer.getChildAt(0),
(ImageView) containerChild.getChildAt(0),
(ImageView) containerChild.getChildAt(1)
};
binding.tvDate.setSelected(true);
binding.tvUsername.setSelected(true);
}
public void bind(final InboxThreadModel model) {
final DirectItemModel[] itemModels;
if (model == null || (itemModels = model.getItems()) == null) {
return;
}
itemView.setTag(model);
final RequestManager glideRequestManager = Glide.with(itemView);
final ProfileModel[] users = model.getUsers();
if (users.length > 1) {
binding.ivProfilePic.setVisibility(View.GONE);
multipleProfilePicsContainer.setVisibility(View.VISIBLE);
for (int i = 0; i < Math.min(3, users.length); ++i)
glideRequestManager.load(users[i].getSdProfilePic()).into(multipleProfilePics[i]);
} else {
binding.ivProfilePic.setVisibility(View.VISIBLE);
multipleProfilePicsContainer.setVisibility(View.GONE);
glideRequestManager.load(users.length == 1 ? users[0].getSdProfilePic() : null).into(binding.ivProfilePic);
}
binding.tvUsername.setText(model.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1];
final DirectItemType itemType = lastItemModel.getItemType();
binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = itemView.getContext();
final CharSequence messageText;
switch (itemType) {
case TEXT:
case LIKE:
messageText = lastItemModel.getText();
break;
case LINK:
messageText = context.getString(R.string.direct_messages_sent_link);
break;
case MEDIA:
case MEDIA_SHARE:
case RAVEN_MEDIA:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
case ACTION_LOG:
final DirectItemModel.DirectItemActionLogModel logModel = lastItemModel.getActionLogModel();
messageText = logModel != null ? logModel.getDescription() : "...";
break;
case REEL_SHARE:
final DirectItemModel.DirectItemReelShareModel reelShare = lastItemModel.getReelShare();
if (reelShare == null)
messageText = context.getString(R.string.direct_messages_sent_media);
else {
final String reelType = reelShare.getType();
final int textRes;
if ("reply".equals(reelType))
textRes = R.string.direct_messages_replied_story;
else if ("mention".equals(reelType))
textRes = R.string.direct_messages_mention_story;
else if ("reaction".equals(reelType))
textRes = R.string.direct_messages_reacted_story;
else textRes = R.string.direct_messages_sent_media;
messageText = context.getString(textRes) + " : " + reelShare.getText();
}
break;
default:
messageText = "<i>Unsupported message</i>";
}
binding.tvComment.setText(HtmlCompat.fromHtml(messageText.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT));
binding.tvDate.setText(lastItemModel.getDateTime());
}
}

View File

@ -1,43 +0,0 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
public final LinearLayout multipleProfilePicsContainer;
public final ImageView[] multipleProfilePics;
public final ImageView ivProfilePic, notTextType;
public final TextView tvUsername, tvDate, tvMessage;
public DirectMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener) {
super(itemView);
if (clickListener != null) itemView.setOnClickListener(clickListener);
itemView.findViewById(R.id.tvLikes).setVisibility(View.GONE);
tvDate = itemView.findViewById(R.id.tvDate);
tvMessage = itemView.findViewById(R.id.tvComment);
tvUsername = itemView.findViewById(R.id.tvUsername);
notTextType = itemView.findViewById(R.id.notTextType);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
multipleProfilePicsContainer = itemView.findViewById(R.id.container);
final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1);
multipleProfilePics = new ImageView[]{
(ImageView) multipleProfilePicsContainer.getChildAt(0),
(ImageView) containerChild.getChildAt(0),
(ImageView) containerChild.getChildAt(1)
};
tvDate.setSelected(true);
tvUsername.setSelected(true);
}
}

View File

@ -0,0 +1,383 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.content.Context;
import android.text.Spanned;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemMessageItemBinding;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT;
public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "DirectMessageViewHolder";
private static final int MESSAGE_INCOMING = 69;
private static final int MESSAGE_OUTGOING = 420;
private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel(Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE)));
private final ItemMessageItemBinding binding;
private final List<ProfileModel> users;
private final List<ProfileModel> leftUsers;
private final int itemMargin;
private final String strDmYou;
private DirectItemModel.DirectItemVoiceMediaModel prevVoiceModel;
private ImageView prevPlayIcon;
private final View.OnClickListener voicePlayClickListener = v -> {
final Object tag = v.getTag();
if (v instanceof ViewGroup && tag instanceof DirectItemModel.DirectItemVoiceMediaModel) {
final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0);
final DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = (DirectItemModel.DirectItemVoiceMediaModel) tag;
final boolean voicePlaying = voiceMediaModel.isPlaying();
voiceMediaModel.setPlaying(!voicePlaying);
if (voiceMediaModel == prevVoiceModel) {
// todo pause / resume
} else {
// todo release prev audio, start new voice
if (prevVoiceModel != null) prevVoiceModel.setPlaying(false);
if (prevPlayIcon != null)
prevPlayIcon.setImageResource(android.R.drawable.ic_media_play);
}
if (voicePlaying) {
playIcon.setImageResource(android.R.drawable.ic_media_play);
} else {
playIcon.setImageResource(android.R.drawable.ic_media_pause);
}
prevVoiceModel = voiceMediaModel;
prevPlayIcon = playIcon;
}
};
private final View.OnClickListener openProfileClickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof ProfileModel) {
// todo do profile stuff
final ProfileModel profileModel = (ProfileModel) tag;
Log.d(TAG, "--> " + profileModel);
}
};
public DirectMessageViewHolder(final ItemMessageItemBinding binding,
final List<ProfileModel> users,
final List<ProfileModel> leftUsers) {
super(binding.getRoot());
this.binding = binding;
this.users = users;
this.leftUsers = leftUsers;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
strDmYou = binding.getRoot().getContext().getString(R.string.direct_messages_you);
}
public void bind(final DirectItemModel directItemModel) {
if (directItemModel == null) {
return;
}
final Context context = itemView.getContext();
itemView.setTag(directItemModel);
final DirectItemType itemType = directItemModel.getItemType();
final ProfileModel user = getUser(directItemModel.getUserId());
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) itemView.getLayoutParams();
layoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0,
type == MESSAGE_INCOMING ? itemMargin : 0, 0);
binding.tvMessage.setVisibility(View.GONE);
final View voiceMessageContainer = (View) binding.waveformSeekBar.getParent();
final View linkMessageContainer = (View) binding.ivLinkPreview.getParent();
final View mediaMessageContainer = (View) binding.ivMediaPreview.getParent();
final View mediaTypeIcon = binding.typeIcon;
final View profileMessageContainer = (View) binding.profileInfo.getParent();
voiceMessageContainer.setVisibility(View.GONE);
binding.ivAnimatedMessage.setVisibility(View.GONE);
linkMessageContainer.setVisibility(View.GONE);
mediaMessageContainer.setVisibility(View.GONE);
mediaTypeIcon.setVisibility(View.GONE);
binding.mediaExpiredIcon.setVisibility(View.GONE);
profileMessageContainer.setVisibility(View.GONE);
binding.isVerified.setVisibility(View.GONE);
final FrameLayout btnOpenProfile = binding.btnInfo;
btnOpenProfile.setVisibility(View.GONE);
btnOpenProfile.setOnClickListener(null);
btnOpenProfile.setTag(null);
CharSequence text = "?";
if (user != null && user != myProfileHolder) text = user.getUsername();
else if (user == myProfileHolder) text = strDmYou;
text = text + " - " + directItemModel.getDateTime();
binding.tvUsername.setText(text);
binding.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
final RequestManager glideRequestManager = Glide.with(itemView);
if (type == MESSAGE_INCOMING && user != null)
glideRequestManager.load(user.getSdProfilePic()).into(binding.ivProfilePic);
DirectItemModel.DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
switch (itemType) {
case PLACEHOLDER:
binding.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
break;
case TEXT:
case LIKE:
text = directItemModel.getText();
text = Utils.getSpannableUrl(text.toString()); // for urls
if (Utils.hasMentions(text)) text = Utils.getMentionText(text); // for mentions
if (text instanceof Spanned)
binding.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
else if (text == "") {
binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
} else binding.tvMessage.setText(text);
binding.tvMessage.setVisibility(View.VISIBLE);
break;
case LINK: {
final DirectItemModel.DirectItemLinkModel link = directItemModel.getLinkModel();
final DirectItemModel.DirectItemLinkContext linkContext = link.getLinkContext();
final String linkImageUrl = linkContext.getLinkImageUrl();
if (!Utils.isEmpty(linkImageUrl)) {
glideRequestManager.load(linkImageUrl).into(binding.ivLinkPreview);
binding.tvLinkTitle.setText(linkContext.getLinkTitle());
binding.tvLinkSummary.setText(linkContext.getLinkSummary());
binding.ivLinkPreview.setVisibility(View.VISIBLE);
linkMessageContainer.setVisibility(View.VISIBLE);
}
binding.tvMessage.setText(Utils.getSpannableUrl(link.getText()));
binding.tvMessage.setVisibility(View.VISIBLE);
}
break;
case MEDIA_SHARE: {
final ProfileModel modelUser = mediaModel.getUser();
if (modelUser != null) {
binding.tvMessage.setText(HtmlCompat.fromHtml("<small>" + context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername()) + "</small>", FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
}
}
case MEDIA: {
glideRequestManager.load(mediaModel.getThumbUrl()).into(binding.ivMediaPreview);
final MediaItemType modelMediaType = mediaModel.getMediaType();
mediaTypeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
break;
case RAVEN_MEDIA: {
final DirectItemModel.DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel();
final boolean isExpired = ravenMediaModel == null || (mediaModel = ravenMediaModel.getMedia()) == null ||
Utils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
DirectItemModel.RavenExpiringMediaActionSummaryModel mediaActionSummary = null;
if (ravenMediaModel != null) {
mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary();
}
binding.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
int textRes = R.string.dms_inbox_raven_media_unknown;
if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
if (!isExpired) {
if (mediaActionSummary != null) {
final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType();
if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED)
textRes = R.string.dms_inbox_raven_media_delivered;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT)
textRes = R.string.dms_inbox_raven_media_sent;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED)
textRes = R.string.dms_inbox_raven_media_opened;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED)
textRes = R.string.dms_inbox_raven_media_replayed;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING)
textRes = R.string.dms_inbox_raven_media_sending;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED)
textRes = R.string.dms_inbox_raven_media_blocked;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED)
textRes = R.string.dms_inbox_raven_media_suggested;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT)
textRes = R.string.dms_inbox_raven_media_screenshot;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER)
textRes = R.string.dms_inbox_raven_media_cant_deliver;
}
final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType();
if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) {
final MediaItemType mediaType = mediaModel.getMediaType();
textRes = -1;
mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(mediaModel.getThumbUrl()).into(binding.ivMediaPreview);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
if (textRes != -1) {
binding.tvMessage.setText(context.getText(textRes));
binding.tvMessage.setVisibility(View.VISIBLE);
}
}
break;
case REEL_SHARE: {
final DirectItemModel.DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (!Utils.isEmpty(text = reelShare.getText())) {
binding.tvMessage.setText(text);
binding.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemModel.DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
if (mediaType == null)
binding.mediaExpiredIcon.setVisibility(View.VISIBLE);
else {
mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(binding.ivMediaPreview);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case STORY_SHARE: {
final DirectItemModel.DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (reelShare == null) {
binding.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
} else {
if (!Utils.isEmpty(text = reelShare.getText())) {
binding.tvMessage.setText(text);
binding.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemModel.DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(binding.ivMediaPreview);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case VOICE_MEDIA: {
final DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel();
if (voiceMediaModel != null) {
final int[] waveformData = voiceMediaModel.getWaveformData();
if (waveformData != null) binding.waveformSeekBar.setSample(waveformData);
final long durationMs = voiceMediaModel.getDurationMs();
binding.tvVoiceDuration.setText(Utils.millisToString(durationMs));
binding.waveformSeekBar.setProgress(voiceMediaModel.getProgress());
binding.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> {
// todo progress audio player
voiceMediaModel.setProgress(progress);
if (fromUser)
binding.tvVoiceDuration.setText(Utils.millisToString(durationMs * progress / 100));
});
binding.btnPlayVoice.setTag(voiceMediaModel);
binding.btnPlayVoice.setOnClickListener(voicePlayClickListener);
} else {
binding.waveformSeekBar.setProgress(0);
}
voiceMessageContainer.setVisibility(View.VISIBLE);
}
break;
case ANIMATED_MEDIA: {
glideRequestManager.asGif().load(directItemModel.getAnimatedMediaModel().getGifUrl())
.into(binding.ivAnimatedMessage);
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
}
break;
case PROFILE: {
final ProfileModel profileModel = directItemModel.getProfileModel();
Glide.with(binding.profileInfo).load(profileModel.getSdProfilePic())
.into(binding.profileInfo);
btnOpenProfile.setTag(profileModel);
btnOpenProfile.setOnClickListener(openProfileClickListener);
binding.tvFullName.setText(profileModel.getName());
binding.profileInfoText.setText(profileModel.getUsername());
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
btnOpenProfile.setVisibility(View.VISIBLE);
profileMessageContainer.setVisibility(View.VISIBLE);
}
break;
case VIDEO_CALL_EVENT: {
// todo add call event info
binding.tvMessage.setVisibility(View.VISIBLE);
binding.profileInfoText.setBackgroundColor(0xFF_1F90E6);
}
break;
case ACTION_LOG: {
text = directItemModel.getActionLogModel().getDescription();
binding.tvMessage.setText(HtmlCompat.fromHtml("<small>" + text + "</small>", FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
}
break;
}
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
if (leftUsers != null)
for (final ProfileModel leftUser : leftUsers) {
if (Long.toString(userId).equals(leftUser.getId())) result = leftUser;
}
return result;
}
return null;
}
}

View File

@ -1,91 +0,0 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.masoudss_waveform.WaveformSeekBar;
import awais.instagrabber.interfaces.MentionClickListener;
public final class TextMessageViewHolder extends RecyclerView.ViewHolder {
public final CardView rootCardView;
public final TextView tvUsername;
public final ImageView ivProfilePic;
// text message
public final RamboTextView tvMessage;
// expired message icon
public final View mediaExpiredIcon;
// media message
public final View mediaMessageContainer;
public final ImageView ivMediaPreview, mediaTypeIcon;
// profile messag
public final View profileMessageContainer, isVerified, btnOpenProfile;
public final TextView tvProfileUsername, tvProfileName;
public final ImageView ivMessageProfilePic;
// animated message
public final ImageView ivAnimatedMessage;
// link message
public final View linkMessageContainer;
public final ImageView ivLinkPreview;
public final TextView tvLinkTitle, tvLinkSummary;
// voice message
public final View voiceMessageContainer, btnPlayVoice;
public final WaveformSeekBar waveformSeekBar;
public final TextView tvVoiceDuration;
public TextMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) {
super(itemView);
if (clickListener != null) itemView.setOnClickListener(clickListener);
tvUsername = itemView.findViewById(R.id.tvUsername);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
// text message
tvMessage = itemView.findViewById(R.id.tvMessage);
tvMessage.setCaptionIsExpandable(true);
tvMessage.setCaptionIsExpanded(true);
if (mentionClickListener != null) tvMessage.setMentionClickListener(mentionClickListener);
// root view
rootCardView = (CardView) tvMessage.getParent().getParent();
// expired message icon
mediaExpiredIcon = itemView.findViewById(R.id.mediaExpiredIcon);
// media message
ivMediaPreview = itemView.findViewById(R.id.ivMediaPreview);
mediaMessageContainer = (View) ivMediaPreview.getParent();
mediaTypeIcon = mediaMessageContainer.findViewById(R.id.typeIcon);
// profile message
btnOpenProfile = itemView.findViewById(R.id.btnInfo);
ivMessageProfilePic = itemView.findViewById(R.id.profileInfo);
profileMessageContainer = (View) ivMessageProfilePic.getParent();
isVerified = profileMessageContainer.findViewById(R.id.isVerified);
tvProfileName = profileMessageContainer.findViewById(R.id.tvFullName);
tvProfileUsername = profileMessageContainer.findViewById(R.id.profileInfoText);
// animated message
ivAnimatedMessage = itemView.findViewById(R.id.ivAnimatedMessage);
// link message
ivLinkPreview = itemView.findViewById(R.id.ivLinkPreview);
linkMessageContainer = (View) ivLinkPreview.getParent();
tvLinkTitle = linkMessageContainer.findViewById(R.id.tvLinkTitle);
tvLinkSummary = linkMessageContainer.findViewById(R.id.tvLinkSummary);
// voice message
waveformSeekBar = itemView.findViewById(R.id.waveformSeekBar);
voiceMessageContainer = (View) waveformSeekBar.getParent();
btnPlayVoice = voiceMessageContainer.findViewById(R.id.btnPlayVoice);
tvVoiceDuration = voiceMessageContainer.findViewById(R.id.tvVoiceDuration);
}
}

View File

@ -0,0 +1,176 @@
package awais.instagrabber.asyncs;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import awais.instagrabber.models.ImageUploadOptions;
import awais.instagrabber.utils.Utils;
public class ImageUploader extends AsyncTask<ImageUploadOptions, Void, ImageUploader.ImageUploadResponse> {
private static final String TAG = "ImageUploader";
private static final long LOWER = 1000000000L;
private static final long UPPER = 9999999999L;
private OnImageUploadCompleteListener listener;
protected ImageUploadResponse doInBackground(final ImageUploadOptions... imageUploadOptions) {
if (imageUploadOptions == null || imageUploadOptions.length == 0 || imageUploadOptions[0] == null) {
return null;
}
HttpURLConnection connection = null;
OutputStream out = null;
InputStream inputStream = null;
BufferedReader r = null;
ByteArrayOutputStream baos = null;
try {
final ImageUploadOptions options = imageUploadOptions[0];
final Bitmap bitmap = options.getBitmap();
baos = new ByteArrayOutputStream();
final boolean compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
if (!compressResult) {
Log.e(TAG, "Compress result was false!");
return null;
}
final byte[] bytes = baos.toByteArray();
final String contentLength = String.valueOf(bytes.length);
final Map<String, String> headers = new HashMap<>();
final String uploadId = String.valueOf(new Date().getTime());
final long random = LOWER + new Random().nextLong() * (UPPER - LOWER + 1);
final String name = String.format("%s_0_%s", uploadId, random);
final String waterfallId = options.getWaterfallId() != null ? options.getWaterfallId() : UUID.randomUUID().toString();
headers.put("X-Entity-Type", "image/jpeg");
headers.put("Offset", "0");
headers.put("X_FB_PHOTO_WATERFALL_ID", waterfallId);
headers.put("X-Instagram-Rupload-Params", new JSONObject(createPhotoRuploadParams(options, uploadId)).toString());
headers.put("X-Entity-Name", name);
headers.put("X-Entity-Length", contentLength);
headers.put("Content-Type", "application/octet-stream");
headers.put("Content-Length", contentLength);
headers.put("Accept-Encoding", "gzip");
final String url = "https://www.instagram.com/rupload_igphoto/" + name + "/";
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setUseCaches(false);
connection.setDoOutput(true);
Utils.setConnectionHeaders(connection, headers);
out = new BufferedOutputStream(connection.getOutputStream());
out.write(bytes);
out.flush();
final int responseCode = connection.getResponseCode();
Log.d(TAG, "response: " + responseCode);
final String responseCodeString = String.valueOf(responseCode);
final InputStream responseInputStream = responseCodeString.startsWith("4") || responseCodeString.startsWith("5")
? connection.getErrorStream() : connection.getInputStream();
r = new BufferedReader(new InputStreamReader(responseInputStream));
final StringBuilder builder = new StringBuilder();
for (String line = r.readLine(); line != null; line = r.readLine()) {
if (builder.length() != 0) {
builder.append("\n");
}
builder.append(line);
}
return new ImageUploadResponse(responseCode, new JSONObject(builder.toString()));
} catch (Exception ex) {
Log.e(TAG, "Image upload error:", ex);
} finally {
if (r != null) {
try {
r.close();
} catch (IOException ignored) {}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
if (out != null) {
try {
out.close();
} catch (IOException ignored) {}
}
if (connection != null) {
connection.disconnect();
}
if (baos != null) {
try {
baos.close();
} catch (IOException ignored) {}
}
}
return null;
}
@Override
protected void onPostExecute(final ImageUploadResponse response) {
if (listener != null) {
listener.onImageUploadComplete(response);
}
}
private Map<String, String> createPhotoRuploadParams(final ImageUploadOptions options, final String uploadId) {
final Map<String, Integer> retryContext = new HashMap<>();
retryContext.put("num_step_auto_retry", 0);
retryContext.put("num_reupload", 0);
retryContext.put("num_step_manual_retry", 0);
final String retryContextString = new JSONObject(retryContext).toString();
final Map<String, String> params = new HashMap<>();
params.put("retry_context", retryContextString);
params.put("media_type", "1");
params.put("upload_id", uploadId);
params.put("xsharing_user_ids", "[]");
final Map<String, String> imageCompression = new HashMap<>();
imageCompression.put("lib_name", "moz");
imageCompression.put("lib_version", "3.1.m");
imageCompression.put("quality", "80");
params.put("image_compression", new JSONObject(imageCompression).toString());
if (options.isSidecar()) {
params.put("is_sidecar", "1");
}
return params;
}
public void setOnTaskCompleteListener(final OnImageUploadCompleteListener listener) {
if (listener != null) {
this.listener = listener;
}
}
public interface OnImageUploadCompleteListener {
void onImageUploadComplete(ImageUploadResponse response);
}
public static class ImageUploadResponse {
private int responseCode;
private JSONObject response;
public ImageUploadResponse(int responseCode, JSONObject response) {
this.responseCode = responseCode;
this.response = response;
}
public int getResponseCode() {
return responseCode;
}
public JSONObject getResponse() {
return response;
}
}
}

View File

@ -9,6 +9,8 @@ import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
@ -21,17 +23,21 @@ import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.logCollector;
import static awaisomereport.LogCollector.LogFile;
public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadModel> {
public final class DirectMessageInboxThreadFetcher extends AsyncTask<Void, Void, InboxThreadModel> {
private static final String TAG = "DMInboxThreadFetcher";
private final String id;
private final String endCursor;
private final FetchListener<InboxThreadModel> fetchListener;
private final String direction;
private final UserInboxDirection direction;
public UserInboxFetcher(final String id, final UserInboxDirection direction, final String endCursor,
final FetchListener<InboxThreadModel> fetchListener) {
public DirectMessageInboxThreadFetcher(final String id,
final UserInboxDirection direction,
final String cursor,
final FetchListener<InboxThreadModel> fetchListener) {
this.id = id;
this.direction = "&direction=" + (direction == UserInboxDirection.NEWER ? "newer" : "older");
this.endCursor = !Utils.isEmpty(endCursor) ? "&cursor=" + endCursor : "";
this.direction = direction;
this.endCursor = cursor;
this.fetchListener = fetchListener;
}
@ -39,11 +45,14 @@ public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadMod
@Override
protected InboxThreadModel doInBackground(final Void... voids) {
InboxThreadModel result = null;
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?visual_message_return_type=unseen"
+ direction + endCursor;
// todo probably
// & seq_id = seqId
final Map<String, String> queryParamsMap = new HashMap<>();
queryParamsMap.put("visual_message_return_type", "unseen");
queryParamsMap.put("direction", direction.getValue());
if (!Utils.isEmpty(endCursor)) {
queryParamsMap.put("cursor", endCursor);
}
final String queryString = Utils.getQueryString(queryParamsMap);
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?" + queryString;
try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
@ -60,9 +69,8 @@ public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadMod
result = null;
if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DMS_THREAD, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
return result;
}

View File

@ -0,0 +1,207 @@
package awais.instagrabber.asyncs.direct_messages;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DirectThreadBroadcaster extends AsyncTask<DirectThreadBroadcaster.BroadcastOptions, Void, DirectThreadBroadcaster.DirectThreadBroadcastResponse> {
private static final String TAG = "DirectThreadBroadcaster";
private final String threadId;
private OnBroadcastCompleteListener listener;
public DirectThreadBroadcaster(String threadId) {
this.threadId = threadId;
}
@Override
protected DirectThreadBroadcastResponse doInBackground(final BroadcastOptions... broadcastOptionsArray) {
if (broadcastOptionsArray == null || broadcastOptionsArray.length == 0 || broadcastOptionsArray[0] == null) {
return null;
}
final BroadcastOptions broadcastOptions = broadcastOptionsArray[0];
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String cc = UUID.randomUUID().toString();
final Map<String, String> form = new HashMap<>();
form.put("_csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
form.put("_uid", Utils.getUserIdFromCookie(cookie));
form.put("__uuid", settingsHelper.getString(Constants.DEVICE_UUID));
form.put("client_context", cc);
form.put("mutation_token", cc);
form.putAll(broadcastOptions.getFormMap());
form.put("thread_ids", String.format("[%s]", threadId));
form.put("action", "send_item");
final String message = new JSONObject(form).toString();
final String content = Utils.sign(message);
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/broadcast/" + broadcastOptions.getItemType().getValue() + "/";
HttpURLConnection connection = null;
DataOutputStream outputStream = null;
BufferedReader r = null;
try {
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (content != null) {
connection.setRequestProperty("Content-Length", "" + content.getBytes().length);
}
connection.setUseCaches(false);
connection.setDoOutput(true);
outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(content);
outputStream.flush();
final int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
Log.d(TAG, responseCode + ": " + content + ": " + cookie);
return new DirectThreadBroadcastResponse(responseCode, null);
}
r = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final StringBuilder builder = new StringBuilder();
for (String line = r.readLine(); line != null; line = r.readLine()) {
if (builder.length() != 0) {
builder.append("\n");
}
builder.append(line);
}
return new DirectThreadBroadcastResponse(responseCode, new JSONObject(builder.toString()));
} catch (Exception e) {
Log.e(TAG, "Error", e);
} finally {
if (r != null) {
try {
r.close();
} catch (IOException ignored) {
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ignored) {
}
}
if (connection != null) {
connection.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(final DirectThreadBroadcastResponse result) {
if (listener != null) {
listener.onTaskComplete(result);
}
}
public void setOnTaskCompleteListener(final OnBroadcastCompleteListener listener) {
if (listener != null) {
this.listener = listener;
}
}
public interface OnBroadcastCompleteListener {
void onTaskComplete(DirectThreadBroadcastResponse response);
}
public enum ItemType {
TEXT("text"),
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("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~");
}
@Override
Map<String, String> getFormMap() {
return Collections.singletonMap("text", text);
}
}
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 int responseCode;
private 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

@ -0,0 +1,149 @@
package awais.instagrabber.fragments.directmessages;
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.adapters.DirectMessageInboxAdapter;
import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesInboxBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.direct_messages.InboxModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Utils;
public class DirectMessageInboxFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DirectMessagesInboxFrag";
private FragmentActivity fragmentActivity;
private SwipeRefreshLayout root;
private RecyclerView inboxList;
private RecyclerLazyLoader lazyLoader;
private LinearLayoutManager layoutManager;
private String endCursor;
private AsyncTask<Void, Void, InboxModel> currentlyRunning;
private InboxThreadModelListViewModel listViewModel;
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@Override
public void doBefore() {
root.setRefreshing(true);
}
@Override
public void onResult(final InboxModel inboxModel) {
if (inboxModel != null) {
endCursor = inboxModel.getOldestCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor))
endCursor = null;
// todo get request / unseen count from inboxModel
final InboxThreadModel[] threads = inboxModel.getThreads();
if (threads != null && threads.length > 0) {
List<InboxThreadModel> list = listViewModel.getList().getValue();
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
// final int oldSize = list != null ? list.size() : 0;
final List<InboxThreadModel> newList = Arrays.asList(threads);
list.addAll(newList);
listViewModel.getList().postValue(list);
}
}
root.setRefreshing(false);
stopCurrentExecutor();
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = requireActivity();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
return root;
}
final FragmentDirectMessagesInboxBinding binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false);
root = binding.getRoot();
root.setOnRefreshListener(this);
inboxList = binding.inboxList;
inboxList.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(requireContext());
inboxList.setLayoutManager(layoutManager);
final DirectMessageInboxAdapter inboxAdapter = new DirectMessageInboxAdapter(inboxThreadModel -> {
final NavDirections action = DirectMessageInboxFragmentDirections.actionDMInboxFragmentToDMThreadFragment(inboxThreadModel.getThreadId(), inboxThreadModel.getThreadTitle());
NavHostFragment.findNavController(this).navigate(action);
});
inboxList.setAdapter(inboxAdapter);
listViewModel = new ViewModelProvider(fragmentActivity).get(InboxThreadModelListViewModel.class);
listViewModel.getList().observe(fragmentActivity, inboxAdapter::submitList);
initData();
return root;
}
@Override
public void onRefresh() {
endCursor = null;
lazyLoader.resetState();
listViewModel.getList().postValue(Collections.emptyList());
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void initData() {
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor))
currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
inboxList.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
}
public static class InboxThreadModelListViewModel extends ViewModel {
private MutableLiveData<List<InboxThreadModel>> list;
public MutableLiveData<List<InboxThreadModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}
}

View File

@ -0,0 +1,351 @@
package awais.instagrabber.fragments.directmessages;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.ProfileViewer;
import awais.instagrabber.activities.StoryViewer;
import awais.instagrabber.adapters.MessageItemsAdapter;
import awais.instagrabber.asyncs.ImageUploader;
import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ImageUploadOptions;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public class DirectMessageThreadFragment extends Fragment {
private static final String TAG = "DirectMessagesThreadFmt";
private static final int PICK_IMAGE = 100;
private FragmentActivity fragmentActivity;
private String threadId;
private String cursor;
private FragmentDirectMessagesThreadBinding binding;
private DirectItemModelListViewModel listViewModel;
private RecyclerView messageList;
private boolean hasSentSomething;
private boolean hasOlder = true;
private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel();
private final List<ProfileModel> users = new ArrayList<>();
private final List<ProfileModel> leftUsers = new ArrayList<>();
private final View.OnClickListener clickListener = v -> {
if (v == binding.commentSend) {
final String text = binding.commentText.getText().toString();
if (Utils.isEmpty(text)) {
Toast.makeText(requireContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return;
}
sendText(text);
return;
}
if (v == binding.image) {
final Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE);
}
};
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override
public void doBefore() {
binding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxThreadModel result) {
if (result == null && ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor) || Utils.isEmpty(cursor)))
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (result != null) {
cursor = result.getOldestCursor();
hasOlder = result.hasOlder();
if ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor)) {
cursor = null;
}
users.clear();
users.addAll(Arrays.asList(result.getUsers()));
leftUsers.clear();
leftUsers.addAll(Arrays.asList(result.getLeftUsers()));
// thread title is already comma separated username, so no need to set by ourselves
// String[] users = new String[result.getUsers().length];
// for (int i = 0; i < users.length; ++i) {
// users[i] = result.getUsers()[i].getUsername();
// }
List<DirectItemModel> list = listViewModel.getList().getValue();
final List<DirectItemModel> newList = Arrays.asList(result.getItems());
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
if (hasSentSomething) {
list = newList;
hasSentSomething = false;
final Handler handler = new Handler();
handler.postDelayed(() -> {
if (messageList != null) {
messageList.smoothScrollToPosition(0);
}
}, 200);
} else {
list.addAll(newList);
}
listViewModel.getList().postValue(list);
}
binding.swipeRefreshLayout.setRefreshing(false);
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = requireActivity();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
binding = FragmentDirectMessagesThreadBinding.inflate(inflater, container, false);
final LinearLayout root = binding.getRoot();
if (getArguments() == null) {
return root;
}
threadId = DirectMessageThreadFragmentArgs.fromBundle(getArguments()).getThreadId();
binding.swipeRefreshLayout.setEnabled(false);
messageList = binding.messageList;
messageList.setHasFixedSize(true);
binding.commentSend.setOnClickListener(clickListener);
binding.image.setOnClickListener(clickListener);
final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
layoutManager.setReverseLayout(true);
// layoutManager.setStackFromEnd(true);
messageList.setLayoutManager(layoutManager);
messageList.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (Utils.isEmpty(cursor) || !hasOlder) {
return;
}
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, cursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
}));
final View.OnClickListener onClickListener = v -> {
Object tag = v.getTag();
if (tag instanceof DirectItemModel) {
final DirectItemModel directItemModel = (DirectItemModel) tag;
final DirectItemType itemType = directItemModel.getItemType();
switch (itemType) {
case MEDIA_SHARE:
startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(directItemModel.getMediaModel().getCode(), false)));
break;
case LINK:
Intent linkIntent = new Intent(Intent.ACTION_VIEW);
linkIntent.setData(Uri.parse(directItemModel.getLinkModel().getLinkContext().getLinkUrl()));
startActivity(linkIntent);
break;
case TEXT:
case REEL_SHARE:
Utils.copyText(v.getContext(), directItemModel.getText());
Toast.makeText(v.getContext(), R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
break;
case RAVEN_MEDIA:
case MEDIA:
final ProfileModel user = getUser(directItemModel.getUserId());
Utils.dmDownload(requireContext(), user.getUsername(), DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia()));
Toast.makeText(v.getContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
StoryModel sm = new StoryModel(
directItemModel.getReelShare().getReelId(),
directItemModel.getReelShare().getMedia().getVideoUrl(),
directItemModel.getReelShare().getMedia().getMediaType(),
directItemModel.getTimestamp(),
directItemModel.getReelShare().getReelOwnerName(),
String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
false
);
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm};
startActivity(new Intent(requireContext(), StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
.putExtra(Constants.EXTRAS_STORIES, sms)
);
} else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
}
break;
case PLACEHOLDER:
if (directItemModel.getText().toString().contains("@"))
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
break;
default:
Log.d("austin_debug", "unsupported type " + itemType);
}
}
};
final MentionClickListener mentionClickListener = (view, text, isHashtag) -> searchUsername(text);
final MessageItemsAdapter adapter = new MessageItemsAdapter(users, leftUsers, onClickListener, mentionClickListener);
messageList.setAdapter(adapter);
listViewModel = new ViewModelProvider(fragmentActivity).get(DirectItemModelListViewModel.class);
listViewModel.getList().observe(fragmentActivity, adapter::submitList);
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return root;
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) {
Log.w(TAG, "data is null!");
return;
}
final Uri uri = data.getData();
sendImage(uri);
}
}
@Override
public void onDestroy() {
super.onDestroy();
listViewModel.getList().postValue(Collections.emptyList());
}
private void sendText(final String text) {
final DirectThreadBroadcaster.TextBroadcastOptions options;
try {
options = new DirectThreadBroadcaster.TextBroadcastOptions(text);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e);
return;
}
broadcast(options, result -> {
if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
binding.commentText.setText("");
// binding.commentText.clearFocus();
hasSentSomething = true;
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
}
private void sendImage(final Uri imageUri) {
try(InputStream inputStream = requireContext().getContentResolver().openInputStream(imageUri)) {
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// Upload Image
final ImageUploader imageUploader = new ImageUploader();
imageUploader.setOnTaskCompleteListener(response -> {
if (response == null || response.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (response != null && response.getResponse() != null) {
Log.e(TAG, response.getResponse().toString());
}
return;
}
final JSONObject responseJson = response.getResponse();
try {
final String uploadId = responseJson.getString("upload_id");
// Broadcast
final DirectThreadBroadcaster.ImageBroadcastOptions options = new DirectThreadBroadcaster.ImageBroadcastOptions(true, uploadId);
hasSentSomething = true;
broadcast(options, onBroadcastCompleteListener -> new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR));
} catch (JSONException e) {
Log.e(TAG, "Error parsing json response", e);
}
});
final ImageUploadOptions options = ImageUploadOptions.builder(bitmap).build();
imageUploader.execute(options);
}
catch (IOException e) {
Log.e(TAG, "Error opening file", e);
}
}
private void broadcast(final DirectThreadBroadcaster.BroadcastOptions broadcastOptions, final DirectThreadBroadcaster.OnBroadcastCompleteListener listener) {
final DirectThreadBroadcaster broadcaster = new DirectThreadBroadcaster(threadId);
broadcaster.setOnTaskCompleteListener(listener);
broadcaster.execute(broadcastOptions);
}
@NonNull
private ProfileModel getUser(final long userId) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
for (final ProfileModel leftUser : leftUsers) {
if (Long.toString(userId).equals(leftUser.getId())) result = leftUser;
}
return result;
}
private void searchUsername(final String text) {
startActivity(new Intent(requireContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text));
}
public static class DirectItemModelListViewModel extends ViewModel {
private MutableLiveData<List<DirectItemModel>> list;
public MutableLiveData<List<DirectItemModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}
}

View File

@ -0,0 +1,62 @@
package awais.instagrabber.models;
import android.graphics.Bitmap;
public class ImageUploadOptions {
private final Bitmap bitmap;
private boolean isSidecar;
private String waterfallId;
public static class Builder {
private Bitmap bitmap;
private boolean isSidecar;
private String waterfallId;
public Builder(final Bitmap bitmap) {
this.bitmap = bitmap;
}
public Builder setBitmap(final Bitmap bitmap) {
this.bitmap = bitmap;
return this;
}
public Builder setIsSidecar(final boolean isSidecar) {
this.isSidecar = isSidecar;
return this;
}
public Builder setWaterfallId(final String waterfallId) {
this.waterfallId = waterfallId;
return this;
}
public ImageUploadOptions build() {
return new ImageUploadOptions(bitmap, isSidecar, waterfallId);
}
}
public static Builder builder(final Bitmap file) {
return new Builder(file);
}
private ImageUploadOptions(final Bitmap bitmap,
final boolean isSidecar,
final String waterfallId) {
this.bitmap = bitmap;
this.isSidecar = isSidecar;
this.waterfallId = waterfallId;
}
public Bitmap getBitmap() {
return bitmap;
}
public boolean isSidecar() {
return isSidecar;
}
public String getWaterfallId() {
return waterfallId;
}
}

View File

@ -31,6 +31,14 @@ public final class ProfileModel implements Serializable {
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);
}
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);
}
public boolean isPrivate() {
return isPrivate;
}
@ -71,17 +79,25 @@ public final class ProfileModel implements Serializable {
return hdProfilePic;
}
public long getPostCount() { return postCount; }
public long getPostCount() {
return postCount;
}
public long getFollowersCount() { return followersCount; }
public long getFollowersCount() {
return followersCount;
}
public long getFollowingCount() {
return followingCount;
}
public boolean getFollowing() { return following; }
public boolean getFollowing() {
return following;
}
public boolean getRestricted() { return restricted; }
public boolean getRestricted() {
return restricted;
}
public boolean getBlocked() {
return blocked;

View File

@ -1,5 +1,7 @@
package awais.instagrabber.models.direct_messages;
import androidx.core.util.ObjectsCompat;
import java.io.Serializable;
import awais.instagrabber.models.ProfileModel;
@ -123,7 +125,7 @@ public final class InboxThreadModel implements Serializable {
return canonical;
}
public boolean isHasOlder() {
public boolean hasOlder() {
return hasOlder;
}
@ -142,4 +144,18 @@ public final class InboxThreadModel implements Serializable {
public long getLastActivityAt() {
return lastActivityAt;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final InboxThreadModel that = (InboxThreadModel) o;
return ObjectsCompat.equals(threadId, that.threadId) &&
ObjectsCompat.equals(threadV2Id, that.threadV2Id);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(threadId, threadV2Id);
}
}

View File

@ -1,6 +1,16 @@
package awais.instagrabber.models.enums;
public enum UserInboxDirection {
OLDER,
NEWER,
OLDER("older"),
NEWER("newer");
private final String value;
UserInboxDirection(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
}

View File

@ -42,6 +42,7 @@ import androidx.fragment.app.FragmentManager;
import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.internal.StringUtil;
import java.io.BufferedReader;
import java.io.File;
@ -57,6 +58,8 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.crypto.Mac;
@ -101,6 +104,7 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
public final class Utils {
private static final String TAG = "Utils";
public static LogCollector logCollector;
public static SettingsHelper settingsHelper;
public static DataBox dataBox;
@ -121,29 +125,31 @@ public final class Utils {
public static void setupCookies(final String cookieRaw) {
final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore();
if (cookieRaw == "LOGOUT") {
cookieStore.removeAll();
if (cookieStore == null || isEmpty(cookieRaw)) {
return;
}
else if (cookieRaw != null && !isEmpty(cookieRaw)) {
try {
final URI uri1 = new URI("https://instagram.com");
final URI uri2 = new URI("https://instagram.com/");
final URI uri3 = new URI("https://i.instagram.com/");
for (final String cookie : cookieRaw.split(";")) {
final String[] strings = cookie.split("=", 2);
final HttpCookie httpCookie = new HttpCookie(strings[0].trim(), strings[1].trim());
httpCookie.setDomain("instagram.com");
httpCookie.setPath("/");
httpCookie.setVersion(0);
cookieStore.add(uri1, httpCookie);
cookieStore.add(uri2, httpCookie);
cookieStore.add(uri3, httpCookie);
}
} catch (final URISyntaxException e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.UTILS, "setupCookies");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (cookieRaw.equals("LOGOUT")) {
cookieStore.removeAll();
return;
}
try {
final URI uri1 = new URI("https://instagram.com");
final URI uri2 = new URI("https://instagram.com/");
final URI uri3 = new URI("https://i.instagram.com/");
for (final String cookie : cookieRaw.split(";")) {
final String[] strings = cookie.split("=", 2);
final HttpCookie httpCookie = new HttpCookie(strings[0].trim(), strings[1].trim());
httpCookie.setDomain("instagram.com");
httpCookie.setPath("/");
httpCookie.setVersion(0);
cookieStore.add(uri1, httpCookie);
cookieStore.add(uri2, httpCookie);
cookieStore.add(uri3, httpCookie);
}
} catch (final URISyntaxException e) {
if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.UTILS, "setupCookies");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
@ -1174,19 +1180,19 @@ public final class Utils {
public static String sign(final String message) {
try {
Mac hasher = Mac.getInstance("HmacSHA256");
final Mac hasher = Mac.getInstance("HmacSHA256");
hasher.init(new SecretKeySpec(Constants.SIGNATURE_KEY.getBytes(), "HmacSHA256"));
byte[] hash = hasher.doFinal(message.getBytes());
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
final StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
final String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return "ig_sig_key_version="+Constants.SIGNATURE_VERSION+"&signed_body=" + hexString.toString() + "." + message;
}
catch (Throwable e) {
Log.e("austin_debug", "sign: ", e);
catch (Exception e) {
Log.e(TAG, "Error signing", e);
return null;
}
}
@ -1355,4 +1361,33 @@ public final class Utils {
return null;
}
public static void setConnectionHeaders(final HttpURLConnection connection, final Map<String, String> headers) {
if (connection == null || headers == null || headers.isEmpty()) {
return;
}
for (Map.Entry<String, String> header : headers.entrySet()) {
connection.setRequestProperty(header.getKey(), header.getValue());
}
}
public static String getQueryString(final Map<String, String> queryParamsMap) {
if (queryParamsMap == null || queryParamsMap.isEmpty()) {
return "";
}
final Set<Map.Entry<String, String>> params = queryParamsMap.entrySet();
final StringBuilder builder = new StringBuilder();
for (final Map.Entry<String, String> param : params) {
if (isEmpty(param.getKey())) {
continue;
}
if (builder.length() != 0) {
builder.append("&");
}
builder.append(param.getKey());
builder.append("=");
builder.append(param.getValue() != null ? param.getValue() : "");
}
return builder.toString();
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,5v14L5,19L5,5h14m0,-2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14.14,11.86l-3,3.87L9,13.14 6,17h12l-3.86,-5.14z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/homeCoordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
tools:context=".activities.DirectMessagesActivity">
<fragment
android:id="@+id/direct_messages_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:navGraph="@navigation/direct_messages_nav_graph"
tools:ignore="FragmentTagUsage" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|exitUntilCollapsed|snap"
app:scrimAnimationDuration="10">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<TextView
android:id="@+id/toolbar_title"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/app_name" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/inbox_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

View File

@ -1,25 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activities.DirectMessages">
<include
android:id="@+id/toolbar"
android:layout_weight="1"
layout="@layout/layout_include_toolbar" />
android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_weight="8"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDirectMessages"
android:id="@+id/messageList"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -30,27 +23,46 @@
<EditText
android:id="@+id/commentText"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:paddingLeft="8dp"
android:layout_weight="1"
android:gravity="bottom"
android:hint="@string/dm_hint"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:maxLength="2200"
android:maxLines="10"
android:visibility="gone"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:scrollHorizontally="false" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
app:srcCompat="@drawable/ic_image_24" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/commentSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingRight="8dp"
android:visibility="gone"
app:srcCompat="@android:drawable/ic_menu_send" />
android:paddingBottom="4dp"
app:srcCompat="@drawable/ic_send_24" />
</LinearLayout>
</LinearLayout>

View File

@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
tools:viewBindingIgnore="true">
android:orientation="horizontal">
<awais.instagrabber.customviews.CircularImageView
android:id="@+id/ivProfilePic"

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/direct_messages_nav_graph"
app:startDestination="@id/directMessagesInboxFragment">
<fragment
android:id="@+id/directMessagesInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment"
android:label="DirectMessagesInboxFragment" >
<action
android:id="@+id/action_dMInboxFragment_to_dMThreadFragment"
app:destination="@id/directMessagesThreadFragment" />
</fragment>
<fragment
android:id="@+id/directMessagesThreadFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment"
android:label="DirectMessagesThreadFragment">
<argument
android:name="threadId"
app:argType="string" />
<argument
android:name="title"
app:argType="string" />
</fragment>
</navigation>

View File

@ -181,7 +181,7 @@
<string name="comment_viewer_like_comment">Like comment</string>
<string name="comment_viewer_unlike_comment">Unlike comment</string>
<string name="comment_viewer_delete_comment">Delete comment</string>
<string name="comment_send_empty_comment">No empty comments, dawg!</string>
<string name="comment_send_empty_comment">No empty comments!</string>
<string name="comment_view_mention_user_search">Do you want to search the username?</string>
<string name="comment_view_mention_hash_search">Do you want to search the hashtag?</string>
<string name="comment_view_mention_location_search">Do you want to search the location?</string>
@ -213,4 +213,5 @@
<string name="use_amoled_dark_theme">Use AMOLED mode for Dark theme</string>
<string name="action_notif">Activity</string>
<string name="license" translatable="false">InstaGrabber\nCopyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR \'\'AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nSee project page for third-party attributions.</string>
<string name="select_picture">Select Picture</string>
</resources>

View File

@ -6,6 +6,9 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.0.1'
def nav_version = "2.3.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}