like dm messages & some polishing

This commit is contained in:
Austin Huang 2020-08-18 16:47:43 -04:00
parent e5c0b376f6
commit 072cd63776
No known key found for this signature in database
GPG Key ID: 84C23AA04587A91F
9 changed files with 201 additions and 48 deletions

View File

@ -50,7 +50,7 @@ public final class MessageItemsAdapter extends ListAdapter<DirectItemModel, Dire
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemMessageItemBinding binding = ItemMessageItemBinding.inflate(layoutInflater, parent, false);
return new DirectMessageViewHolder(binding, users, leftUsers);
return new DirectMessageViewHolder(binding, users, leftUsers, onClickListener, mentionClickListener);
}
@Override

View File

@ -7,6 +7,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
@ -20,6 +21,7 @@ import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemMessageItemBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.enums.DirectItemType;
@ -44,6 +46,8 @@ public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
private final String strDmYou;
private DirectItemModel.DirectItemVoiceMediaModel prevVoiceModel;
private ImageView prevPlayIcon;
private View.OnClickListener onClickListener;
private MentionClickListener mentionClickListener;
private final View.OnClickListener voicePlayClickListener = v -> {
final Object tag = v.getTag();
@ -73,24 +77,18 @@ public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
}
};
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) {
final List<ProfileModel> leftUsers,
final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) {
super(binding.getRoot());
this.binding = binding;
this.users = users;
this.leftUsers = leftUsers;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener;
strDmYou = binding.getRoot().getContext().getString(R.string.direct_messages_you);
}
@ -99,7 +97,7 @@ public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
return;
}
final Context context = itemView.getContext();
itemView.setTag(directItemModel);
//itemView.setTag(directItemModel);
final DirectItemType itemType = directItemModel.getItemType();
final ProfileModel user = getUser(directItemModel.getUserId());
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
@ -137,6 +135,15 @@ public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
binding.tvUsername.setText(text);
binding.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
binding.ivProfilePic.setTag(user);
binding.ivProfilePic.setOnClickListener(onClickListener);
binding.tvMessage.setMentionClickListener(mentionClickListener);
binding.messageCard.setTag(directItemModel);
LinearLayout parent = (LinearLayout) binding.messageCard.getParent();
parent.setTag(directItemModel);
binding.messageCard.setOnClickListener(onClickListener);
binding.liked.setVisibility(directItemModel.isLiked() ? View.VISIBLE : View.GONE);
final RequestManager glideRequestManager = Glide.with(itemView);
@ -338,7 +345,7 @@ public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
Glide.with(binding.profileInfo).load(profileModel.getSdProfilePic())
.into(binding.profileInfo);
btnOpenProfile.setTag(profileModel);
btnOpenProfile.setOnClickListener(openProfileClickListener);
btnOpenProfile.setOnClickListener(onClickListener);
binding.tvFullName.setText(profileModel.getName());
binding.profileInfoText.setText(profileModel.getUsername());

View File

@ -49,7 +49,7 @@ public class DirectThreadBroadcaster extends AsyncTask<DirectThreadBroadcaster.B
form.put("client_context", cc);
form.put("mutation_token", cc);
form.putAll(broadcastOptions.getFormMap());
form.put("thread_ids", String.format("[%s]", threadId));
form.put("thread_id", threadId);
form.put("action", "send_item");
final String message = new JSONObject(form).toString();
final String content = Utils.sign(message);
@ -125,6 +125,7 @@ public class DirectThreadBroadcaster extends AsyncTask<DirectThreadBroadcaster.B
public enum ItemType {
TEXT("text"),
REACTION("reaction"),
IMAGE("configure_photo");
private final String value;
@ -168,6 +169,26 @@ public class DirectThreadBroadcaster extends AsyncTask<DirectThreadBroadcaster.B
}
}
public static class ReactionBroadcastOptions extends BroadcastOptions {
private final String itemId;
private final boolean delete;
public ReactionBroadcastOptions(String itemId, boolean delete) {
super(ItemType.REACTION);
this.itemId = itemId;
this.delete = delete;
}
@Override
Map<String, String> getFormMap() {
final Map<String, String> form = new HashMap<>();
form.put("item_id", itemId);
form.put("reaction_status", delete ? "deleted" : "created");
form.put("reaction_type", "like");
return form;
}
}
public static class ImageBroadcastOptions extends BroadcastOptions {
final boolean allowFullAspectRatio;
final String uploadId;

View File

@ -1,6 +1,7 @@
package awais.instagrabber.fragments.directmessages;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -12,11 +13,13 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.MutableLiveData;
@ -71,8 +74,11 @@ public class DirectMessageThreadFragment extends Fragment {
private FragmentActivity fragmentActivity;
private String threadId;
private String cursor;
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
private final String myId = Utils.getUserIdFromCookie(cookie);
private FragmentDirectMessagesThreadBinding binding;
private DirectItemModelListViewModel listViewModel;
private DirectItemModel directItemModel;
private RecyclerView messageList;
private boolean hasSentSomething;
private boolean hasOlder = true;
@ -80,6 +86,7 @@ public class DirectMessageThreadFragment extends Fragment {
private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel();
private final List<ProfileModel> users = new ArrayList<>();
private final List<ProfileModel> leftUsers = new ArrayList<>();
private ArrayAdapter<String> dialogAdapter;
private final View.OnClickListener clickListener = v -> {
if (v == binding.commentSend) {
@ -88,7 +95,7 @@ public class DirectMessageThreadFragment extends Fragment {
Toast.makeText(requireContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return;
}
sendText(text);
sendText(text, null, false);
return;
}
if (v == binding.image) {
@ -180,10 +187,8 @@ public class DirectMessageThreadFragment extends Fragment {
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, cursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
}));
final View.OnClickListener onClickListener = v -> {
Object tag = v.getTag();
if (tag instanceof DirectItemModel) {
final DirectItemModel directItemModel = (DirectItemModel) tag;
final DialogInterface.OnClickListener onDialogListener = (d,w) -> {
if (w == 0) {
final DirectItemType itemType = directItemModel.getItemType();
switch (itemType) {
case MEDIA_SHARE:
@ -197,14 +202,14 @@ public class DirectMessageThreadFragment extends Fragment {
break;
case TEXT:
case REEL_SHARE:
Utils.copyText(v.getContext(), directItemModel.getText());
Toast.makeText(v.getContext(), R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
Utils.copyText(requireContext(), directItemModel.getText());
Toast.makeText(requireContext(), R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
break;
case RAVEN_MEDIA:
case MEDIA:
final ProfileModel user = getUser(directItemModel.getUserId());
Utils.dmDownload(requireContext(), user.getUsername(), DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia()));
Toast.makeText(v.getContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
Toast.makeText(requireContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
@ -235,6 +240,69 @@ public class DirectMessageThreadFragment extends Fragment {
Log.d("austin_debug", "unsupported type " + itemType);
}
}
else if (w == 1) {
sendText(null, directItemModel.getItemId(), directItemModel.isLiked());
}
else if (w == 2) {
if (String.valueOf(directItemModel.getUserId()).equals(myId)) {
// unsend: https://www.instagram.com/direct_v2/web/threads/340282366841710300949128288687654467119/items/29473546990090204551245070881259520/delete/
}
else searchUsername(getUser(directItemModel.getUserId()).getUsername());
}
};
final View.OnClickListener onClickListener = v -> {
Object tag = v.getTag();
if (tag instanceof ProfileModel) {
searchUsername(((ProfileModel) tag).getUsername());
}
else if (tag instanceof DirectItemModel) {
directItemModel = (DirectItemModel) tag;
final DirectItemType itemType = directItemModel.getItemType();
int firstOption = R.string.dms_inbox_raven_message_unknown;
String[] dialogList;
switch (itemType) {
case MEDIA_SHARE:
firstOption = R.string.view_post;
break;
case LINK:
firstOption = R.string.dms_inbox_open_link;
break;
case TEXT:
case REEL_SHARE:
firstOption = R.string.dms_inbox_copy_text;
break;
case RAVEN_MEDIA:
case MEDIA:
firstOption = R.string.dms_inbox_download;
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
firstOption = R.string.show_stories;
} else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
firstOption = R.string.open_profile;
}
break;
case PLACEHOLDER:
if (directItemModel.getText().toString().contains("@"))
firstOption = R.string.open_profile;
break;
}
dialogList = new String[]{
getString(firstOption),
getString(directItemModel.isLiked() ? R.string.dms_inbox_unlike : R.string.dms_inbox_like),
getString(String.valueOf(directItemModel.getUserId()).equals(myId) ? R.string.dms_inbox_unsend : R.string.dms_inbox_author)
};
dialogAdapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_list_item_1, dialogList);
new AlertDialog.Builder(requireContext())
//.setTitle(title)
.setAdapter(dialogAdapter, onDialogListener)
.setNeutralButton(R.string.cancel, null)
.show();
}
};
final MentionClickListener mentionClickListener = (view, text, isHashtag) -> searchUsername(text);
final MessageItemsAdapter adapter = new MessageItemsAdapter(users, leftUsers, onClickListener, mentionClickListener);
@ -264,29 +332,42 @@ public class DirectMessageThreadFragment extends Fragment {
listViewModel.getList().postValue(Collections.emptyList());
}
private void sendText(final String text) {
final DirectThreadBroadcaster.TextBroadcastOptions options;
try {
options = new DirectThreadBroadcaster.TextBroadcastOptions(text);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e);
return;
private void sendText(final String text, final String itemId, final boolean delete) {
DirectThreadBroadcaster.TextBroadcastOptions textOptions = null;
DirectThreadBroadcaster.ReactionBroadcastOptions reactionOptions = null;
if (text != null) {
try {
textOptions = new DirectThreadBroadcaster.TextBroadcastOptions(text);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e);
return;
}
}
broadcast(options, result -> {
else {
reactionOptions = new DirectThreadBroadcaster.ReactionBroadcastOptions(itemId, delete);
}
broadcast(text != null ? textOptions : reactionOptions, result -> {
if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
binding.commentText.setText("");
// binding.commentText.clearFocus();
if (text != null) {
binding.commentText.setText("");
}
else {
LinearLayout dim = (LinearLayout) binding.messageList.findViewWithTag(directItemModel);
if (dim.findViewById(R.id.liked) != null) dim.findViewById(R.id.liked).setVisibility(deleted ? View.GONE : View.VISIBLE);
directItemModel.setLiked();
}
hasSentSomething = true;
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
}
private void sendImage(final Uri imageUri) {
try(InputStream inputStream = requireContext().getContentResolver().openInputStream(imageUri)) {
try (InputStream inputStream = requireContext().getContentResolver().openInputStream(imageUri)) {
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
Toast.makeText(requireContext(), R.string.uploading, Toast.LENGTH_SHORT).show();
// Upload Image
final ImageUploader imageUploader = new ImageUploader();
imageUploader.setOnTaskCompleteListener(response -> {
@ -312,6 +393,7 @@ public class DirectMessageThreadFragment extends Fragment {
imageUploader.execute(options);
}
catch (IOException e) {
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error opening file", e);
}
}

View File

@ -3,6 +3,7 @@ package awais.instagrabber.models.direct_messages;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import awais.instagrabber.models.ProfileModel;
@ -12,10 +13,14 @@ import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Constants.COOKIE;
public final class DirectItemModel implements Serializable, Comparable<DirectItemModel> {
private final long userId, timestamp;
private final DirectItemType itemType;
private final String itemId;
private String[] likes;
private boolean liked;
private final CharSequence text;
private final DirectItemLinkModel linkModel;
private final DirectItemMediaModel mediaModel;
@ -27,9 +32,11 @@ public final class DirectItemModel implements Serializable, Comparable<DirectIte
private final DirectItemAnimatedMediaModel animatedMediaModel;
private final DirectItemVideoCallEventModel videoCallEventModel;
public DirectItemModel(final long userId, final long timestamp, final String itemId, final DirectItemType itemType,
final CharSequence text, final DirectItemLinkModel linkModel, final ProfileModel profileModel,
final DirectItemReelShareModel reelShare, final DirectItemMediaModel mediaModel,
private final String myId = Utils.getUserIdFromCookie(Utils.settingsHelper.getString(COOKIE));
public DirectItemModel(final long userId, final long timestamp, final String itemId, final String[] likes,
final DirectItemType itemType, final CharSequence text, final DirectItemLinkModel linkModel,
final ProfileModel profileModel, final DirectItemReelShareModel reelShare, final DirectItemMediaModel mediaModel,
final DirectItemActionLogModel actionLogModel, final DirectItemVoiceMediaModel voiceMediaModel,
final DirectItemRavenMediaModel ravenMediaModel, final DirectItemVideoCallEventModel videoCallEventModel,
final DirectItemAnimatedMediaModel animatedMediaModel) {
@ -37,6 +44,8 @@ public final class DirectItemModel implements Serializable, Comparable<DirectIte
this.timestamp = timestamp;
this.itemType = itemType;
this.itemId = itemId;
this.likes = likes;
this.liked = likes != null ? Arrays.asList(likes).contains(myId) : false;
this.text = text;
this.linkModel = linkModel;
this.profileModel = profileModel;
@ -65,6 +74,14 @@ public final class DirectItemModel implements Serializable, Comparable<DirectIte
return userId;
}
public boolean isLiked() {
return liked;
}
public void setLiked() {
this.liked = !liked;
}
public long getTimestamp() {
return timestamp;
}

View File

@ -732,10 +732,20 @@ public final class Utils {
break;
}
String[] liked = null;
if (!itemObject.isNull("reactions") && !itemObject.getJSONObject("reactions").isNull("likes")) {
JSONArray rawLiked = itemObject.getJSONObject("reactions").getJSONArray("likes");
liked = new String[rawLiked.length()];
for (int l = 0; l < rawLiked.length(); ++l) {
liked[l] = String.valueOf(rawLiked.getJSONObject(l).getLong("sender_id"));
}
}
itemModels.add(new DirectItemModel(
itemObject.getLong("user_id"),
itemObject.getLong("timestamp"),
itemObject.getString("item_id"),
liked,
itemType,
text,
linkModel,

View File

@ -21,6 +21,17 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
app:srcCompat="@drawable/ic_image_24" />
<EditText
android:id="@+id/commentText"
android:layout_width="0dp"
@ -38,17 +49,6 @@
android:paddingRight="4dp"
android:scrollHorizontally="false" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
app:srcCompat="@drawable/ic_image_24" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/commentSend"
android:layout_width="wrap_content"

View File

@ -30,6 +30,7 @@
android:textColor="?android:textColorPrimary" />
<androidx.cardview.widget.CardView
android:id="@+id/messageCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?android:selectableItemBackground"
@ -251,5 +252,12 @@
</FrameLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/liked"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
app:srcCompat="@drawable/ic_like"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

View File

@ -150,6 +150,13 @@
<string name="direct_messages_reacted_story">Reacted on a story</string>
<string name="direct_messages_mention_story">Mentioned in a story</string>
<string name="dms_inbox_raven_message_unknown"><i>Unsupported message type</i></string>
<string name="dms_inbox_open_link">Open link</string>
<string name="dms_inbox_copy_text">Copy text</string>
<string name="dms_inbox_download">Download attachment</string>
<string name="dms_inbox_like">Like message</string>
<string name="dms_inbox_unlike">Unlike message</string>
<string name="dms_inbox_unsend">Unsend message</string>
<string name="dms_inbox_author">View author profile</string>
<string name="dms_inbox_media_shared_from">Post shared from %s</string>
<string name="dms_inbox_raven_media_unknown"><i>Unknown media type</i></string>
<string name="dms_inbox_raven_media_expired">Media expired!</string>
@ -214,4 +221,5 @@
<string name="action_notif">Activity</string>
<string name="license" translatable="false">InstaGrabber\nCopyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR \'\'AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nSee project page for third-party attributions.</string>
<string name="select_picture">Select Picture</string>
<string name="uploading">Uploading...</string>
</resources>