From 4e667479aec485b2f5b58b76e679d2400d383f7e Mon Sep 17 00:00:00 2001 From: Andrew Dolgov Date: Mon, 31 Dec 2012 12:49:30 +0400 Subject: layout improvements, add missing files --- src/android/webkit/WebViewClassic.java | 14 + .../android/view/web/TitleBarWebView.java | 353 +++++++++++++++++++++ src/org/fox/ttrss/ArticleFragment.java | 4 + src/org/fox/ttrss/FeedsActivity.java | 24 +- src/org/fox/ttrss/HeadlinesActivity.java | 6 +- src/org/fox/ttrss/offline/OfflineActivity.java | 2 + .../fox/ttrss/offline/OfflineFeedsActivity.java | 49 ++- 7 files changed, 442 insertions(+), 10 deletions(-) create mode 100644 src/android/webkit/WebViewClassic.java create mode 100644 src/com/nobu_games/android/view/web/TitleBarWebView.java (limited to 'src') diff --git a/src/android/webkit/WebViewClassic.java b/src/android/webkit/WebViewClassic.java new file mode 100644 index 00000000..e315beea --- /dev/null +++ b/src/android/webkit/WebViewClassic.java @@ -0,0 +1,14 @@ +package android.webkit; + +import android.view.View; + +/** + * Trojan class for getting access to a hidden API level 16 interface + */ +public class WebViewClassic { + public interface TitleBarDelegate { + int getTitleHeight(); + + public void onSetEmbeddedTitleBar(final View title); + } +} diff --git a/src/com/nobu_games/android/view/web/TitleBarWebView.java b/src/com/nobu_games/android/view/web/TitleBarWebView.java new file mode 100644 index 00000000..4931bed0 --- /dev/null +++ b/src/com/nobu_games/android/view/web/TitleBarWebView.java @@ -0,0 +1,353 @@ +package com.nobu_games.android.view.web; + +/* + * Copyright (C) 2012 Thomas Werner + * Portions Copyright (C) 2006 The Android Open Source Project + * Portions Copyright (C) 2012 The K-9 Dog Walkers + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import java.lang.reflect.Method; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.webkit.WebViewClassic.TitleBarDelegate; +import android.widget.FrameLayout; + +/** + * WebView derivative with custom setEmbeddedTitleBar implementation for Android + * versions that do not support that feature anymore. + *

+ * Usage + *

+ * Call {@link #setEmbeddedTitleBarCompat(View)} for setting a view as embedded + * title bar on top of the displayed WebView page. + */ +public class TitleBarWebView extends WebView implements TitleBarDelegate { + /** + * Internally used view wrapper for suppressing unwanted touch events on the + * title bar view when WebView contents is being touched. + */ + private class TouchBlockView extends FrameLayout { + public TouchBlockView(Context context) { + super(context); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if(!mTouchInTitleBar) { + return false; + } else { + switch(ev.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTouchInTitleBar = false; + break; + + default: + + } + + return super.dispatchTouchEvent(ev); + } + } + } + + private static final String TAG = "TitleBarWebView"; + View mTitleBar; + int mTitleBarOffs; + boolean mTouchInTitleBar; + boolean mTouchMove; + private Rect mClipBounds = new Rect(); + private Matrix mMatrix = new Matrix(); + private Method mNativeGetVisibleTitleHeightMethod; + + /** + * This will always contain a reference to the title bar view no matter if + * {@code setEmbeddedTitleBar()} or the Jelly Bean workaround is used. We use this in + * {@link #getTitleHeight()}. + */ + private View mTitleBarView; + + public TitleBarWebView(Context context) { + super(context); + init(); + } + + public TitleBarWebView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public TitleBarWebView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * Corrects the visual displacement caused by the title bar view. + *

+ * {@inheritDoc} + */ + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if(mTitleBar != null) { + final int sy = getScrollY(); + final int visTitleHeight = getVisibleTitleHeightCompat(); + final float x = event.getX(); + float y = event.getY(); + + switch(event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + if(y <= visTitleHeight) { + mTouchInTitleBar = true; + } + break; + + case MotionEvent.ACTION_MOVE: + mTouchMove = true; + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTouchMove = false; + break; + + default: + } + + if(mTouchInTitleBar) { + y += sy; + event.setLocation(x, y); + + return mTitleBar.dispatchTouchEvent(event); + } else { + if(Build.VERSION.SDK_INT < 16) { + if(!mTouchMove) { + mTitleBarOffs = getVisibleTitleHeightCompat(); + } + + y -= mTitleBarOffs; + if(y < 0) y = 0; + event.setLocation(x, y); + } + + return super.dispatchTouchEvent(event); + } + } else { + return super.dispatchTouchEvent(event); + } + } + + /** + * Sets a {@link View} as an embedded title bar to appear on top of the + * WebView page. + *

+ * This method tries to call the hidden API method setEmbeddedTitleBar if + * present. On failure the custom implementation provided by this class will + * be used instead. + * + * @param v The view to set or null for removing the title bar view. + */ + public void setEmbeddedTitleBarCompat(View v) { + try { + Method nativeMethod = getClass().getMethod("setEmbeddedTitleBar", + View.class); + nativeMethod.invoke(this, v); + } catch(Exception e) { + Log.d(TAG, + "Native setEmbeddedTitleBar not available. Starting workaround"); + setEmbeddedTitleBarJellyBean(v); + } + + mTitleBarView = v; + } + + @Override + protected int computeVerticalScrollExtent() { + if(mTitleBar == null) return super.computeVerticalScrollExtent(); + return getViewHeightWithTitle() - getVisibleTitleHeightCompat(); + } + + @Override + protected int computeVerticalScrollOffset() { + if(mTitleBar == null) return super.computeVerticalScrollOffset(); + return Math.max(getScrollY() - getTitleHeight(), 0); + } + + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + canvas.save(); + + if(child == mTitleBar) { + mTitleBar.offsetLeftAndRight(getScrollX() - mTitleBar.getLeft()); + + if(Build.VERSION.SDK_INT < 16) { + mMatrix.set(canvas.getMatrix()); + mMatrix.postTranslate(0, -getScrollY()); + canvas.setMatrix(mMatrix); + } + } + + boolean result = super.drawChild(canvas, child, drawingTime); + canvas.restore(); + + return result; + } + + /** + * Gets the currently visible height of the title bar view if set. + * + * @return Visible height of title bar view or 0 if not set. + */ + protected int getVisibleTitleHeightCompat() { + if(mTitleBar == null && mNativeGetVisibleTitleHeightMethod != null) { + try { + return (Integer) mNativeGetVisibleTitleHeightMethod + .invoke(this); + } catch(Exception e) { + } + } + + return Math.max(getTitleHeight() - Math.max(0, getScrollY()), 0); + } + + @Override + protected void onDraw(Canvas canvas) { + if (Build.VERSION.SDK_INT >= 16) { + super.onDraw(canvas); + return; + } + + canvas.save(); + + if(mTitleBar != null) { + final int sy = getScrollY(); + final int sx = getScrollX(); + mClipBounds.top = sy; + mClipBounds.left = sx; + mClipBounds.right = mClipBounds.left + getWidth(); + mClipBounds.bottom = mClipBounds.top + getHeight(); + canvas.clipRect(mClipBounds); + mMatrix.set(canvas.getMatrix()); + int titleBarOffs = getVisibleTitleHeightCompat(); + if(titleBarOffs < 0) titleBarOffs = 0; + mMatrix.postTranslate(0, titleBarOffs); + canvas.setMatrix(mMatrix); + } + + super.onDraw(canvas); + canvas.restore(); + } + + /** + * Overrides a hidden method by replicating the behavior of the original + * WebView class from Android 2.3.4. + *

+ * The worst that could happen is that this method never gets called, which + * isn't too bad because this does not harm the functionality of this class. + */ + protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, + int l, int t, int r, int b) { + int sy = getScrollY(); + + if(sy < 0) { + t -= sy; + } + scrollBar.setBounds(l, t + getVisibleTitleHeightCompat(), r, b); + scrollBar.draw(canvas); + } + + /** + * Get the height of the title bar view. + * + *

+ * In the Jelly Bean workaround we need this method because we have to implement the + * {@link TitleBarDelegate} interface. But by implementing this method we override the hidden + * {@code getTitleHeight()} of the {@link WebView}s in older Android versions. + *
+ * What we should do, is return the title height on Jelly Bean and call through to the parent + * class on older Android versions. But this would require even more trickery, so we just + * inline the parent functionality which simply calls {@link View#getHeight()}. This is exactly + * what we do on Jelly Bean anyway. + *

+ */ + @Override + public int getTitleHeight() { + if (mTitleBarView != null) { + return mTitleBarView.getHeight(); + } + return 0; + } + + private int getViewHeightWithTitle() { + int height = getHeight(); + if(isHorizontalScrollBarEnabled() && !overlayHorizontalScrollbar()) { + height -= getHorizontalScrollbarHeight(); + } + return height; + } + + private void init() { + try { + mNativeGetVisibleTitleHeightMethod = WebView.class + .getDeclaredMethod("getVisibleTitleHeight"); + } catch(NoSuchMethodException e) { + Log.w(TAG, + "Could not retrieve native hidden getVisibleTitleHeight method"); + } + } + + /** + * The hidden method setEmbeddedTitleBar has been removed from Jelly Bean. + * This method replicates the functionality. + * + * @param v + */ + private void setEmbeddedTitleBarJellyBean(View v) { + if(mTitleBar == v) return; + + if(mTitleBar != null) { + removeView(mTitleBar); + } + + if(null != v) { + LayoutParams vParams = new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0); + + TouchBlockView tbv = new TouchBlockView(getContext()); + FrameLayout.LayoutParams tbvParams = new FrameLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); + tbv.addView(v, tbvParams); + addView(tbv, vParams); + v = tbv; + } + + mTitleBar = v; + } + + @Override + public void onSetEmbeddedTitleBar(View title) { /* unused */ } +} diff --git a/src/org/fox/ttrss/ArticleFragment.java b/src/org/fox/ttrss/ArticleFragment.java index 9e3734e8..86fc4472 100644 --- a/src/org/fox/ttrss/ArticleFragment.java +++ b/src/org/fox/ttrss/ArticleFragment.java @@ -126,6 +126,10 @@ public class ArticleFragment extends Fragment implements GestureDetector.OnDoubl else titleStr = m_article.title; + if (!m_activity.isSmallScreen()) { + title.setTextSize(TypedValue.COMPLEX_UNIT_SP, 21f); + } + title.setText(titleStr); title.setPaintFlags(title.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); title.setOnClickListener(new OnClickListener() { diff --git a/src/org/fox/ttrss/FeedsActivity.java b/src/org/fox/ttrss/FeedsActivity.java index 7d95e317..58f8eda6 100644 --- a/src/org/fox/ttrss/FeedsActivity.java +++ b/src/org/fox/ttrss/FeedsActivity.java @@ -10,6 +10,7 @@ import org.fox.ttrss.util.AppRater; import android.view.ViewGroup; import android.animation.LayoutTransition; +import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Intent; import android.content.SharedPreferences; @@ -21,6 +22,7 @@ import android.support.v4.app.FragmentTransaction; import android.util.Log; import android.view.MenuItem; import android.view.View; +import android.widget.LinearLayout; public class FeedsActivity extends OnlineActivity implements HeadlinesEventListener { private final String TAG = this.getClass().getSimpleName(); @@ -97,7 +99,7 @@ public class FeedsActivity extends OnlineActivity implements HeadlinesEventListe } else { FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - + if (m_prefs.getBoolean("enable_cats", false)) { ft.replace(R.id.feeds_fragment, new FeedCategoriesFragment(), FRAG_CATS); } else { @@ -115,7 +117,13 @@ public class FeedsActivity extends OnlineActivity implements HeadlinesEventListe } } else { // savedInstanceState != null m_actionbarUpEnabled = savedInstanceState.getBoolean("actionbarUpEnabled"); - + + if (!isSmallScreen()) { + // temporary hack because FeedsActivity doesn't track whether active feed is open + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + container.setWeightSum(3f); + } + if (!isCompatMode() && m_actionbarUpEnabled) { getActionBar().setDisplayHomeAsUpEnabled(true); } @@ -175,7 +183,17 @@ public class FeedsActivity extends OnlineActivity implements HeadlinesEventListe ft.replace(R.id.headlines_fragment, new LoadingFragment(), null); ft.commit(); - + + if (!isCompatMode()) { + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + float wSum = container.getWeightSum(); + if (wSum <= 2.0f) { + ObjectAnimator anim = ObjectAnimator.ofFloat(container, "weightSum", wSum, 3.0f); + anim.setDuration(200); + anim.start(); + } + } + final Feed fFeed = feed; new Handler().postDelayed(new Runnable() { diff --git a/src/org/fox/ttrss/HeadlinesActivity.java b/src/org/fox/ttrss/HeadlinesActivity.java index 81dd1043..81c4ca8c 100644 --- a/src/org/fox/ttrss/HeadlinesActivity.java +++ b/src/org/fox/ttrss/HeadlinesActivity.java @@ -121,7 +121,11 @@ public class HeadlinesActivity extends OnlineActivity implements HeadlinesEventL @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + overridePendingTransition(0, R.anim.right_slide_out); + return true; default: Log.d(TAG, "onOptionsItemSelected, unhandled id=" + item.getItemId()); return super.onOptionsItemSelected(item); diff --git a/src/org/fox/ttrss/offline/OfflineActivity.java b/src/org/fox/ttrss/offline/OfflineActivity.java index f7ee9768..608b9bcf 100644 --- a/src/org/fox/ttrss/offline/OfflineActivity.java +++ b/src/org/fox/ttrss/offline/OfflineActivity.java @@ -76,6 +76,8 @@ public class OfflineActivity extends CommonActivity { if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_DARK")) { setTheme(R.style.DarkTheme); + } else if (m_prefs.getString("theme", "THEME_DARK").equals("THEME_SEPIA")) { + setTheme(R.style.SepiaTheme); } else { setTheme(R.style.LightTheme); } diff --git a/src/org/fox/ttrss/offline/OfflineFeedsActivity.java b/src/org/fox/ttrss/offline/OfflineFeedsActivity.java index 07bedc37..1b35d9e1 100644 --- a/src/org/fox/ttrss/offline/OfflineFeedsActivity.java +++ b/src/org/fox/ttrss/offline/OfflineFeedsActivity.java @@ -1,12 +1,16 @@ package org.fox.ttrss.offline; import org.fox.ttrss.GlobalState; +import org.fox.ttrss.LoadingFragment; import org.fox.ttrss.R; +import android.animation.LayoutTransition; +import android.animation.ObjectAnimator; import android.annotation.SuppressLint; import android.content.Intent; import android.database.sqlite.SQLiteStatement; import android.os.Bundle; +import android.os.Handler; import android.preference.PreferenceManager; import android.provider.BaseColumns; import android.support.v4.app.Fragment; @@ -14,6 +18,8 @@ import android.support.v4.app.FragmentTransaction; import android.util.Log; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; public class OfflineFeedsActivity extends OfflineActivity implements OfflineHeadlinesEventListener { private final String TAG = this.getClass().getSimpleName(); @@ -96,6 +102,16 @@ public class OfflineFeedsActivity extends OfflineActivity implements OfflineHead findViewById(R.id.loading_container).setVisibility(View.GONE); initMenu(); + + /* if (!isSmallScreen()) { + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + container.setWeightSum(3f); + } */ + + if (!isCompatMode() && !isSmallScreen()) { + ((ViewGroup)findViewById(R.id.headlines_fragment)).setLayoutTransition(new LayoutTransition()); + ((ViewGroup)findViewById(R.id.feeds_fragment)).setLayoutTransition(new LayoutTransition()); + } } public void openFeedArticles(int feedId, boolean isCat) { @@ -189,7 +205,7 @@ public class OfflineFeedsActivity extends OfflineActivity implements OfflineHead onFeedSelected(feedId, false, true); } - public void onFeedSelected(int feedId, boolean isCat, boolean open) { + public void onFeedSelected(final int feedId, final boolean isCat, boolean open) { if (open) { if (isSmallScreen()) { @@ -201,13 +217,34 @@ public class OfflineFeedsActivity extends OfflineActivity implements OfflineHead startActivityForResult(intent, 0); } else { - FragmentTransaction ft = getSupportFragmentManager() - .beginTransaction(); + /* if (!isCompatMode()) { + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + float wSum = container.getWeightSum(); + if (wSum <= 2.0f) { + ObjectAnimator anim = ObjectAnimator.ofFloat(container, "weightSum", wSum, 3.0f); + anim.setDuration(200); + anim.start(); + } + } */ - OfflineHeadlinesFragment hf = new OfflineHeadlinesFragment(feedId, isCat); - ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + // ^ no idea why the animation hangs half the time :( + + LinearLayout container = (LinearLayout) findViewById(R.id.fragment_container); + container.setWeightSum(3f); + + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + FragmentTransaction ft = getSupportFragmentManager() + .beginTransaction(); + + OfflineHeadlinesFragment hf = new OfflineHeadlinesFragment(feedId, isCat); + ft.replace(R.id.headlines_fragment, hf, FRAG_HEADLINES); + + ft.commit(); + } + }, 10); - ft.commit(); } } } -- cgit v1.2.3-54-g00ecf