From 918967f502dfd7f0647bb79062420494c27ac6f4 Mon Sep 17 00:00:00 2001 From: Vologhat <47892605+Vologhat@users.noreply.github.com> Date: Sun, 17 Jul 2022 21:04:49 +0300 Subject: [PATCH] added some features to photo viewer #411 -Added image searching in the internet with feature of selecting search engine -Added copying image file url to the clipboard -Added opening image file url in the browsers --- .../ru/vtosters/lite/hooks/PhotoViewer.java | 167 +++++++++++++++++ .../ui/components/ImagineArrayAdapter.java | 173 ++++++++++++++++++ .../lite/ui/fragments/MediaFragment.java | 29 +++ smali/res/drawable/google.xml | 18 ++ smali/res/drawable/microsoft.png | Bin 0 -> 3184 bytes smali/res/drawable/yandex.xml | 12 ++ smali/res/menu/photo_viewer.xml | 12 ++ smali/res/values-ru/strings.xml | 4 + smali/res/values/ids.xml | 3 + smali/res/values/strings.xml | 4 + smali/res/xml/preferences_media.xml | 5 + .../vk/ui/photoviewer/MenuController.smali | 19 ++ 12 files changed, 446 insertions(+) create mode 100644 app/src/main/java/ru/vtosters/lite/hooks/PhotoViewer.java create mode 100644 app/src/main/java/ru/vtosters/lite/ui/components/ImagineArrayAdapter.java create mode 100644 smali/res/drawable/google.xml create mode 100644 smali/res/drawable/microsoft.png create mode 100644 smali/res/drawable/yandex.xml 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 0000000000000000000000000000000000000000..9aac33649ba86f6742156b438c4d41d3fc827aa4 GIT binary patch literal 3184 zcmeH~`&Sa!7RL{uXui8zrdU4KjFy(x7@}zxGw-N5iOp12J~J=Q6-0amK0(dWFs*FT z(oj~%201%}K0qSt21jI0~N`Z%Zd$=oAxnu`96s2q+dY+60KvVzghJc&J zTL93~^Kw6MlpHkoE|7SXWYi(f_w{D%>luLBZyq*^^qTBC7TkBlV+#DPg$Pw`z{u{X4qC8-!(FL2`9+XIQ_m@ z^Y->bSfm(tWZc-6jqi(2s^<)eQ#(e=?*ur(DndB}k(%0NU0N(J9~1gIQV$?EK?+mx$H?Lj z{_k#R>)wQ3#skI1(m78}h@J=NPYy+JYr|lSC3U?sYkXJGe^kY6I5Ejl(RoNZdWY4& zl`07bNf<&ee#SrWi$C?0)h3=Y0d^?tX?GN`*<1&i-$mW~H5dfdX3d$J@;iNH0J#1y z_Of1KY77N1Xz6*tr2>XI*lqUB{jDZ{vmthcbXn0b{JfEm1|EDc%4x%45cjebf#~X@EQ+hNB)LRn219Ap);v z$Rd)ngc9jEtBG>z)lL-)R05*)T_d!QziM#=Gh>|gR!dw%F3wv_-X%3Zv9TS&9jF(= zeHm6D^FE6DRt&$~915GSFKa>bRNn~vHw8uB3-zf@ZRz#D@{fEmz7bhG-!a!JjVeJ< z;*Kr1++j6t%ajd4wiAGMLNi1@wwNENd?n7^YLj-yYmF3%OoH^!YIjg_t$57Jy&{c_ zYL5cW{lMh$>#~H@k%^H^55kWZi}NPxi`Mo8eC$ClI!Q-$MF?9o)IcLs73P~I$EotS z)Z^!04Wnfrm;C(LJqjejPq39hS}!=%l+({iHp!5pC|}}5#P6Gb=)tVZ3xBHU{E_lb z?ou~3!)jbFUtJX_rgT$-)n~{m+atjhj=?NtbpoyEel0SQjZ}-8R)))Uo0k^3C|$#p znfW&vp3L#(-*U&eb*stZw!AEBOJ29kIW?JY7rY#=VwM$f4G*jCJTRDU1p_HwU!qg8 zbjbq95&m{Gs4n<`$xWzHi{&Bn=$uvuE6LvH!{yjs*CUYHqiLZ z5QBqP79Ne}paO4t)=gb>+N6TW4V)Yx&nBVswZ{ z{}NL^n$0Y*;$b~P<8Xd@^0^0^ip1xtlvKVd#bquX-L9bZFLa3Q6a$J6Xu_PmD96A~ zPJR-+%umY?Hj&^?CtoNu8kEdbk5<}}-v2_F&a^}=$bZ*=BX*fLDoFWIv!FkI@tXx$M2S%^ssnX=A^mR1*>Um1K|{hT z54EVmF4KB*)24=g+{=lvgsL6Ej*s!F7V)hTt@H`(?f2{{h9gVC9x92RTj-b_oMN2D z*qX}M+9uN)MT>!>AVQUkeAaux-pV2DP7wa0t&E0Fnmq6J`8{ttiLvA5iqp)*JDaXg zRy-T^M9aXc@Ye5ZNcS9VD5(UYgwpmT3gcpRb#TDm2)-`o?yr)@QOtJ+DO;p;kB;lv zxLE8b?%^JRtc}+$!scW8%Y`y_ zMYx3)Jw-4gSS3(oZOw_W%ZS0N*(I{IA=Bq1#j>!(+oEdk4e^U#=$@=nvN*Uh{81=u z3oC`+@}ztkPJF(Fde_YPk(2$J{IKmhkGH!B54`hfXsP7Vi-yIQkN)I=5?}o%n0*1x z*3hc&bhtI9Pt~N2{9r{%+8>6X8InWjepVv6pjOm6esbM68n&H=p-bEoRgdvI$K8dk zn96$)N8CGrO&`?9gb7*MLd?Z6UD_x9^L;+n6>13TO>4*r4tf+6n*%)H(}p0f^*fi< zxo~Qp1&H|P6>TaAu=_S4{^o@E1{nW0be%j3G%CSQYBlBl8@TaGIJ>)z+Oz_FkD{jI zl%T1j2jW#+yMKdp4nv9U@V;(%X`b>-6U2mR2Ttp;$*@9otVgZfdes8rD@8Bfy*74m TZ^oXl9|y00`M5uH3rYVkx2n*3 literal 0 HcmV?d00001 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;