1
0
mirror of https://github.com/KokaKiwi/BarInsta synced 2024-11-22 14:47:29 +00:00

update direct messages

This commit sets up the base for future enhancements in direct messages. The changes are:

1. Introducing fragments and navigation using androidx.navigation.fragment.NavHostFragment.

2. Using ListAdapter and LiveData for the RecyclerViews.

3. Fixes a bug where a single direct message was lost when querying older messages.
This commit is contained in:
Ammar Githam 2020-08-17 22:49:51 +09:00
parent 650062646d
commit 30695ae02f
26 changed files with 1164 additions and 955 deletions

View File

@ -1,4 +1,5 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
android { android {
compileSdkVersion 29 compileSdkVersion 29
@ -37,10 +38,14 @@ android {
dependencies { dependencies {
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true } 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('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { 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' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation('org.jsoup:jsoup:1.13.1') { transitive true } implementation('org.jsoup:jsoup:1.13.1') { transitive true }

View File

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

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.DirectMessagesThreadFragmentArgs;
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 = DirectMessagesThreadFragmentArgs.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

@ -29,7 +29,6 @@ import java.util.Stack;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.MainHelper; import awais.instagrabber.MainHelper;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.directmessages.DirectMessages;
import awais.instagrabber.adapters.HighlightsAdapter; import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.SuggestionsAdapter; import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.SuggestionsFetcher; import awais.instagrabber.asyncs.SuggestionsFetcher;
@ -86,7 +85,8 @@ public final class Main extends BaseLanguageActivity {
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle()) .putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result) .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); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
} }
@ -143,7 +143,7 @@ public final class Main extends BaseLanguageActivity {
if (mainHelper != null && !Utils.isEmpty(result)) { if (mainHelper != null && !Utils.isEmpty(result)) {
closeAnyOpenDrawer(); closeAnyOpenDrawer();
addToStack(); addToStack();
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@"+result); userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@" + result);
mainHelper.onRefresh(); mainHelper.onRefresh();
} }
}; };
@ -154,7 +154,8 @@ public final class Main extends BaseLanguageActivity {
if (!Utils.isEmpty(username)) { if (!Utils.isEmpty(username)) {
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
userQuery = username; 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 // adds cookies to database for quick access
cookieModel = Utils.dataBox.getCookie(uid); cookieModel = Utils.dataBox.getCookie(uid);
@ -168,7 +169,7 @@ public final class Main extends BaseLanguageActivity {
final String username = cookieModel.getUsername(); final String username = cookieModel.getUsername();
if (username != null) { if (username != null) {
found = true; found = true;
fetchListener.onResult("@"+username); fetchListener.onResult("@" + username);
} }
} }
@ -199,8 +200,8 @@ public final class Main extends BaseLanguageActivity {
intent = new Intent(this, ProfilePicViewer.class).putExtra( intent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)), ((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel))); ((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
} } else
else intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", "")) intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels) .putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null)); .putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(intent); startActivity(intent);
@ -241,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.privatePage2.setText(mainHelper.isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage.setVisibility(View.VISIBLE); 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()); mainHelper.onIntent(getIntent());
} }
@ -301,7 +303,7 @@ public final class Main extends BaseLanguageActivity {
if (item == downloadAction) if (item == downloadAction)
downloadSelectedItems(); downloadSelectedItems();
else if (item == dmsAction) else if (item == dmsAction)
startActivity(new Intent(this, DirectMessages.class)); startActivity(new Intent(this, DirectMessagesActivity.class));
else if (item == notifAction) else if (item == notifAction)
startActivity(new Intent(this, NotificationsViewer.class)); startActivity(new Intent(this, NotificationsViewer.class));
else if (item == settingsAction) else if (item == settingsAction)
@ -336,7 +338,7 @@ public final class Main extends BaseLanguageActivity {
searchView.setQueryHint(getResources().getString(R.string.action_search)); searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setSuggestionsAdapter(suggestionAdapter); searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setOnSearchClickListener(v -> { 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_about).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false); menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_dms).setVisible(false); menu.findItem(R.id.action_dms).setVisible(false);
@ -400,7 +402,10 @@ public final class Main extends BaseLanguageActivity {
private void cancelSuggestionsAsync() { private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null) if (prevSuggestionAsync != null)
try { prevSuggestionAsync.cancel(true); } catch (final Exception ignored) { } try {
prevSuggestionAsync.cancel(true);
} catch (final Exception ignored) {
}
} }
@Override @Override
@ -411,7 +416,7 @@ public final class Main extends BaseLanguageActivity {
closeAnyOpenDrawer(); closeAnyOpenDrawer();
addToStack(); addToStack();
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@"+query); userQuery = (query.contains("@") || query.contains("#")) ? query : ("@" + query);
searchAction.collapseActionView(); searchAction.collapseActionView();
searchView.setIconified(true); searchView.setIconified(true);
searchView.setIconified(true); searchView.setIconified(true);
@ -468,8 +473,7 @@ public final class Main extends BaseLanguageActivity {
mainHelper.onRefresh(); mainHelper.onRefresh();
return; return;
} }
} } else {
else {
finish(); finish();
} }
} }

View File

@ -1,119 +0,0 @@
package awais.instagrabber.activities.directmessages;
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.activities.BaseLanguageActivity;
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, DirectMessageThread.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,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; 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.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.NonNull;
import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil;
import androidx.core.text.HtmlCompat; import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide; import java.util.List;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList; import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageViewHolder;
import awais.instagrabber.databinding.ItemMessageItemBinding;
import awais.instagrabber.R;
import awais.instagrabber.activities.Main;
import awais.instagrabber.adapters.viewholder.directmessages.TextMessageViewHolder;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel; 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; public final class MessageItemsAdapter extends ListAdapter<DirectItemModel, DirectMessageViewHolder> {
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkModel; private final List<ProfileModel> users;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel; private final List<ProfileModel> leftUsers;
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;
private final View.OnClickListener onClickListener; private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener; private final MentionClickListener mentionClickListener;
private final View.OnClickListener openProfileClickListener = v -> {
final Object tag = v.getTag(); private static final DiffUtil.ItemCallback<DirectItemModel> diffCallback = new DiffUtil.ItemCallback<DirectItemModel>() {
if (tag instanceof ProfileModel) { @Override
// todo do profile stuff public boolean areItemsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
final ProfileModel profileModel = (ProfileModel) tag; return oldItem.getItemId().equals(newItem.getItemId());
Log.d("AWAISKING_APP", "--> " + profileModel); }
@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) { public MessageItemsAdapter(final List<ProfileModel> users,
// todo pause / resume final List<ProfileModel> leftUsers,
} else { final View.OnClickListener onClickListener,
// 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,
final MentionClickListener mentionClickListener) { final MentionClickListener mentionClickListener) {
super(diffCallback);
this.users = users; this.users = users;
this.leftusers = leftusers; this.leftUsers = leftUsers;
this.directItemModels = directItemModels;
this.onClickListener = onClickListener; this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener; this.mentionClickListener = mentionClickListener;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
} }
@NonNull @NonNull
@Override @Override
public TextMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) { public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (context == null) context = parent.getContext(); final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
if (strDmYou == null) strDmYou = context.getString(R.string.direct_messages_you); final ItemMessageItemBinding binding = ItemMessageItemBinding.inflate(layoutInflater, parent, false);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); return new DirectMessageViewHolder(binding, users, leftUsers);
return new TextMessageViewHolder(layoutInflater.inflate(R.layout.item_message_item, parent, false),
onClickListener, mentionClickListener);
} }
@Override @Override
public void onBindViewHolder(@NonNull final TextMessageViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) {
final DirectItemModel directItemModel = directItemModels.get(position); final DirectItemModel directItemModel = getItem(position);
holder.itemView.setTag(directItemModel); holder.bind(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;
}
}
} }
@Override @Override
public int getItemViewType(final int position) { public int getItemViewType(final int position) {
return directItemModels.get(position).getItemType().ordinal(); return getItem(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;
} }
} }

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

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

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 = DirectMessagesInboxFragmentDirections.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

@ -1,17 +1,26 @@
package awais.instagrabber.activities.directmessages; package awais.instagrabber.fragments.directmessages;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -25,24 +34,21 @@ import java.net.HttpURLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.BaseLanguageActivity;
import awais.instagrabber.activities.PostViewer; import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.ProfileViewer; import awais.instagrabber.activities.ProfileViewer;
import awais.instagrabber.activities.StoryViewer; import awais.instagrabber.activities.StoryViewer;
import awais.instagrabber.adapters.MessageItemsAdapter; import awais.instagrabber.adapters.MessageItemsAdapter;
import awais.instagrabber.asyncs.ImageUploader; import awais.instagrabber.asyncs.ImageUploader;
import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster; import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.BroadcastOptions;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.ImageBroadcastOptions;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.OnBroadcastCompleteListener;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.TextBroadcastOptions;
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding; import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ImageUploadOptions; import awais.instagrabber.models.ImageUploadOptions;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
@ -55,72 +61,34 @@ import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import static android.view.View.VISIBLE; public class DirectMessageThreadFragment extends Fragment {
private static final String TAG = "DirectMessagesThreadFmt";
public final class DirectMessageThread extends BaseLanguageActivity {
private static final String TAG = "DirectMessageThread";
private static final int PICK_IMAGE = 100; private static final int PICK_IMAGE = 100;
private DirectItemModel directItemModel; private FragmentActivity fragmentActivity;
private String threadId; private String threadId;
private String endCursor; private String cursor;
private ActivityDmsBinding dmsBinding; private FragmentDirectMessagesThreadBinding binding;
private MessageItemsAdapter messageItemsAdapter; private DirectItemModelListViewModel listViewModel;
private RecyclerView messageList;
private boolean hasSentSomething;
private boolean hasOlder = true;
private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel(); private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel();
private final ArrayList<ProfileModel> users = new ArrayList<>(); private final List<ProfileModel> users = new ArrayList<>();
private final ArrayList<ProfileModel> leftUsers = new ArrayList<>(); private final List<ProfileModel> leftUsers = new ArrayList<>();
private final ArrayList<DirectItemModel> directItemModels = new ArrayList<>();
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(DirectMessageThread.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(TextUtils.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 final View.OnClickListener clickListener = v -> { private final View.OnClickListener clickListener = v -> {
if (v == dmsBinding.commentSend) { if (v == binding.commentSend) {
final String text = dmsBinding.commentText.getText().toString(); final String text = binding.commentText.getText().toString();
if (Utils.isEmpty(text)) { if (Utils.isEmpty(text)) {
Toast.makeText(getApplicationContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show(); Toast.makeText(requireContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return; return;
} }
sendText(text); sendText(text);
return; return;
} }
if (v == dmsBinding.image) { if (v == binding.image) {
final Intent intent = new Intent(); final Intent intent = new Intent();
intent.setType("image/*"); intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT); intent.setAction(Intent.ACTION_GET_CONTENT);
@ -128,45 +96,95 @@ public final class DirectMessageThread extends BaseLanguageActivity {
} }
}; };
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override @Override
protected void onCreate(@Nullable final Bundle savedInstanceState) { public void doBefore() {
super.onCreate(savedInstanceState); binding.swipeRefreshLayout.setRefreshing(true);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater()); }
setContentView(dmsBinding.getRoot());
final InboxThreadModel threadModel; @Override
final Intent intent = getIntent(); public void onResult(final InboxThreadModel result) {
if (intent == null || !intent.hasExtra(Constants.EXTRAS_THREAD_MODEL) || if (result == null && ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor) || Utils.isEmpty(cursor)))
(threadModel = (InboxThreadModel) intent.getSerializableExtra(Constants.EXTRAS_THREAD_MODEL)) == null) { Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Utils.errorFinish(this);
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 = DirectMessagesThreadFragmentArgs.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; return;
} }
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, cursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
dmsBinding.swipeRefreshLayout.setEnabled(false);
dmsBinding.commentText.setVisibility(VISIBLE);
dmsBinding.commentSend.setVisibility(VISIBLE);
dmsBinding.image.setVisibility(VISIBLE);
dmsBinding.commentSend.setOnClickListener(clickListener);
dmsBinding.image.setOnClickListener(clickListener);
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 -> { final View.OnClickListener onClickListener = v -> {
Object tag = v.getTag(); Object tag = v.getTag();
if (tag instanceof DirectItemModel) { if (tag instanceof DirectItemModel) {
directItemModel = (DirectItemModel) tag; final DirectItemModel directItemModel = (DirectItemModel) tag;
final DirectItemType itemType = directItemModel.getItemType(); final DirectItemType itemType = directItemModel.getItemType();
switch (itemType) { switch (itemType) {
case MEDIA_SHARE: case MEDIA_SHARE:
startActivity(new Intent(this, PostViewer.class) startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(directItemModel.getMediaModel().getCode(), false))); .putExtra(Constants.EXTRAS_POST, new PostModel(directItemModel.getMediaModel().getCode(), false)));
break; break;
case LINK: case LINK:
@ -182,9 +200,7 @@ public final class DirectMessageThread extends BaseLanguageActivity {
case RAVEN_MEDIA: case RAVEN_MEDIA:
case MEDIA: case MEDIA:
final ProfileModel user = getUser(directItemModel.getUserId()); final ProfileModel user = getUser(directItemModel.getUserId());
if (user != null) { Utils.dmDownload(requireContext(), user.getUsername(), DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia()));
Utils.dmDownload(this, 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(); Toast.makeText(v.getContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
break; break;
case STORY_SHARE: case STORY_SHARE:
@ -200,7 +216,7 @@ public final class DirectMessageThread extends BaseLanguageActivity {
); );
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl()); sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm}; StoryModel[] sms = {sm};
startActivity(new Intent(this, StoryViewer.class) startActivity(new Intent(requireContext(), StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName()) .putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
.putExtra(Constants.EXTRAS_STORIES, sms) .putExtra(Constants.EXTRAS_STORIES, sms)
); );
@ -216,15 +232,18 @@ public final class DirectMessageThread extends BaseLanguageActivity {
Log.d("austin_debug", "unsupported type " + itemType); Log.d("austin_debug", "unsupported type " + itemType);
} }
} }
}, (view, text, isHashtag) -> searchUsername(text)); };
final MentionClickListener mentionClickListener = (view, text, isHashtag) -> searchUsername(text);
dmsBinding.rvDirectMessages.setAdapter(messageItemsAdapter); final MessageItemsAdapter adapter = new MessageItemsAdapter(users, leftUsers, onClickListener, mentionClickListener);
messageList.setAdapter(adapter);
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 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 @Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) { if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) { if (data == null || data.getData() == null) {
@ -236,61 +255,46 @@ public final class DirectMessageThread extends BaseLanguageActivity {
} }
} }
@Nullable @Override
private ProfileModel getUser(final long userId) { public void onDestroy() {
if (users != null) { super.onDestroy();
ProfileModel result = myProfileHolder; listViewModel.getList().postValue(Collections.emptyList());
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 void sendText(final String text) { private void sendText(final String text) {
final TextBroadcastOptions options; final DirectThreadBroadcaster.TextBroadcastOptions options;
try { try {
options = new TextBroadcastOptions(text); options = new DirectThreadBroadcaster.TextBroadcastOptions(text);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e); Log.e(TAG, "Error", e);
return; return;
} }
broadcast(options, result -> { broadcast(options, result -> {
if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) { if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return; return;
} }
dmsBinding.commentText.setText(""); binding.commentText.setText("");
dmsBinding.commentText.clearFocus(); // binding.commentText.clearFocus();
directItemModels.clear(); hasSentSomething = true;
messageItemsAdapter.notifyDataSetChanged(); new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new UserInboxFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}); });
} }
private void sendImage(final Uri imageUri) { private void sendImage(final Uri imageUri) {
try { try {
final ParcelFileDescriptor fileDescriptor = getContentResolver().openFileDescriptor(imageUri, "r"); final ParcelFileDescriptor fileDescriptor = requireContext().getContentResolver().openFileDescriptor(imageUri, "r");
if (fileDescriptor == null) { if (fileDescriptor == null) {
Log.e(TAG, "fileDescriptor is null!"); Log.e(TAG, "fileDescriptor is null!");
return; return;
} }
final long contentLength = fileDescriptor.getStatSize(); final long contentLength = fileDescriptor.getStatSize();
final InputStream inputStream = getContentResolver().openInputStream(imageUri); final InputStream inputStream = requireContext().getContentResolver().openInputStream(imageUri);
// Upload Image // Upload Image
final ImageUploader imageUploader = new ImageUploader(); final ImageUploader imageUploader = new ImageUploader();
imageUploader.setOnTaskCompleteListener(response -> { imageUploader.setOnTaskCompleteListener(response -> {
if (response == null || response.getResponseCode() != HttpURLConnection.HTTP_OK) { if (response == null || response.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (response != null && response.getResponse() != null) { if (response != null && response.getResponse() != null) {
Log.e(TAG, response.getResponse().toString()); Log.e(TAG, response.getResponse().toString());
} }
@ -300,12 +304,9 @@ public final class DirectMessageThread extends BaseLanguageActivity {
try { try {
final String uploadId = responseJson.getString("upload_id"); final String uploadId = responseJson.getString("upload_id");
// Broadcast // Broadcast
final ImageBroadcastOptions options = new ImageBroadcastOptions(true, uploadId); final DirectThreadBroadcaster.ImageBroadcastOptions options = new DirectThreadBroadcaster.ImageBroadcastOptions(true, uploadId);
broadcast(options, onBroadcastCompleteListener -> { hasSentSomething = true;
directItemModels.clear(); broadcast(options, onBroadcastCompleteListener -> new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR));
messageItemsAdapter.notifyDataSetChanged();
new UserInboxFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "Error parsing json response", e); Log.e(TAG, "Error parsing json response", e);
} }
@ -317,9 +318,36 @@ public final class DirectMessageThread extends BaseLanguageActivity {
} }
} }
private void broadcast(final BroadcastOptions broadcastOptions, final OnBroadcastCompleteListener listener) { private void broadcast(final DirectThreadBroadcaster.BroadcastOptions broadcastOptions, final DirectThreadBroadcaster.OnBroadcastCompleteListener listener) {
final DirectThreadBroadcaster broadcaster = new DirectThreadBroadcaster(threadId); final DirectThreadBroadcaster broadcaster = new DirectThreadBroadcaster(threadId);
broadcaster.setOnTaskCompleteListener(listener); broadcaster.setOnTaskCompleteListener(listener);
broadcaster.execute(broadcastOptions); 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

@ -35,6 +35,10 @@ public final class ProfileModel implements Serializable {
return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false); return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
} }
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() { public boolean isPrivate() {
return isPrivate; return isPrivate;
} }

View File

@ -1,5 +1,7 @@
package awais.instagrabber.models.direct_messages; package awais.instagrabber.models.direct_messages;
import androidx.core.util.ObjectsCompat;
import java.io.Serializable; import java.io.Serializable;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
@ -123,7 +125,7 @@ public final class InboxThreadModel implements Serializable {
return canonical; return canonical;
} }
public boolean isHasOlder() { public boolean hasOlder() {
return hasOlder; return hasOlder;
} }
@ -142,4 +144,18 @@ public final class InboxThreadModel implements Serializable {
public long getLastActivityAt() { public long getLastActivityAt() {
return lastActivityAt; 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; package awais.instagrabber.models.enums;
public enum UserInboxDirection { public enum UserInboxDirection {
OLDER, OLDER("older"),
NEWER, 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.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.jsoup.internal.StringUtil;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -58,6 +59,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.crypto.Mac; import javax.crypto.Mac;
@ -1371,4 +1373,24 @@ public final class Utils {
connection.setRequestProperty(header.getKey(), header.getValue()); 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,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,24 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical">
tools:context=".activities.directmessages.DirectMessages">
<include
android:id="@+id/toolbar"
layout="@layout/layout_include_toolbar" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="8"> android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDirectMessages" android:id="@+id/messageList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -31,7 +25,7 @@
android:id="@+id/commentText" android:id="@+id/commentText"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2" android:layout_weight="1"
android:gravity="bottom" android:gravity="bottom"
android:hint="@string/dm_hint" android:hint="@string/dm_hint"
android:importantForAutofill="no" android:importantForAutofill="no"
@ -42,8 +36,7 @@
android:paddingLeft="8dp" android:paddingLeft="8dp"
android:paddingEnd="4dp" android:paddingEnd="4dp"
android:paddingRight="4dp" android:paddingRight="4dp"
android:scrollHorizontally="false" android:scrollHorizontally="false" />
android:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image" android:id="@+id/image"
@ -54,7 +47,6 @@
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:padding="4dp" android:padding="4dp"
android:visibility="visible"
app:srcCompat="@drawable/ic_image_24" /> app:srcCompat="@drawable/ic_image_24" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
@ -71,7 +63,6 @@
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:paddingBottom="4dp" android:paddingBottom="4dp"
android:visibility="visible"
app:srcCompat="@drawable/ic_send_24" /> app:srcCompat="@drawable/ic_send_24" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal">
tools:viewBindingIgnore="true">
<awais.instagrabber.customviews.CircularImageView <awais.instagrabber.customviews.CircularImageView
android:id="@+id/ivProfilePic" 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_like_comment">Like comment</string>
<string name="comment_viewer_unlike_comment">Unlike comment</string> <string name="comment_viewer_unlike_comment">Unlike comment</string>
<string name="comment_viewer_delete_comment">Delete 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_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_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> <string name="comment_view_mention_location_search">Do you want to search the location?</string>

View File

@ -6,6 +6,9 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' 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"
} }
} }