diff --git a/app/src/main/java/ru/vtosters/lite/hooks/PhotoViewer.java b/app/src/main/java/ru/vtosters/lite/hooks/PhotoViewer.java new file mode 100644 index 000000000..de5f655f7 --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/hooks/PhotoViewer.java @@ -0,0 +1,167 @@ +package ru.vtosters.lite.hooks; + +import static ru.vtosters.lite.utils.Globals.getIdentifier; +import static ru.vtosters.lite.utils.Globals.getPreferences; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.vk.core.dialogs.actionspopup.ActionsPopup; +import com.vk.core.drawable.RecoloredDrawable; +import com.vk.dto.common.AttachmentWithMedia; +import com.vtosters.lite.R; +import com.vtosters.lite.attachments.PhotoAttachment; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.URLEncoder; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import ru.vtosters.lite.utils.Globals; +import ru.vtosters.lite.utils.Themes; + +public class PhotoViewer { + + static OkHttpClient client = new OkHttpClient(); + + public static boolean interceptClick(AttachmentWithMedia attachment, MenuItem item, View view) { + if (item.getItemId() == Globals.getIdentifier("search_photo", "id")) { + searchPhoto(getUrlFromAttachment((PhotoAttachment) attachment)); + return true; + } else if (item.getItemId() == Globals.getIdentifier("copy_photo_url", "id")) { + copyPhotoUrl(getUrlFromAttachment((PhotoAttachment) attachment)); + return true; + } else if (item.getItemId() == Globals.getIdentifier("open_original_photo", "id")) { + openUrl(getUrlFromAttachment((PhotoAttachment) attachment)); + return true; + } + return true; + } + + public static void addMenuItems(AttachmentWithMedia attachment, ActionsPopup.b actionPopup, int i, boolean z) { + actionPopup.a(getIdentifier("search_photo_content", "string"), + Themes.isDarkTheme() + ? Globals.getContext().getDrawable(R.drawable.ic_menu_search_outline_28) + : new RecoloredDrawable(Globals.getContext().getDrawable(R.drawable.ic_menu_search_outline_28), Themes.getAccentColor()), + false, + () -> { + searchPhoto(getUrlFromAttachment((PhotoAttachment) attachment)); + return null; + } + ); + actionPopup.a(getIdentifier("copy_photo_url_content", "string"), + !Themes.isDarkTheme() + ? new RecoloredDrawable(Globals.getContext().getDrawable(R.drawable.ic_copy_outline_28), Themes.getAccentColor()) + : new RecoloredDrawable(Globals.getContext().getDrawable(R.drawable.ic_copy_outline_28), Color.WHITE), + false, + () -> { + copyPhotoUrl(getUrlFromAttachment((PhotoAttachment) attachment)); + return null; + } + ); + actionPopup.a(getIdentifier("open_original_photo_content", "string"), + Themes.isDarkTheme() + ? Globals.getContext().getDrawable(R.drawable.ic_link_outline_28) + : new RecoloredDrawable(Globals.getContext().getDrawable(R.drawable.ic_link_outline_28), + Themes.getAccentColor()), + false, + () -> { + openUrl(getUrlFromAttachment((PhotoAttachment) attachment)); + return null; + } + ); + } + + private static String getUrlFromAttachment(PhotoAttachment attachment) { + var imageSizes = attachment.D.Q.t1(); + if (imageSizes.isEmpty()) + return null; + var max = imageSizes.get(0); + for (int i = 1; i < imageSizes.size(); ++i) { + var size = imageSizes.get(i); + if (max != null && max.t1() < size.t1()) { + max = size; + } + } + return max.url; + } + + private static void searchPhoto(String url) { + if (url == null || url.isEmpty()) return; + switch (getPreferences().getInt("search_engine", 0)) { + case 0: + searchWithYandex(url); + break; + case 1: + searchWithGoogle(url); + break; + case 2: + searchWithBing(url); + break; + } + } + + private static void searchWithYandex(String url) { + var request = new Request.a() + .b(String.format("https://yandex.ru/images-apphost/image-download?url=%s&images_avatars_size=preview&images_avatars_namespace=images-cbir", + URLEncoder.encode(url))) + .a(); + client.a(request).a(new Callback() { + @Override + public void a(Call call, IOException e) { + Log.d("PhotoViewer", e+""); + } + + @Override + public void a(Call call, Response response) throws IOException { + try { + JSONObject payload = new JSONObject(response.a().g()); + var cbirId = payload.optInt("image_shard") + "/" + payload.optString("image_id"); + var imageUrl = String.format("https://avatars.mds.yandex.net/get-images-cbir/%s/orig", cbirId); + var searchUrl = String.format("https://yandex.ru/images/search?rpt=imageview&url=%s&cbir_id=%s", imageUrl, cbirId); + openUrl(searchUrl); + } catch (JSONException e) { + e.printStackTrace(); + } + } + }); + } + + private static void searchWithGoogle(String url) { + openUrl("https://www.google.ru/searchbyimage?image_url=" + + URLEncoder.encode(url)); + } + + private static void searchWithBing(String url) { + openUrl("https://www.bing.com/images/search?view=detailv2&iss=SBI&form=SBIIDP&sbisrc=UrlPaste&q=imgurl:" + + URLEncoder.encode(url)); + } + + private static void copyPhotoUrl(String url) { + if (url == null || url.isEmpty()) return; + ClipboardManager manager = (ClipboardManager) Globals.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + manager.setPrimaryClip(ClipData.newPlainText("vk_photo_url", url)); + Toast.makeText(Globals.getContext(), Globals.getIdentifier("copied_to_clipboard", "string"), Toast.LENGTH_SHORT).show(); + } + + private static void openUrl(String url) { + var uri = Uri.parse(url); + var intent = new Intent(Intent.ACTION_VIEW, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Globals.getContext().startActivity(intent); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/components/ImagineArrayAdapter.java b/app/src/main/java/ru/vtosters/lite/ui/components/ImagineArrayAdapter.java new file mode 100644 index 000000000..269a1b9eb --- /dev/null +++ b/app/src/main/java/ru/vtosters/lite/ui/components/ImagineArrayAdapter.java @@ -0,0 +1,173 @@ +package ru.vtosters.lite.ui.components; + +import static ru.vtosters.lite.utils.Themes.getTextAttr; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.CheckedTextView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ListView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import ru.vtosters.lite.utils.Globals; +import ru.vtosters.lite.utils.LayoutHelper; + +public class ImagineArrayAdapter extends ArrayAdapter { + + private List mItems = new ArrayList<>(); + private OnClickListener mListener; + private int mSelectedIndex = -1; + + public ImagineArrayAdapter(@NonNull Context context, OnClickListener listener) { + super(context, 0); + this.mListener = listener; + } + + public ImagineArrayAdapter(@NonNull Context context, List items, OnClickListener listener) { + super(context, 0, items); + mItems = items; + this.mListener = listener; + } + + public void setSelected(int i) { + this.mSelectedIndex = i; + } + + @Override + public int getCount() { + return mItems.size(); + } + + public void add(ImagineArrayAdapterItem item) { + mItems.add(item); + super.add(item); + } + + public void add(Drawable icon, String text) { + var item = new ImagineArrayAdapterItem(icon, text); + mItems.add(item); + super.add(item); + } + + public void add(int iconId, String text) { + var icon = Globals.getResources().getDrawable(iconId); + var item = new ImagineArrayAdapterItem(icon, text); + mItems.add(item); + super.add(item); + } + + public void add(Drawable icon, int textId) { + var text = Globals.getResources().getString(textId); + var item = new ImagineArrayAdapterItem(icon, text); + mItems.add(item); + super.add(item); + } + + public void add(int iconId, int textId) { + var icon = Globals.getResources().getDrawable(iconId); + var text = Globals.getResources().getString(textId); + var item = new ImagineArrayAdapterItem(icon, text); + mItems.add(item); + super.add(item); + } + + @NonNull + @Override + public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { + var container = new LinearLayout(getContext()); + container.setOrientation(LinearLayout.HORIZONTAL); + container.setGravity(Gravity.CENTER_VERTICAL); + container.setPadding(0, Globals.convertDpToPixel(5.0f), 0, Globals.convertDpToPixel(5.0f)); + container.setLayoutParams(new ViewGroup.LayoutParams(-1, -2)); + container.setOnClickListener(v -> { + if (position != mSelectedIndex) { + mListener.onClick(position); + } + }); + + // if make radiobutton without layout inflation it can't be unclickable + var checkedTextView = (CheckedTextView) LayoutInflater.from(getContext()) + .inflate(Globals.getIdentifier("select_dialog_singlechoice_material", "layout"), parent, false); + checkedTextView.setChecked(position == mSelectedIndex); + container.addView(checkedTextView, LayoutHelper.createLinear(Globals.convertDpToPixel(50.0f), -2)); + + var item = mItems.get(position); + + var imageView = new ImageView(getContext()); + imageView.setImageDrawable(item.getIcon()); + imageView.setPadding( + Globals.convertDpToPixel(13.0f), + 0, + 0, + 0 + ); + var imageViewParams = LayoutHelper.createLinear( + Globals.convertDpToPixel(50.0f), + Globals.convertDpToPixel(50.0f) + ); + container.addView(imageView, imageViewParams); + + var textView = new CheckedTextView(getContext()); + textView.setText(item.getText()); + textView.setTextColor(getTextAttr()); + textView.setPadding( + Globals.convertDpToPixel(13.0f), + Globals.convertDpToPixel(5.0f), + Globals.convertDpToPixel(5.0f), + Globals.convertDpToPixel(5.0f)); + textView.setTextSize(18.0f); + textView.setGravity(Gravity.CENTER_VERTICAL); + var textViewParams = LayoutHelper.createLinear(-2, -1); + container.addView(textView, textViewParams); + + return container; + } + + public static class ImagineArrayAdapterItem { + private Drawable mIcon; + private String mText; + + public ImagineArrayAdapterItem(Drawable icon, String text) { + this.mIcon = icon; + this.mText = text; + } + + public ImagineArrayAdapterItem(int iconId, String text) { + this.mIcon = Globals.getResources().getDrawable(iconId); + this.mText = text; + } + + public ImagineArrayAdapterItem(Drawable icon, int textId) { + this.mIcon = icon; + this.mText = Globals.getResources().getString(textId); + } + + public ImagineArrayAdapterItem(int iconId, int textId) { + this.mIcon = Globals.getResources().getDrawable(iconId); + this.mText = Globals.getResources().getString(textId); + } + + public Drawable getIcon() { + return mIcon; + } + + public String getText() { + return mText; + } + } + + public interface OnClickListener { + public void onClick(int i); + } +} diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java index b357ace17..7477078c0 100644 --- a/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java +++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java @@ -2,6 +2,7 @@ package ru.vtosters.lite.ui.fragments; import static ru.vtosters.lite.utils.Globals.edit; import static ru.vtosters.lite.utils.Globals.getIdentifier; +import static ru.vtosters.lite.utils.Globals.getPreferences; import static ru.vtosters.lite.utils.Globals.restartApplicationWithTimer; import static ru.vtosters.lite.utils.Themes.getAccentColor; import static ru.vtosters.lite.utils.Themes.getAlertStyle; @@ -11,15 +12,21 @@ import android.content.Context; import android.content.DialogInterface; import android.content.res.ColorStateList; import android.os.Bundle; +import android.view.LayoutInflater; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.ListView; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; +import com.vk.core.dialogs.alert.VkAlertDialog; import com.vtosters.lite.general.fragments.MaterialPreferenceToolbarFragment; +import java.util.Arrays; + import ru.vtosters.lite.downloaders.VideoDownloader; +import ru.vtosters.lite.ui.components.ImagineArrayAdapter; public class MediaFragment extends MaterialPreferenceToolbarFragment{ public static void download(Context ctx){ @@ -58,6 +65,28 @@ public class MediaFragment extends MaterialPreferenceToolbarFragment{ private void prefs(){ findPreference("download_video").setOnPreferenceClickListener(new MediaFragment.download()); findPreference("dateformat").setOnPreferenceChangeListener(new MediaFragment.restart()); + findPreference("select_photo_search_engine").setOnPreferenceClickListener(preference -> { + var items = Arrays.asList( + new ImagineArrayAdapter.ImagineArrayAdapterItem(getIdentifier("yandex", "drawable"), "Yandex"), + new ImagineArrayAdapter.ImagineArrayAdapterItem(getIdentifier("google", "drawable"), "Google"), + new ImagineArrayAdapter.ImagineArrayAdapterItem(getIdentifier("microsoft", "drawable"), "Bing") + ); + + var alert = new VkAlertDialog.Builder(getActivity()) + .create(); + + var listView = (ListView) LayoutInflater.from(getContext()).inflate(com.vtosters.lite.R.layout.abc_select_dialog_material, null, false); + var adapter = new ImagineArrayAdapter(getContext(), items, i -> { + getPreferences().edit().putInt("search_engine", i).apply(); + alert.dismiss(); + }); + adapter.setSelected(getPreferences().getInt("search_engine", 0)); + listView.setAdapter(adapter); + alert.setView(listView); + alert.show(); + + return true; + }); } public class download implements Preference.OnPreferenceClickListener{ diff --git a/smali/res/drawable/google.xml b/smali/res/drawable/google.xml new file mode 100644 index 000000000..159606987 --- /dev/null +++ b/smali/res/drawable/google.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/smali/res/drawable/microsoft.png b/smali/res/drawable/microsoft.png new file mode 100644 index 000000000..9aac33649 Binary files /dev/null and b/smali/res/drawable/microsoft.png differ diff --git a/smali/res/drawable/yandex.xml b/smali/res/drawable/yandex.xml new file mode 100644 index 000000000..c196cc044 --- /dev/null +++ b/smali/res/drawable/yandex.xml @@ -0,0 +1,12 @@ + + + + diff --git a/smali/res/menu/photo_viewer.xml b/smali/res/menu/photo_viewer.xml index 28e572f82..aaa3c723c 100644 --- a/smali/res/menu/photo_viewer.xml +++ b/smali/res/menu/photo_viewer.xml @@ -40,4 +40,16 @@ android:id="@id/report" android:showAsAction="never" android:title="@string/report_content" /> + + + \ No newline at end of file diff --git a/smali/res/values-ru/strings.xml b/smali/res/values-ru/strings.xml index c12f5b998..3273f08e3 100644 --- a/smali/res/values-ru/strings.xml +++ b/smali/res/values-ru/strings.xml @@ -2645,6 +2645,10 @@ Фотографии загружены Фотографии успешно загружены Сохранить + Найти фото + Скопировать ссылку на фото + Оригинал + Скопировано в буфер обмена Поделиться Фотографии Фотографии diff --git a/smali/res/values/ids.xml b/smali/res/values/ids.xml index c06f92aad..d70f23ce4 100644 --- a/smali/res/values/ids.xml +++ b/smali/res/values/ids.xml @@ -2769,6 +2769,9 @@ + + + diff --git a/smali/res/values/strings.xml b/smali/res/values/strings.xml index 6a73142c4..ee66da595 100644 --- a/smali/res/values/strings.xml +++ b/smali/res/values/strings.xml @@ -3277,6 +3277,10 @@ the VK camera" Reply as community Reply Report + Search photo + Copy photo url + Original + Copied to clipboard Are you sure you want to mark as spam and delete %s? Spam Mark as spam diff --git a/smali/res/xml/preferences_media.xml b/smali/res/xml/preferences_media.xml index df9bbd336..54a26b6f2 100644 --- a/smali/res/xml/preferences_media.xml +++ b/smali/res/xml/preferences_media.xml @@ -63,6 +63,11 @@ android:summary="@string/interfacevideorecsum" android:title="@string/interfacevideorec" /> + + + addMenuItems(Lcom/vk/dto/common/AttachmentWithMedia;Lcom/vk/core/dialogs/actionspopup/ActionsPopup$b;IZ)V + invoke-virtual/range {p2 .. p2}, Lcom/vk/core/dialogs/actionspopup/ActionsPopup$b;->c()Lcom/vk/core/dialogs/actionspopup/ActionsPopup; move-result-object v0 @@ -2747,6 +2757,15 @@ .method public final a(Lcom/vk/dto/common/AttachmentWithMedia;Landroid/view/MenuItem;Landroid/view/View;)Z .locals 2 + invoke-static {p1, p2, p3}, Lru/vtosters/lite/hooks/PhotoViewer;->interceptClick(Lcom/vk/dto/common/AttachmentWithMedia;Landroid/view/MenuItem;Landroid/view/View;)Z + + move-result v0 + + if-nez v0, :cond_custom + + return v0 + + :cond_custom .line 20 instance-of v0, p1, Lcom/vtosters/lite/attachments/PhotoAttachment;