Send stickers and gifs in DM

This commit is contained in:
Ammar Githam 2021-03-16 01:48:39 +09:00
parent 4bb77abb33
commit 6d73528387
22 changed files with 952 additions and 29 deletions

View File

@ -0,0 +1,107 @@
package awais.instagrabber.adapters;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder;
import com.facebook.drawee.controller.BaseControllerListener;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import java.util.Objects;
import awais.instagrabber.databinding.ItemMediaBinding;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.Utils;
public class GifItemsAdapter extends ListAdapter<GiphyGif, GifItemsAdapter.GifViewHolder> {
private static final DiffUtil.ItemCallback<GiphyGif> diffCallback = new DiffUtil.ItemCallback<GiphyGif>() {
@Override
public boolean areItemsTheSame(@NonNull final GiphyGif oldItem, @NonNull final GiphyGif newItem) {
return Objects.equals(oldItem.getId(), newItem.getId());
}
@Override
public boolean areContentsTheSame(@NonNull final GiphyGif oldItem, @NonNull final GiphyGif newItem) {
return Objects.equals(oldItem.getId(), newItem.getId());
}
};
private final OnItemClickListener onItemClickListener;
public GifItemsAdapter(final OnItemClickListener onItemClickListener) {
super(diffCallback);
this.onItemClickListener = onItemClickListener;
}
@NonNull
@Override
public GifViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemMediaBinding binding = ItemMediaBinding.inflate(layoutInflater, parent, false);
return new GifViewHolder(binding, onItemClickListener);
}
@Override
public void onBindViewHolder(@NonNull final GifViewHolder holder, final int position) {
holder.bind(getItem(position));
}
public static class GifViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = GifViewHolder.class.getSimpleName();
private static final int size = Utils.displayMetrics.widthPixels / 3;
private final ItemMediaBinding binding;
private final OnItemClickListener onItemClickListener;
public GifViewHolder(@NonNull final ItemMediaBinding binding,
final OnItemClickListener onItemClickListener) {
super(binding.getRoot());
this.binding = binding;
this.onItemClickListener = onItemClickListener;
binding.duration.setVisibility(View.GONE);
final GenericDraweeHierarchyBuilder builder = new GenericDraweeHierarchyBuilder(itemView.getResources());
builder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER);
binding.item.setHierarchy(builder.build());
}
public void bind(final GiphyGif item) {
if (onItemClickListener != null) {
itemView.setOnClickListener(v -> onItemClickListener.onItemClick(item));
}
final BaseControllerListener<ImageInfo> controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFailure(final String id, final Throwable throwable) {
Log.e(TAG, "onFailure: ", throwable);
}
};
final ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(item.getImages().getFixedHeight().getWebp()))
.setResizeOptions(ResizeOptions.forDimensions(size, size))
.build();
final PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setAutoPlayAnimations(true)
.setControllerListener(controllerListener);
binding.item.setController(builder.build());
}
}
public interface OnItemClickListener {
void onItemClick(GiphyGif giphyGif);
}
}

View File

@ -0,0 +1,150 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.text.Editable;
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.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.snackbar.Snackbar;
import awais.instagrabber.R;
import awais.instagrabber.adapters.GifItemsAdapter;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.LayoutGifPickerBinding;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.Debouncer;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.viewmodels.GifPickerViewModel;
public class GifPickerBottomDialogFragment extends BottomSheetDialogFragment {
private static final String TAG = GifPickerBottomDialogFragment.class.getSimpleName();
private static final int INPUT_DEBOUNCE_INTERVAL = 500;
private static final String INPUT_KEY = "gif_search_input";
private LayoutGifPickerBinding binding;
private GifPickerViewModel viewModel;
private GifItemsAdapter gifItemsAdapter;
private OnSelectListener onSelectListener;
private Debouncer<String> inputDebouncer;
public static GifPickerBottomDialogFragment newInstance() {
final Bundle args = new Bundle();
final GifPickerBottomDialogFragment fragment = new GifPickerBottomDialogFragment();
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NORMAL, R.style.ThemeOverlay_Rounded_BottomSheetDialog);
final Debouncer.Callback<String> callback = new Debouncer.Callback<String>() {
@Override
public void call(final String key) {
final Editable text = binding.input.getText();
if (TextUtils.isEmpty(text)) {
viewModel.search(null);
return;
}
viewModel.search(text.toString().trim());
}
@Override
public void onError(final Throwable t) {
Log.e(TAG, "onError: ", t);
}
};
inputDebouncer = new Debouncer<>(callback, INPUT_DEBOUNCE_INTERVAL);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = LayoutGifPickerBinding.inflate(inflater, container, false);
viewModel = new ViewModelProvider(this).get(GifPickerViewModel.class);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
init();
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (dialog == null) return;
final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialog;
final View bottomSheetInternal = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
if (bottomSheetInternal == null) return;
bottomSheetInternal.getLayoutParams().height = ViewGroup.LayoutParams.MATCH_PARENT;
bottomSheetInternal.requestLayout();
}
private void init() {
setupList();
setupInput();
setupObservers();
}
private void setupList() {
final Context context = getContext();
if (context == null) return;
binding.gifList.setLayoutManager(new GridLayoutManager(context, 3));
binding.gifList.setHasFixedSize(true);
gifItemsAdapter = new GifItemsAdapter(entry -> {
if (onSelectListener == null) return;
onSelectListener.onSelect(entry);
});
binding.gifList.setAdapter(gifItemsAdapter);
}
private void setupInput() {
binding.input.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
inputDebouncer.call(INPUT_KEY);
}
});
}
private void setupObservers() {
viewModel.getImages().observe(getViewLifecycleOwner(), imagesResource -> {
if (imagesResource == null) return;
switch (imagesResource.status) {
case SUCCESS:
gifItemsAdapter.submitList(imagesResource.data);
break;
case ERROR:
final Context context = getContext();
if (context != null && imagesResource.message != null) {
Snackbar.make(context, binding.getRoot(), imagesResource.message, Snackbar.LENGTH_LONG);
}
break;
case LOADING:
break;
}
});
}
public void setOnSelectListener(final OnSelectListener onSelectListener) {
this.onSelectListener = onSelectListener;
}
public interface OnSelectListener {
void onSelect(GiphyGif giphyGif);
}
}

View File

@ -81,6 +81,7 @@ import awais.instagrabber.customviews.helpers.SwipeAndRestoreItemTouchHelperCall
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.dialogs.DirectItemReactionDialogFragment;
import awais.instagrabber.dialogs.GifPickerBottomDialogFragment;
import awais.instagrabber.dialogs.MediaPickerBottomDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.fragments.UserSearchFragment;
@ -737,6 +738,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void hideInput() {
binding.emojiToggle.setVisibility(View.GONE);
binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
binding.input.setVisibility(View.GONE);
@ -750,6 +752,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
private void showInput() {
binding.emojiToggle.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
binding.input.setVisibility(View.VISIBLE);
@ -788,16 +791,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
binding.send.setListenForRecord(true);
startIconAnimation();
}
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
return;
}
if (binding.send.isListenForRecord()) {
binding.send.setListenForRecord(false);
startIconAnimation();
}
binding.gallery.setVisibility(View.GONE);
binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
}
private String getDirectItemPreviewText(final DirectItem item) {
@ -937,8 +942,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onStart() {
isRecording = true;
binding.input.setHint(null);
binding.gallery.setVisibility(View.GONE);
binding.gif.setVisibility(View.GONE);
binding.camera.setVisibility(View.GONE);
binding.gallery.setVisibility(View.GONE);
if (PermissionUtils.hasAudioRecordPerms(context)) {
viewModel.startRecording();
return;
@ -958,8 +964,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
public void onFinish(final long recordTime) {
Log.d(TAG, "onFinish");
binding.input.setHint("Message");
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
viewModel.stopRecording(false);
isRecording = false;
}
@ -971,16 +978,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
if (PermissionUtils.hasAudioRecordPerms(context)) {
tooltip.show(binding.send);
}
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
viewModel.stopRecording(true);
isRecording = false;
}
});
binding.recordView.setOnBasketAnimationEndListener(() -> {
binding.input.setHint(R.string.dms_thread_message_hint);
binding.gallery.setVisibility(View.VISIBLE);
binding.gif.setVisibility(View.VISIBLE);
binding.camera.setVisibility(View.VISIBLE);
binding.gallery.setVisibility(View.VISIBLE);
});
binding.input.addTextChangedListener(new TextWatcherAdapter() {
// int prevLength = 0;
@ -1057,6 +1066,16 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact
mediaPicker.show(getChildFragmentManager(), "MediaPicker");
hideKeyboard(true);
});
binding.gif.setOnClickListener(v -> {
final GifPickerBottomDialogFragment gifPicker = GifPickerBottomDialogFragment.newInstance();
gifPicker.setOnSelectListener(giphyGif -> {
gifPicker.dismiss();
if (giphyGif == null) return;
handleSentMessage(viewModel.sendAnimatedMedia(giphyGif));
});
gifPicker.show(getChildFragmentManager(), "GifPicker");
hideKeyboard(true);
});
binding.camera.setOnClickListener(v -> {
final Intent intent = new Intent(context, CameraActivity.class);
startActivityForResult(intent, CAMERA_REQUEST_CODE);

View File

@ -53,6 +53,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThreadDeta
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.BitmapUtils;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
@ -641,6 +642,24 @@ public final class ThreadManager {
return data;
}
public LiveData<Resource<Object>> sendAnimatedMedia(@NonNull final GiphyGif giphyGif) {
final MutableLiveData<Resource<Object>> data = new MutableLiveData<>();
final Long userId = getCurrentUserId(data);
if (userId == null) return data;
final String clientContext = UUID.randomUUID().toString();
final DirectItem directItem = DirectItemFactory.createAnimatedMedia(userId, clientContext, giphyGif);
directItem.setPending(true);
addItems(0, Collections.singletonList(directItem));
data.postValue(Resource.loading(directItem));
final Call<DirectThreadBroadcastResponse> request = service.broadcastAnimatedMedia(
clientContext,
threadIdOrUserIds,
giphyGif
);
enqueueRequest(request, data, directItem);
return data;
}
public void sendVoice(@NonNull final MutableLiveData<Resource<Object>> data,
@NonNull final Uri uri,
@NonNull final List<Float> waveform,

View File

@ -7,7 +7,8 @@ public enum BroadcastItemType {
IMAGE("configure_photo"),
LINK("link"),
VIDEO("configure_video"),
VOICE("share_voice");
VOICE("share_voice"),
ANIMATED_MEDIA("animated_media");
private final String value;

View File

@ -0,0 +1,14 @@
package awais.instagrabber.repositories;
import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface GifRepository {
@GET("/api/v1/creatives/story_media_search_keyed_format/")
Call<GiphyGifResponse> searchGiphyGifs(@Query("request_surface") final String requestSurface,
@Query("q") final String query,
@Query("media_types") final String mediaTypes);
}

View File

@ -0,0 +1,27 @@
package awais.instagrabber.repositories.requests.directmessages;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.models.enums.BroadcastItemType;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
public class AnimatedMediaBroadcastOptions extends BroadcastOptions {
private final GiphyGif giphyGif;
public AnimatedMediaBroadcastOptions(final String clientContext,
final ThreadIdOrUserIds threadIdOrUserIds,
final GiphyGif giphyGif) {
super(clientContext, threadIdOrUserIds, BroadcastItemType.ANIMATED_MEDIA);
this.giphyGif = giphyGif;
}
@Override
public Map<String, String> getFormMap() {
final Map<String, String> form = new HashMap<>();
form.put("is_sticker", String.valueOf(giphyGif.isSticker()));
form.put("id", giphyGif.getId());
return form;
}
}

View File

@ -0,0 +1,70 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.Objects;
public class GiphyGif {
private final String type;
private final String id;
private final String title;
private final int isSticker;
private final GiphyGifImages images;
public GiphyGif(final String type, final String id, final String title, final int isSticker, final GiphyGifImages images) {
this.type = type;
this.id = id;
this.title = title;
this.isSticker = isSticker;
this.images = images;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getTitle() {
return title;
}
public boolean isSticker() {
return isSticker == 1;
}
public GiphyGifImages getImages() {
return images;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGif giphyGif = (GiphyGif) o;
return isSticker == giphyGif.isSticker &&
Objects.equals(type, giphyGif.type) &&
Objects.equals(id, giphyGif.id) &&
Objects.equals(title, giphyGif.title) &&
Objects.equals(images, giphyGif.images);
}
@Override
public int hashCode() {
return Objects.hash(type, id, title, isSticker, images);
}
@NonNull
@Override
public String toString() {
return "GiphyGif{" +
"type='" + type + '\'' +
", id='" + id + '\'' +
", title='" + title + '\'' +
", isSticker=" + isSticker() +
", images=" + images +
'}';
}
}

View File

@ -0,0 +1,59 @@
package awais.instagrabber.repositories.responses.giphy;
import java.util.Objects;
public class GiphyGifImage {
private final int height;
private final int width;
private final long webpSize;
private final String webp;
public GiphyGifImage(final int height, final int width, final long webpSize, final String webp) {
this.height = height;
this.width = width;
this.webpSize = webpSize;
this.webp = webp;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public long getWebpSize() {
return webpSize;
}
public String getWebp() {
return webp;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifImage that = (GiphyGifImage) o;
return height == that.height &&
width == that.width &&
webpSize == that.webpSize &&
Objects.equals(webp, that.webp);
}
@Override
public int hashCode() {
return Objects.hash(height, width, webpSize, webp);
}
@Override
public String toString() {
return "GiphyGifImage{" +
"height=" + height +
", width=" + width +
", webpSize=" + webpSize +
", webp='" + webp + '\'' +
'}';
}
}

View File

@ -0,0 +1,40 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.Objects;
import awais.instagrabber.repositories.responses.AnimatedMediaFixedHeight;
public class GiphyGifImages {
private final AnimatedMediaFixedHeight fixedHeight;
public GiphyGifImages(final AnimatedMediaFixedHeight fixedHeight) {
this.fixedHeight = fixedHeight;
}
public AnimatedMediaFixedHeight getFixedHeight() {
return fixedHeight;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifImages that = (GiphyGifImages) o;
return Objects.equals(fixedHeight, that.fixedHeight);
}
@Override
public int hashCode() {
return Objects.hash(fixedHeight);
}
@NonNull
@Override
public String toString() {
return "GiphyGifImages{" +
"fixedHeight=" + fixedHeight +
'}';
}
}

View File

@ -0,0 +1,46 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.Objects;
public class GiphyGifResponse {
private final GiphyGifResults results;
private final String status;
public GiphyGifResponse(final GiphyGifResults results, final String status) {
this.results = results;
this.status = status;
}
public GiphyGifResults getResults() {
return results;
}
public String getStatus() {
return status;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifResponse that = (GiphyGifResponse) o;
return Objects.equals(results, that.results) &&
Objects.equals(status, that.status);
}
@Override
public int hashCode() {
return Objects.hash(results, status);
}
@NonNull
@Override
public String toString() {
return "GiphyGifResponse{" +
"results=" + results +
", status='" + status + '\'' +
'}';
}
}

View File

@ -0,0 +1,47 @@
package awais.instagrabber.repositories.responses.giphy;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.Objects;
public class GiphyGifResults {
private final List<GiphyGif> giphyGifs;
private final List<GiphyGif> giphy;
public GiphyGifResults(final List<GiphyGif> giphyGifs, final List<GiphyGif> giphy) {
this.giphyGifs = giphyGifs;
this.giphy = giphy;
}
public List<GiphyGif> getGiphyGifs() {
return giphyGifs;
}
public List<GiphyGif> getGiphy() {
return giphy;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final GiphyGifResults that = (GiphyGifResults) o;
return Objects.equals(giphyGifs, that.giphyGifs) &&
Objects.equals(giphy, that.giphy);
}
@Override
public int hashCode() {
return Objects.hash(giphyGifs, giphy);
}
@NonNull
@Override
public String toString() {
return "GiphyGifResults{" +
"giphyGifs=" + giphyGifs +
", giphy=" + giphy +
'}';
}
}

View File

@ -15,6 +15,7 @@ import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
@ -84,11 +85,16 @@ public final class DMUtils {
message = item.getPlaceholder().getMessage();
break;
case MEDIA_SHARE:
subtitle = resources.getString(R.string.dms_inbox_shared_post, username != null ? username : "",
item.getMediaShare().getUser().getUsername());
final User mediaShareUser = item.getMediaShare().getUser();
subtitle = resources.getString(R.string.dms_inbox_shared_post,
username != null ? username : "",
mediaShareUser == null ? "" : mediaShareUser.getUsername());
break;
case ANIMATED_MEDIA:
subtitle = resources.getString(R.string.dms_inbox_shared_gif, username != null ? username : "");
final DirectItemAnimatedMedia animatedMedia = item.getAnimatedMedia();
subtitle = resources.getString(animatedMedia.isSticker() ? R.string.dms_inbox_shared_sticker
: R.string.dms_inbox_shared_gif,
username != null ? username : "");
break;
case PROFILE:
subtitle = resources
@ -111,8 +117,10 @@ public final class DMUtils {
final int format = reelType.equals("highlight_reel")
? R.string.dms_inbox_shared_highlight
: R.string.dms_inbox_shared_story;
subtitle = resources.getString(format, username != null ? username : "",
item.getStoryShare().getMedia().getUser().getUsername());
final User storyShareMediaUser = item.getStoryShare().getMedia().getUser();
subtitle = resources.getString(format,
username != null ? username : "",
storyShareMediaUser == null ? "" : storyShareMediaUser.getUsername());
}
break;
}
@ -126,12 +134,16 @@ public final class DMUtils {
subtitle = item.getVideoCallEvent().getDescription();
break;
case CLIP:
subtitle = resources.getString(R.string.dms_inbox_shared_clip, username != null ? username : "",
item.getClip().getClip().getUser().getUsername());
final User clipUser = item.getClip().getClip().getUser();
subtitle = resources.getString(R.string.dms_inbox_shared_clip,
username != null ? username : "",
clipUser == null ? "" : clipUser.getUsername());
break;
case FELIX_SHARE:
subtitle = resources.getString(R.string.dms_inbox_shared_igtv, username != null ? username : "",
item.getFelixShare().getVideo().getUser().getUsername());
final User felixShareVideoUser = item.getFelixShare().getVideo().getUser();
subtitle = resources.getString(R.string.dms_inbox_shared_igtv,
username != null ? username : "",
felixShareVideoUser == null ? "" : felixShareVideoUser.getUsername());
break;
case RAVEN_MEDIA:
subtitle = getRavenMediaSubtitle(item, resources, username);

View File

@ -8,13 +8,16 @@ import java.util.UUID;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.repositories.responses.AnimatedMediaImages;
import awais.instagrabber.repositories.responses.Audio;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.MediaCandidate;
import awais.instagrabber.repositories.responses.VideoVersion;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia;
import awais.instagrabber.repositories.responses.directmessages.DirectItemVoiceMedia;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
public final class DirectItemFactory {
@ -213,4 +216,45 @@ public final class DirectItemFactory {
0,
false);
}
public static DirectItem createAnimatedMedia(final long userId,
final String clientContext,
final GiphyGif giphyGif) {
final AnimatedMediaImages animatedImages = new AnimatedMediaImages(giphyGif.getImages().getFixedHeight());
final DirectItemAnimatedMedia animateMedia = new DirectItemAnimatedMedia(
giphyGif.getId(),
animatedImages,
false,
giphyGif.isSticker()
);
return new DirectItem(
UUID.randomUUID().toString(),
userId,
System.currentTimeMillis() * 1000,
DirectItemType.ANIMATED_MEDIA,
null,
null,
null,
clientContext,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
null,
animateMedia,
null,
null,
null,
null,
0,
false
);
}
}

View File

@ -26,6 +26,7 @@ import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;
import awais.instagrabber.repositories.responses.directmessages.DirectThread;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DirectoryUtils;
@ -219,6 +220,10 @@ public class DirectThreadViewModel extends AndroidViewModel {
return threadManager.unsend(item);
}
public LiveData<Resource<Object>> sendAnimatedMedia(@NonNull final GiphyGif giphyGif) {
return threadManager.sendAnimatedMedia(giphyGif);
}
public User getCurrentUser() {
return currentUser;
}

View File

@ -0,0 +1,121 @@
package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import awais.instagrabber.models.Resource;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse;
import awais.instagrabber.repositories.responses.giphy.GiphyGifResults;
import awais.instagrabber.webservices.GifService;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class GifPickerViewModel extends ViewModel {
private static final String TAG = GifPickerViewModel.class.getSimpleName();
private final MutableLiveData<Resource<List<GiphyGif>>> images = new MutableLiveData<>(Resource.success(Collections.emptyList()));
private final GifService gifService;
private Call<GiphyGifResponse> searchRequest;
public GifPickerViewModel() {
gifService = GifService.getInstance();
search(null);
}
public LiveData<Resource<List<GiphyGif>>> getImages() {
return images;
}
public void search(final String query) {
final Resource<List<GiphyGif>> currentValue = images.getValue();
if (currentValue != null && currentValue.status == Resource.Status.LOADING) {
cancelSearchRequest();
}
images.postValue(Resource.loading(getCurrentImages()));
searchRequest = gifService.searchGiphyGifs(query, query != null);
searchRequest.enqueue(new Callback<GiphyGifResponse>() {
@Override
public void onResponse(@NonNull final Call<GiphyGifResponse> call,
@NonNull final Response<GiphyGifResponse> response) {
if (response.isSuccessful()) {
parseResponse(response);
return;
}
if (response.errorBody() != null) {
try {
final String string = response.errorBody().string();
final String msg = String.format(Locale.US,
"onResponse: url: %s, responseCode: %d, errorBody: %s",
call.request().url().toString(),
response.code(),
string);
images.postValue(Resource.error(msg, getCurrentImages()));
Log.e(TAG, msg);
} catch (IOException e) {
images.postValue(Resource.error(e.getMessage(), getCurrentImages()));
Log.e(TAG, "onResponse: ", e);
}
}
images.postValue(Resource.error("request was not successful and response error body was null", getCurrentImages()));
}
@Override
public void onFailure(@NonNull final Call<GiphyGifResponse> call,
@NonNull final Throwable t) {
images.postValue(Resource.error(t.getMessage(), getCurrentImages()));
Log.e(TAG, "enqueueRequest: onFailure: ", t);
}
});
}
private void parseResponse(final Response<GiphyGifResponse> response) {
final GiphyGifResponse giphyGifResponse = response.body();
if (giphyGifResponse == null) {
images.postValue(Resource.error("Response body was null", getCurrentImages()));
return;
}
final GiphyGifResults results = giphyGifResponse.getResults();
images.postValue(Resource.success(
ImmutableList.<GiphyGif>builder()
.addAll(results.getGiphy() == null ? Collections.emptyList() : results.getGiphy())
.addAll(results.getGiphyGifs() == null ? Collections.emptyList() : results.getGiphyGifs())
.build()
));
}
// @NonNull
// private List<GiphyGifImage> getGiphyGifImages(@NonNull final List<GiphyGif> giphy) {
// return giphy.stream()
// .map(giphyGif -> {
// final GiphyGifImages images = giphyGif.getImages();
// if (images == null) return null;
// return images.getOriginal();
// })
// .filter(Objects::nonNull)
// .collect(Collectors.toList());
// }
private List<GiphyGif> getCurrentImages() {
final Resource<List<GiphyGif>> value = images.getValue();
return value == null ? Collections.emptyList() : value.data;
}
public void cancelSearchRequest() {
if (searchRequest == null) return;
searchRequest.cancel();
}
}

View File

@ -17,6 +17,7 @@ import java.util.UUID;
import java.util.stream.Collectors;
import awais.instagrabber.repositories.DirectMessagesRepository;
import awais.instagrabber.repositories.requests.directmessages.AnimatedMediaBroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions;
import awais.instagrabber.repositories.requests.directmessages.BroadcastOptions.ThreadIdOrUserIds;
import awais.instagrabber.repositories.requests.directmessages.LinkBroadcastOptions;
@ -34,6 +35,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectThreadDeta
import awais.instagrabber.repositories.responses.directmessages.DirectThreadFeedResponse;
import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipientsResponse;
import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
@ -182,6 +184,11 @@ public class DirectMessagesService extends BaseService {
return broadcast(new ReactionBroadcastOptions(clientContext, threadIdOrUserIds, itemId, emoji, delete));
}
public Call<DirectThreadBroadcastResponse> broadcastAnimatedMedia(final String clientContext,
final ThreadIdOrUserIds threadIdOrUserIds,
final GiphyGif giphyGif) {
return broadcast(new AnimatedMediaBroadcastOptions(clientContext, threadIdOrUserIds, giphyGif));
}
private Call<DirectThreadBroadcastResponse> broadcast(@NonNull final BroadcastOptions broadcastOptions) {
if (TextUtils.isEmpty(broadcastOptions.getClientContext())) {

View File

@ -0,0 +1,33 @@
package awais.instagrabber.webservices;
import awais.instagrabber.repositories.GifRepository;
import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse;
import retrofit2.Call;
import retrofit2.Retrofit;
public class GifService extends BaseService {
private final GifRepository repository;
private static GifService instance;
private GifService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(GifRepository.class);
}
public static GifService getInstance() {
if (instance == null) {
instance = new GifService();
}
return instance;
}
public Call<GiphyGifResponse> searchGiphyGifs(final String query,
final boolean includeGifs) {
final String mediaTypes = includeGifs ? "[\"giphy_gifs\",\"giphy\"]" : "[\"giphy\"]";
return repository.searchGiphyGifs("direct", query, mediaTypes);
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12.25,9c0.41,0 0.75,0.34 0.75,0.75v4.5c0,0.41 -0.34,0.75 -0.75,0.75s-0.75,-0.34 -0.75,-0.75v-4.5c0,-0.41 0.34,-0.75 0.75,-0.75zM10,9.75c0,-0.41 -0.34,-0.75 -0.75,-0.75L6,9c-0.6,0 -1,0.5 -1,1v4c0,0.5 0.4,1 1,1h3c0.6,0 1,-0.5 1,-1v-1.25c0,-0.41 -0.34,-0.75 -0.75,-0.75s-0.75,0.34 -0.75,0.75v0.75h-2v-3h2.75c0.41,0 0.75,-0.34 0.75,-0.75zM19,9.75c0,-0.41 -0.34,-0.75 -0.75,-0.75L15.5,9c-0.55,0 -1,0.45 -1,1v4.25c0,0.41 0.34,0.75 0.75,0.75s0.75,-0.34 0.75,-0.75L16,13h1.25c0.41,0 0.75,-0.34 0.75,-0.75s-0.34,-0.75 -0.75,-0.75L16,11.5v-1h2.25c0.41,0 0.75,-0.34 0.75,-0.75z"/>
</vector>

View File

@ -120,7 +120,8 @@
app:layout_constraintBottom_toBottomOf="@id/input"
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/input" />
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/emoji_toggle"
@ -143,7 +144,8 @@
app:rippleColor="@color/grey_500"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
app:strokeColor="@color/black"
app:strokeWidth="1dp" />
app:strokeWidth="1dp"
tools:visibility="visible" />
<awais.instagrabber.customviews.KeyNotifyingEmojiEditText
android:id="@+id/input"
@ -158,16 +160,32 @@
android:textColorHint="@color/grey_500"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/camera"
app:layout_constraintEnd_toStartOf="@id/gif"
app:layout_constraintStart_toEndOf="@id/emoji_toggle"
app:layout_constraintTop_toBottomOf="@id/reply_preview_text"
app:layout_goneMarginBottom="4dp"
app:layout_goneMarginEnd="24dp" />
app:layout_goneMarginEnd="24dp"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gif"
android:layout_width="32dp"
android:layout_height="0dp"
android:background="@android:color/transparent"
android:scaleType="fitCenter"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/camera"
app:layout_constraintStart_toEndOf="@id/input"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_round_gif_24"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/camera"
android:layout_width="32dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:background="@android:color/transparent"
android:paddingStart="4dp"
android:paddingEnd="4dp"
@ -175,10 +193,10 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/gallery"
app:layout_constraintStart_toEndOf="@id/input"
app:layout_constraintStart_toEndOf="@id/gif"
app:layout_constraintTop_toTopOf="@id/input"
app:srcCompat="@drawable/ic_camera_24"
tools:visibility="gone" />
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/gallery"
@ -196,7 +214,7 @@
app:layout_constraintEnd_toStartOf="@id/send"
app:layout_constraintStart_toEndOf="@id/camera"
app:layout_constraintTop_toTopOf="@id/input"
tools:visibility="gone" />
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordView
android:id="@+id/record_view"
@ -214,7 +232,7 @@
app:slide_to_cancel_margin_right="16dp"
app:slide_to_cancel_text="Slide To Cancel"
app:slide_to_cancel_text_color="@color/white"
tools:visibility="gone" />
tools:visibility="visible" />
<awais.instagrabber.customviews.RecordButton
android:id="@+id/send"
@ -231,7 +249,8 @@
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/input_bg"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle" />
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
tools:visibility="visible" />
<awais.instagrabber.customviews.emoji.EmojiPicker
android:id="@+id/emoji_picker"
@ -241,7 +260,8 @@
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept_pending_request_question"
@ -255,7 +275,7 @@
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/chats_barrier"
tools:visibility="visible" />
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/decline"
@ -272,7 +292,7 @@
app:layout_constraintEnd_toStartOf="@id/accept"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="visible" />
tools:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/accept"
@ -288,5 +308,5 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/decline"
app:layout_constraintTop_toBottomOf="@id/accept_pending_request_question"
tools:visibility="visible" />
tools:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/input_bg"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="4dp"
android:layout_marginEnd="4dp"
android:background="@drawable/bg_input"
app:layout_constraintBottom_toBottomOf="@id/input"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/input" />
<com.google.android.material.button.MaterialButton
android:id="@+id/search_icon"
style="@style/Widget.MaterialComponents.Button.Icon.NoInsets"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="2dp"
android:background="@android:color/transparent"
android:clickable="false"
android:scrollbars="none"
app:icon="@drawable/ic_search_24"
app:iconGravity="textStart"
app:iconSize="24dp"
app:iconTint="@color/grey_700"
app:layout_constraintBottom_toBottomOf="@id/input_bg"
app:layout_constraintEnd_toStartOf="@id/input"
app:layout_constraintStart_toStartOf="@id/input_bg"
app:layout_constraintTop_toTopOf="@id/input"
app:rippleColor="@color/grey_500"
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.Button.Circle"
app:strokeColor="@color/black"
app:strokeWidth="1dp" />
<androidx.emoji.widget.EmojiAppCompatEditText
android:id="@+id/input"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="20dp"
android:layout_marginBottom="4dp"
android:background="@android:color/transparent"
android:hint="@string/search_giphy"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:textColor="@color/white"
android:textColorHint="@color/grey_500"
app:layout_constraintBottom_toTopOf="@id/gif_list"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/search_icon"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/gif_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/input" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -185,6 +185,7 @@
<string name="dms_inbox_shared_video">%s shared a video</string>
<string name="dms_inbox_shared_message">%s sent a message</string>
<string name="dms_inbox_shared_gif">%s shared a gif</string>
<string name="dms_inbox_shared_sticker">%s shared a sticker</string>
<string name="dms_inbox_shared_profile">%s shared a profile: @%s</string>
<string name="dms_inbox_shared_location">%s shared a location: %s</string>
<string name="dms_inbox_shared_highlight">%s shared a story highlight by @%s</string>
@ -490,4 +491,5 @@
<string name="auto_refresh_every">Auto refresh every</string>
<string name="secs">secs</string>
<string name="mins">mins</string>
<string name="search_giphy">Search GIPHY</string>
</resources>