package adonai.diary_browser; import android.annotation.SuppressLint; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Message; import android.util.AttributeSet; import android.util.Pair; import android.view.GestureDetector; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.Toast; import com.afollestad.materialdialogs.AlertDialogWrapper; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import adonai.diary_browser.misc.SearchContainer; import adonai.diary_browser.pages.Umail; @SuppressLint("SetJavaScriptEnabled") public class DiaryWebView extends FrameLayout implements View.OnClickListener { private static final int VIEW_SCROLL_DOWN = 2; private static final int VIEW_SCROLL_UP = 1; private static final int MIN_TRIGGER_DISTANCE = 90; private static final int MILLIS_TO_FAST_SCROLL = 200; // текущий контекст public static final int IMAGE_SAVE = 0; public static final int IMAGE_COPY_URL = 1; public static final int IMAGE_OPEN_HERE = 2; public static final int IMAGE_OPEN_EXTERNAL = 3; private final GestureDetector mGestureDetector = new GestureDetector(getContext(), new ClickScrollDetector()); private final Runnable fadeAnimation = new FadeAnimation(); private DiaryActivity mActivity; private WebView mWebContent; private SearchContainer mSearchBar; private LinearLayout mButtonPanel; private ImageButton mScrollButton; private ImageButton mSearchButton; private PositionTracker mListener; private int mScrollDirection = 0; /** * Стандартные конструкторы */ public DiaryWebView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public DiaryWebView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } public DiaryWebView(Context context) { super(context); init(); } /** * Делегируем наверх методы WebView */ public void loadUrl(String url) { mWebContent.loadUrl(url); } public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) { mWebContent.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); } public WebSettings getSettings() { return mWebContent.getSettings(); } @Override public boolean canScrollVertically(int direction) { return mWebContent.canScrollVertically(direction); } /** * Далее наша логика */ public void init() { final View layout = LayoutInflater.from(getContext()).inflate(R.layout.diary_web_view, this, true); mWebContent = (WebView) layout.findViewById(R.id.web_content); mSearchBar = new SearchContainer((LinearLayout) layout.findViewById(R.id.search_bar_layout), mWebContent); mButtonPanel = (LinearLayout) layout.findViewById(R.id.page_button_panel); mScrollButton = (ImageButton) mButtonPanel.findViewById(R.id.page_updown_button); mSearchButton = (ImageButton) mButtonPanel.findViewById(R.id.page_search_button); mActivity = (DiaryActivity) getContext(); mWebContent.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return mGestureDetector.onTouchEvent(event); } }); mScrollButton.setOnClickListener(this); mSearchButton.setOnClickListener(this); } public void setDefaultSettings() { WebSettings settings = mWebContent.getSettings(); settings.setBuiltInZoomControls(true); settings.setDisplayZoomControls(false); settings.setJavaScriptEnabled(true); settings.setUserAgentString(DiaryHttpClient.FIXED_USER_AGENT); settings.setDefaultTextEncodingName("windows-1251"); settings.setJavaScriptCanOpenWindowsAutomatically(false); mWebContent.setWebViewClient(new DiaryWebClient()); mWebContent.setWebChromeClient(new WebChromeClient()); // Lollipop blocks mixed content but we should load CSS from filesystem if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); } } public void setPositionTracker(PositionTracker listener) { this.mListener = listener; } @Override public void onClick(View v) { if (v == mScrollButton) { // Офигительная штука, документации по которой нет. // Устанавливает начальную скорость скролла даже если в данный момент уже происходит скроллинг if (mScrollDirection == VIEW_SCROLL_DOWN) mWebContent.flingScroll(0, 100000); else mWebContent.flingScroll(0, -100000); } if(v == mSearchButton) { boolean wasHidden = mSearchBar.getVisibility() == View.GONE; mSearchBar.setVisibility(wasHidden ? View.VISIBLE : View.GONE); } } // Часть кода относится к панели кнопок private void handleScroll() { mButtonPanel.setVisibility(View.VISIBLE); mButtonPanel.removeCallbacks(fadeAnimation); mButtonPanel.clearAnimation(); mButtonPanel.postDelayed(fadeAnimation, 2000); // для кнопки быстрой промотки switch (mScrollDirection) { case VIEW_SCROLL_DOWN: mScrollButton.setImageResource(R.drawable.overscroll_button_down); break; case VIEW_SCROLL_UP: mScrollButton.setImageResource(R.drawable.overscroll_button_up); break; } } /** * Трекер ответственен за хранение состояния и отслеживание страницы */ public interface PositionTracker { /** * Сохраняет текущую позицию страницы */ void savePosition(String url, int position); /** * Восстанавливает текущую позицию */ int restorePosition(String url); } private class ClickScrollDetector extends GestureDetector.SimpleOnGestureListener { @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (e1 != null && e2 != null && e2.getEventTime() - e1.getEventTime() < MILLIS_TO_FAST_SCROLL) { if (distanceY > MIN_TRIGGER_DISTANCE) { mScrollDirection = VIEW_SCROLL_DOWN; handleScroll(); } else if (distanceY < -MIN_TRIGGER_DISTANCE) { mScrollDirection = VIEW_SCROLL_UP; handleScroll(); } } return false; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { Message msg = Message.obtain(mActivity.mUiHandler, Utils.HANDLE_NAME_CLICK); mWebContent.requestFocusNodeHref(msg); return super.onSingleTapConfirmed(e); } @Override public void onLongPress(MotionEvent e) { Message msg = Message.obtain(mActivity.mUiHandler, Utils.HANDLE_IMAGE_CLICK); mWebContent.requestImageRef(msg); } } private class DiaryWebClient extends WebViewClient { @Override public void onPageFinished(final WebView view, String url) { if(mListener == null) return; final Integer pos = mListener.restorePosition(url); if (pos > 0) view.postDelayed(new Runnable() { @Override public void run() { view.scrollTo(0, pos); } }, 1000); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (!url.contains("diary.ru") || !(mActivity instanceof DiaryListActivity)) { // не обрабатываем никакие ссылки кроме дневниковых, остальные отправляем в селектор // не открываем ссылки из активности U-Mail, посылаем обратно final Intent sendIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); // createChooser создает новый Intent из предыдущего, флаги нужно присоединять уже к нему! mActivity.startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.app_name))); return true; } if(mListener != null) { // сохраним позицию перед загрузкой mListener.savePosition(view.getUrl(), view.getScrollY()); } if (url.contains("?delpost&postid=")) { // удаление поста final String id = url.substring(url.lastIndexOf("=") + 1); AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(view.getContext()); builder.setTitle(android.R.string.dialog_alert_title).setCancelable(false).setMessage(R.string.really_delete); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mActivity.handleBackground(Utils.HANDLE_DELETE_POST, id); } }).setNegativeButton(android.R.string.no, null); builder.create().show(); return true; } if (url.contains("newpost"e_")) { // кнопка репоста mActivity.handleBackground(Utils.HANDLE_REPOST, url); return true; } if (url.contains("?delcomment&commentid=")) { // удаление коммента final String id = url.substring(url.lastIndexOf("=") + 1); AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(view.getContext()); builder.setTitle(android.R.string.dialog_alert_title).setCancelable(false).setMessage(R.string.really_delete); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mActivity.handleBackground(Utils.HANDLE_DELETE_COMMENT, id); } }).setNegativeButton(android.R.string.no, null); builder.create().show(); return true; } if(url.contains("?tag_delete")) { // удаление тэга final String confirmed = url; AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(view.getContext()); builder.setTitle(android.R.string.dialog_alert_title).setCancelable(false).setMessage(R.string.really_delete); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { mActivity.handleBackground(Utils.HANDLE_DELETE_TAG, confirmed); } }).setNegativeButton(android.R.string.no, null); builder.create().show(); return true; } if (url.contains("?editpost&postid=")) { // редактирование поста mActivity.handleBackground(Utils.HANDLE_EDIT_POST, url); return true; } if (url.contains("?editcomment&commentid=")) { // редактирование комментария mActivity.handleBackground(Utils.HANDLE_EDIT_COMMENT, url); return true; } if (url.contains("u-mail/?new&username=")) { // послать кому-то U-Mail try { Umail withAddress = new Umail(); withAddress.receiver = URLDecoder.decode(url.substring(url.lastIndexOf("username=") + "username=".length()), "windows-1251"); mActivity.messagePane.prepareFragment(mActivity.getUser().getSignature(), withAddress); mActivity.slider.openPane(); } catch (UnsupportedEncodingException e) { Toast.makeText(getContext(), getContext().getString(R.string.codepage_missing), Toast.LENGTH_SHORT).show(); } return true; } // а вот здесь будет обработчик того, что не смог сделать AJAX в яваскрипте дневников if (url.contains("?newquote&postid=") || url.contains("?delquote&postid=") || url.contains("up&signature=") || url.contains("down&signature=") || url.contains("?fav_add&userid=") || url.contains("?fav_del&userid=")) { mActivity.mService.handleRequest(Utils.HANDLE_JUST_DO_GET, url); return true; } mActivity.handleBackground(Utils.HANDLE_PICK_URL, new Pair<>(url, url.equals(mActivity.getUser().getCurrentDiaryPage().getPageUrl()))); return true; } } // Часть кода относится к кнопке быстрой промотки private class FadeAnimation implements Runnable { @Override public void run() { Animation animation = AnimationUtils.loadAnimation(mButtonPanel.getContext(), android.R.anim.fade_out); animation.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mButtonPanel.setVisibility(View.INVISIBLE); } @Override public void onAnimationRepeat(Animation animation) { } }); mButtonPanel.startAnimation(animation); } } }