mirror of
https://github.com/moparisthebest/k-9
synced 2025-02-25 15:11:52 -05:00
Merge pull request #415 from zjw/non_locking_scroll_view_rebased
Kit Kat UI issue
This commit is contained in:
commit
8f19d56f00
@ -7,21 +7,23 @@
|
|||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
|
||||||
<!-- Header area -->
|
<com.fsck.k9.view.NonLockingScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/message_view_header_container"
|
android:orientation="vertical"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<!-- Header area -->
|
||||||
<include layout="@layout/message_view_header"/>
|
<include layout="@layout/message_view_header"/>
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Content area -->
|
<!-- Content area -->
|
||||||
<com.fsck.k9.view.MessageWebView
|
<com.fsck.k9.view.MessageWebView
|
||||||
android:id="@+id/message_content"
|
android:id="@+id/message_content"
|
||||||
android:layout_height="0dip"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_width="fill_parent"/>
|
android:layout_width="fill_parent"/>
|
||||||
|
|
||||||
<com.fsck.k9.view.AccessibleWebView
|
<com.fsck.k9.view.AccessibleWebView
|
||||||
@ -30,14 +32,9 @@
|
|||||||
android:layout_width="fill_parent"/>
|
android:layout_width="fill_parent"/>
|
||||||
|
|
||||||
<!-- Attachments area -->
|
<!-- Attachments area -->
|
||||||
<ScrollView
|
|
||||||
android:id="@+id/attachments_container"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/inside_attachments_container"
|
android:id="@+id/attachments_container"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
@ -64,7 +61,9 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</com.fsck.k9.view.NonLockingScrollView>
|
||||||
|
|
||||||
<Button android:id="@+id/download_remainder"
|
<Button android:id="@+id/download_remainder"
|
||||||
android:text="@string/message_view_download_remainder"
|
android:text="@string/message_view_download_remainder"
|
||||||
|
@ -3,23 +3,19 @@
|
|||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/header_container"
|
android:id="@+id/header_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<!-- Color chip -->
|
<!-- Color chip -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/chip"
|
android:id="@+id/chip"
|
||||||
android:layout_width="8dip"
|
android:layout_width="8dip"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -41,13 +37,13 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal" >
|
||||||
|
|
||||||
|
|
||||||
<RelativeLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<QuickContactBadge
|
<QuickContactBadge
|
||||||
android:id="@+id/contact_badge"
|
android:id="@+id/contact_badge"
|
||||||
@ -65,21 +61,21 @@
|
|||||||
android:layout_marginBottom="2dip"
|
android:layout_marginBottom="2dip"
|
||||||
android:layout_below="@+id/contact_badge"
|
android:layout_below="@+id/contact_badge"
|
||||||
android:layout_centerHorizontal="true"
|
android:layout_centerHorizontal="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical" >
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/answered"
|
android:id="@+id/answered"
|
||||||
android:layout_width="32sp"
|
android:layout_width="32sp"
|
||||||
android:layout_height="32sp"
|
android:layout_height="32sp"
|
||||||
android:paddingRight="2dip"
|
android:paddingRight="2dip"
|
||||||
android:background="@drawable/ic_email_answered_small"/>
|
android:background="@drawable/ic_email_answered_small" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/forwarded"
|
android:id="@+id/forwarded"
|
||||||
android:layout_width="22sp"
|
android:layout_width="22sp"
|
||||||
android:layout_height="22sp"
|
android:layout_height="22sp"
|
||||||
android:paddingRight="4dip"
|
android:paddingRight="4dip"
|
||||||
android:background="@drawable/ic_email_forwarded_small"/>
|
android:background="@drawable/ic_email_forwarded_small" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
@ -89,7 +85,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="6dip"
|
android:padding="6dip"
|
||||||
android:layout_marginLeft="2dp">
|
android:layout_marginLeft="2dp" >
|
||||||
|
|
||||||
<!-- From -->
|
<!-- From -->
|
||||||
<TextView
|
<TextView
|
||||||
@ -148,7 +144,7 @@
|
|||||||
android:text="@string/message_view_cc_label"
|
android:text="@string/message_view_cc_label"
|
||||||
android:textColor="?android:attr/textColorPrimary"
|
android:textColor="?android:attr/textColorPrimary"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textAppearance="@android:style/TextAppearance.Medium" /> -->
|
android:textAppearance="@android:style/TextAppearance.Medium" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/cc"
|
android:id="@+id/cc"
|
||||||
@ -194,7 +190,6 @@
|
|||||||
android:id="@+id/additional_headers_view"
|
android:id="@+id/additional_headers_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:layout_marginRight="6dip"
|
android:layout_marginRight="6dip"
|
||||||
android:singleLine="false"
|
android:singleLine="false"
|
||||||
@ -211,7 +206,7 @@
|
|||||||
android:layout_height="1dip"
|
android:layout_height="1dip"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:background="@drawable/divider_horizontal_email"/>
|
android:background="@drawable/divider_horizontal_email" />
|
||||||
|
|
||||||
<!-- Button area -->
|
<!-- Button area -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@ -221,7 +216,7 @@
|
|||||||
android:paddingLeft="6dip"
|
android:paddingLeft="6dip"
|
||||||
android:paddingRight="6dip"
|
android:paddingRight="6dip"
|
||||||
android:paddingTop="4dip"
|
android:paddingTop="4dip"
|
||||||
android:baselineAligned="false">
|
android:baselineAligned="false" >
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/show_pictures"
|
android:id="@+id/show_pictures"
|
||||||
@ -231,7 +226,7 @@
|
|||||||
android:layout_marginBottom="4dip"
|
android:layout_marginBottom="4dip"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/message_view_show_pictures_action"
|
android:text="@string/message_view_show_pictures_action"
|
||||||
style="?android:attr/buttonStyleSmall"/>
|
style="?android:attr/buttonStyleSmall" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/show_attachments"
|
android:id="@+id/show_attachments"
|
||||||
@ -241,7 +236,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/message_view_show_attachments_action"
|
android:text="@string/message_view_show_attachments_action"
|
||||||
style="?android:attr/buttonStyleSmall"/>
|
style="?android:attr/buttonStyleSmall" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/show_message"
|
android:id="@+id/show_message"
|
||||||
@ -251,12 +246,10 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/message_view_show_message_action"
|
android:text="@string/message_view_show_message_action"
|
||||||
style="?android:attr/buttonStyleSmall"/>
|
style="?android:attr/buttonStyleSmall" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<include layout="@layout/message_view_crypto_layout"/>
|
<include layout="@layout/message_view_crypto_layout" />
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.fsck.k9.view.MessageHeader>
|
</com.fsck.k9.view.MessageHeader>
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
32
src/com/fsck/k9/Clock.java
Normal file
32
src/com/fsck/k9/Clock.java
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.fsck.k9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class provide the current time (like {@link System#currentTimeMillis()}).
|
||||||
|
* It's intended to be mocked out for unit tests.
|
||||||
|
*/
|
||||||
|
public class Clock {
|
||||||
|
public static final Clock INSTANCE = new Clock();
|
||||||
|
|
||||||
|
protected Clock() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
}
|
178
src/com/fsck/k9/Throttle.java
Normal file
178
src/com/fsck/k9/Throttle.java
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.fsck.k9;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.TimerTask;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class used to "throttle" a flow of events.
|
||||||
|
*
|
||||||
|
* When {@link #onEvent()} is called, it calls the callback in a certain timeout later.
|
||||||
|
* Initially {@link #mMinTimeout} is used as the timeout, but if it gets multiple {@link #onEvent}
|
||||||
|
* calls in a certain amount of time, it extends the timeout, until it reaches {@link #mMaxTimeout}.
|
||||||
|
*
|
||||||
|
* This class is primarily used to throttle content changed events.
|
||||||
|
*/
|
||||||
|
public class Throttle {
|
||||||
|
public static final boolean DEBUG = false; // Don't submit with true
|
||||||
|
|
||||||
|
public static final int DEFAULT_MIN_TIMEOUT = 150;
|
||||||
|
public static final int DEFAULT_MAX_TIMEOUT = 2500;
|
||||||
|
/* package */ static final int TIMEOUT_EXTEND_INTERVAL = 500;
|
||||||
|
|
||||||
|
private static Timer TIMER = new Timer();
|
||||||
|
|
||||||
|
private final Clock mClock;
|
||||||
|
private final Timer mTimer;
|
||||||
|
|
||||||
|
/** Name of the instance. Only for logging. */
|
||||||
|
private final String mName;
|
||||||
|
|
||||||
|
/** Handler for UI thread. */
|
||||||
|
private final Handler mHandler;
|
||||||
|
|
||||||
|
/** Callback to be called */
|
||||||
|
private final Runnable mCallback;
|
||||||
|
|
||||||
|
/** Minimum (default) timeout, in milliseconds. */
|
||||||
|
private final int mMinTimeout;
|
||||||
|
|
||||||
|
/** Max timeout, in milliseconds. */
|
||||||
|
private final int mMaxTimeout;
|
||||||
|
|
||||||
|
/** Current timeout, in milliseconds. */
|
||||||
|
private int mTimeout;
|
||||||
|
|
||||||
|
/** When {@link #onEvent()} was last called. */
|
||||||
|
private long mLastEventTime;
|
||||||
|
|
||||||
|
private MyTimerTask mRunningTimerTask;
|
||||||
|
|
||||||
|
/** Constructor with default timeout */
|
||||||
|
public Throttle(String name, Runnable callback, Handler handler) {
|
||||||
|
this(name, callback, handler, DEFAULT_MIN_TIMEOUT, DEFAULT_MAX_TIMEOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructor that takes custom timeout */
|
||||||
|
public Throttle(String name, Runnable callback, Handler handler,int minTimeout,
|
||||||
|
int maxTimeout) {
|
||||||
|
this(name, callback, handler, minTimeout, maxTimeout, Clock.INSTANCE, TIMER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constructor for tests */
|
||||||
|
/* package */ Throttle(String name, Runnable callback, Handler handler,int minTimeout,
|
||||||
|
int maxTimeout, Clock clock, Timer timer) {
|
||||||
|
if (maxTimeout < minTimeout) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
mName = name;
|
||||||
|
mCallback = callback;
|
||||||
|
mClock = clock;
|
||||||
|
mTimer = timer;
|
||||||
|
mHandler = handler;
|
||||||
|
mMinTimeout = minTimeout;
|
||||||
|
mMaxTimeout = maxTimeout;
|
||||||
|
mTimeout = mMinTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void debugLog(String message) {
|
||||||
|
Log.d(K9.LOG_TAG, "Throttle: [" + mName + "] " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCallbackScheduled() {
|
||||||
|
return mRunningTimerTask != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelScheduledCallback() {
|
||||||
|
if (mRunningTimerTask != null) {
|
||||||
|
if (DEBUG) debugLog("Canceling scheduled callback");
|
||||||
|
mRunningTimerTask.cancel();
|
||||||
|
mRunningTimerTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ void updateTimeout() {
|
||||||
|
final long now = mClock.getTime();
|
||||||
|
if ((now - mLastEventTime) <= TIMEOUT_EXTEND_INTERVAL) {
|
||||||
|
mTimeout *= 2;
|
||||||
|
if (mTimeout >= mMaxTimeout) {
|
||||||
|
mTimeout = mMaxTimeout;
|
||||||
|
}
|
||||||
|
if (DEBUG) debugLog("Timeout extended " + mTimeout);
|
||||||
|
} else {
|
||||||
|
mTimeout = mMinTimeout;
|
||||||
|
if (DEBUG) debugLog("Timeout reset to " + mTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
mLastEventTime = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEvent() {
|
||||||
|
if (DEBUG) debugLog("onEvent");
|
||||||
|
|
||||||
|
updateTimeout();
|
||||||
|
|
||||||
|
if (isCallbackScheduled()) {
|
||||||
|
if (DEBUG) debugLog(" callback already scheduled");
|
||||||
|
} else {
|
||||||
|
if (DEBUG) debugLog(" scheduling callback");
|
||||||
|
mRunningTimerTask = new MyTimerTask();
|
||||||
|
mTimer.schedule(mRunningTimerTask, mTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timer task called on timeout,
|
||||||
|
*/
|
||||||
|
private class MyTimerTask extends TimerTask {
|
||||||
|
private boolean mCanceled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mHandler.post(new HandlerRunnable());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean cancel() {
|
||||||
|
mCanceled = true;
|
||||||
|
return super.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HandlerRunnable implements Runnable {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mRunningTimerTask = null;
|
||||||
|
if (!mCanceled) { // This check has to be done on the UI thread.
|
||||||
|
if (DEBUG) debugLog("Kicking callback");
|
||||||
|
mCallback.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ int getTimeoutForTest() {
|
||||||
|
return mTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ long getLastEventTimeForTest() {
|
||||||
|
return mLastEventTime;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,8 @@ import android.database.Cursor;
|
|||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@ -65,6 +67,8 @@ public class Utility {
|
|||||||
private static final Pattern TAG_PATTERN = Pattern.compile("\\[[-_a-z0-9]+\\] ",
|
private static final Pattern TAG_PATTERN = Pattern.compile("\\[[-_a-z0-9]+\\] ",
|
||||||
Pattern.CASE_INSENSITIVE);
|
Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private static Handler sMainThreadHandler;
|
||||||
|
|
||||||
public static boolean arrayContains(Object[] a, Object o) {
|
public static boolean arrayContains(Object[] a, Object o) {
|
||||||
for (Object element : a) {
|
for (Object element : a) {
|
||||||
if (element.equals(o)) {
|
if (element.equals(o)) {
|
||||||
@ -714,4 +718,16 @@ public class Utility {
|
|||||||
|
|
||||||
return newArray;
|
return newArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a {@link Handler} tied to the main thread.
|
||||||
|
*/
|
||||||
|
public static Handler getMainThreadHandler() {
|
||||||
|
if (sMainThreadHandler == null) {
|
||||||
|
// No need to synchronize -- it's okay to create an extra Handler, which will be used
|
||||||
|
// only once and then thrown away.
|
||||||
|
sMainThreadHandler = new Handler(Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
return sMainThreadHandler;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@ import android.view.View;
|
|||||||
import android.view.View.OnClickListener;
|
import android.view.View.OnClickListener;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.QuickContactBadge;
|
import android.widget.QuickContactBadge;
|
||||||
import android.widget.ScrollView;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import com.fsck.k9.FontSizes;
|
import com.fsck.k9.FontSizes;
|
||||||
@ -40,7 +40,7 @@ import java.util.LinkedList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class MessageHeader extends ScrollView implements OnClickListener {
|
public class MessageHeader extends LinearLayout implements OnClickListener {
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private TextView mFromView;
|
private TextView mFromView;
|
||||||
private TextView mDateView;
|
private TextView mDateView;
|
||||||
|
@ -8,15 +8,16 @@ import android.util.AttributeSet;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.webkit.WebSettings;
|
import android.webkit.WebSettings;
|
||||||
|
import android.webkit.WebView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.helper.HtmlConverter;
|
import com.fsck.k9.helper.HtmlConverter;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import com.nobu_games.android.view.web.TitleBarWebView;
|
|
||||||
|
|
||||||
public class MessageWebView extends TitleBarWebView {
|
public class MessageWebView extends RigidWebView {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,9 +49,6 @@ public class MessageWebView extends TitleBarWebView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private int mOverrideScrollCounter;
|
|
||||||
|
|
||||||
|
|
||||||
public MessageWebView(Context context) {
|
public MessageWebView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@ -187,7 +185,6 @@ public class MessageWebView extends TitleBarWebView {
|
|||||||
content += HtmlConverter.cssStylePre();
|
content += HtmlConverter.cssStylePre();
|
||||||
content += "</head><body>" + text + "</body></html>";
|
content += "</head><body>" + text + "</body></html>";
|
||||||
loadDataWithBaseURL("http://", content, "text/html", "utf-8", null);
|
loadDataWithBaseURL("http://", content, "text/html", "utf-8", null);
|
||||||
mOverrideScrollCounter = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -206,25 +203,4 @@ public class MessageWebView extends TitleBarWebView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void scrollTo(int x, int y) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 16 && mOverrideScrollCounter < 3) {
|
|
||||||
/*
|
|
||||||
* 2013-03-12 - cketti
|
|
||||||
*
|
|
||||||
* WebView on Android 4.1+ automatically scrolls past the title view using this method.
|
|
||||||
* It looks like user-triggered scroll operations don't call this method. So we use
|
|
||||||
* it to override the initial scrolling past the title view.
|
|
||||||
*
|
|
||||||
* It's a dirty hack and we should find a better way to display the message header. When
|
|
||||||
* testing this I saw up to two calls to this method during initialization. To make
|
|
||||||
* sure we don't totally cripple the WebView when the implementation changes we only
|
|
||||||
* override the first three scrollTo() invocations.
|
|
||||||
*/
|
|
||||||
y = 0;
|
|
||||||
mOverrideScrollCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.scrollTo(x, y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
177
src/com/fsck/k9/view/NonLockingScrollView.java
Normal file
177
src/com/fsck/k9/view/NonLockingScrollView.java
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package com.fsck.k9.view;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewParent;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ScrollView} that will never lock scrolling in a particular direction.
|
||||||
|
*
|
||||||
|
* Usually ScrollView will capture all touch events once a drag has begun. In some cases,
|
||||||
|
* we want to delegate those touches to children as normal, even in the middle of a drag. This is
|
||||||
|
* useful when there are childviews like a WebView tha handles scrolling in the horizontal direction
|
||||||
|
* even while the ScrollView drags vertically.
|
||||||
|
*
|
||||||
|
* This is only tested to work for ScrollViews where the content scrolls in one direction.
|
||||||
|
*/
|
||||||
|
public class NonLockingScrollView extends ScrollView {
|
||||||
|
public NonLockingScrollView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
public NonLockingScrollView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public NonLockingScrollView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the contents of this view is being dragged by one of the children in
|
||||||
|
* {@link #mChildrenNeedingAllTouches}.
|
||||||
|
*/
|
||||||
|
private boolean mInCustomDrag = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of children who should always receive touch events, and not have them intercepted.
|
||||||
|
*/
|
||||||
|
private final ArrayList<View> mChildrenNeedingAllTouches = new ArrayList<View>();
|
||||||
|
|
||||||
|
private boolean mSkipWebViewScroll = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
final int action = getActionMasked(ev);
|
||||||
|
final boolean isUp = action == MotionEvent.ACTION_UP;
|
||||||
|
|
||||||
|
if (isUp && mInCustomDrag) {
|
||||||
|
// An up event after a drag should be intercepted so that child views don't handle
|
||||||
|
// click events falsely after a drag.
|
||||||
|
mInCustomDrag = false;
|
||||||
|
onTouchEvent(ev);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mInCustomDrag && !isEventOverChild(ev, mChildrenNeedingAllTouches)) {
|
||||||
|
return super.onInterceptTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note the normal scrollview implementation is to intercept all touch events after it has
|
||||||
|
// detected a drag starting. We will handle this ourselves.
|
||||||
|
mInCustomDrag = super.onInterceptTouchEvent(ev);
|
||||||
|
if (mInCustomDrag) {
|
||||||
|
onTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't intercept events - pass them on to children as normal.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getActionMasked(MotionEvent ev) {
|
||||||
|
// Equivalent to MotionEvent.getActionMasked() which is in API 8+
|
||||||
|
return ev.getAction() & MotionEvent.ACTION_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onFinishInflate() {
|
||||||
|
super.onFinishInflate();
|
||||||
|
excludeChildrenFromInterceptions(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Traverses the view tree for {@link WebView}s so they can be excluded from touch
|
||||||
|
* interceptions and receive all events.
|
||||||
|
*/
|
||||||
|
private void excludeChildrenFromInterceptions(View node) {
|
||||||
|
// If additional types of children should be excluded (e.g. horizontal scrolling banners),
|
||||||
|
// this needs to be modified accordingly.
|
||||||
|
if (node instanceof WebView) {
|
||||||
|
mChildrenNeedingAllTouches.add(node);
|
||||||
|
} else if (node instanceof ViewGroup) {
|
||||||
|
ViewGroup viewGroup = (ViewGroup) node;
|
||||||
|
final int childCount = viewGroup.getChildCount();
|
||||||
|
for (int i = 0; i < childCount; i++) {
|
||||||
|
final View child = viewGroup.getChildAt(i);
|
||||||
|
excludeChildrenFromInterceptions(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Rect sHitFrame = new Rect();
|
||||||
|
private boolean isEventOverChild(MotionEvent ev, ArrayList<View> children) {
|
||||||
|
final int actionIndex = getActionIndex(ev);
|
||||||
|
final float x = ev.getX(actionIndex) + getScrollX();
|
||||||
|
final float y = ev.getY(actionIndex) + getScrollY();
|
||||||
|
|
||||||
|
for (View child : children) {
|
||||||
|
if (!canViewReceivePointerEvents(child)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
child.getHitRect(sHitFrame);
|
||||||
|
|
||||||
|
// child can receive the motion event.
|
||||||
|
if (sHitFrame.contains((int) x, (int) y)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private static int getActionIndex(MotionEvent ev) {
|
||||||
|
// Equivalent to MotionEvent.getActionIndex() which is in API 8+
|
||||||
|
return ((ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
|
||||||
|
>> MotionEvent.ACTION_POINTER_INDEX_SHIFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean canViewReceivePointerEvents(View child) {
|
||||||
|
return child.getVisibility() == VISIBLE || (child.getAnimation() != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestChildFocus(View child, View focused) {
|
||||||
|
/*
|
||||||
|
* Normally a ScrollView will scroll the child into view.
|
||||||
|
* Prevent this when a MessageWebView is first touched,
|
||||||
|
* assuming it already is at least partially in view.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
if (mSkipWebViewScroll &&
|
||||||
|
focused instanceof MessageWebView &&
|
||||||
|
focused.getGlobalVisibleRect(new Rect())) {
|
||||||
|
mSkipWebViewScroll = false;
|
||||||
|
super.requestChildFocus(child, child);
|
||||||
|
ViewParent parent = getParent();
|
||||||
|
if (parent != null) {
|
||||||
|
parent.requestChildFocus(this, focused);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
super.requestChildFocus(child, focused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
src/com/fsck/k9/view/RigidWebView.java
Normal file
102
src/com/fsck/k9/view/RigidWebView.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2011 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package com.fsck.k9.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.WebView;
|
||||||
|
|
||||||
|
import com.fsck.k9.Clock;
|
||||||
|
import com.fsck.k9.K9;
|
||||||
|
import com.fsck.k9.Throttle;
|
||||||
|
import com.fsck.k9.helper.Utility;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom WebView that is robust to rapid resize events in sequence.
|
||||||
|
*
|
||||||
|
* This is useful for a WebView which needs to have a layout of {@code WRAP_CONTENT}, since any
|
||||||
|
* contents with percent-based height will force the WebView to infinitely expand (or shrink).
|
||||||
|
*/
|
||||||
|
public class RigidWebView extends WebView {
|
||||||
|
|
||||||
|
public RigidWebView(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
public RigidWebView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public RigidWebView(Context context, AttributeSet attrs, int defStyle) {
|
||||||
|
super(context, attrs, defStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MIN_RESIZE_INTERVAL = 200;
|
||||||
|
private static final int MAX_RESIZE_INTERVAL = 300;
|
||||||
|
private final Clock mClock = Clock.INSTANCE;
|
||||||
|
|
||||||
|
private final Throttle mThrottle = new Throttle(getClass().getName(),
|
||||||
|
new Runnable() {
|
||||||
|
@Override public void run() {
|
||||||
|
performSizeChangeDelayed();
|
||||||
|
}
|
||||||
|
}, Utility.getMainThreadHandler(),
|
||||||
|
MIN_RESIZE_INTERVAL, MAX_RESIZE_INTERVAL);
|
||||||
|
|
||||||
|
private int mRealWidth;
|
||||||
|
private int mRealHeight;
|
||||||
|
private boolean mIgnoreNext;
|
||||||
|
private long mLastSizeChangeTime = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onSizeChanged(int w, int h, int ow, int oh) {
|
||||||
|
mRealWidth = w;
|
||||||
|
mRealHeight = h;
|
||||||
|
long now = mClock.getTime();
|
||||||
|
boolean recentlySized = (now - mLastSizeChangeTime < MIN_RESIZE_INTERVAL);
|
||||||
|
|
||||||
|
// It's known that the previous resize event may cause a resize event immediately. If
|
||||||
|
// this happens sufficiently close to the last resize event, drop it on the floor.
|
||||||
|
if (mIgnoreNext) {
|
||||||
|
mIgnoreNext = false;
|
||||||
|
if (recentlySized) {
|
||||||
|
if (K9.DEBUG) {
|
||||||
|
Log.w(K9.LOG_TAG, "Supressing size change in RigidWebView");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recentlySized) {
|
||||||
|
mThrottle.onEvent();
|
||||||
|
} else {
|
||||||
|
// It's been a sufficiently long time - just perform the resize as normal. This should
|
||||||
|
// be the normal code path.
|
||||||
|
performSizeChange(ow, oh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performSizeChange(int ow, int oh) {
|
||||||
|
super.onSizeChanged(mRealWidth, mRealHeight, ow, oh);
|
||||||
|
mLastSizeChangeTime = mClock.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void performSizeChangeDelayed() {
|
||||||
|
mIgnoreNext = true;
|
||||||
|
performSizeChange(getWidth(), getHeight());
|
||||||
|
}
|
||||||
|
}
|
@ -101,10 +101,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
private LayoutInflater mInflater;
|
private LayoutInflater mInflater;
|
||||||
private Contacts mContacts;
|
private Contacts mContacts;
|
||||||
private AttachmentView.AttachmentFileDownloadCallback attachmentCallback;
|
private AttachmentView.AttachmentFileDownloadCallback attachmentCallback;
|
||||||
private LinearLayout mHeaderPlaceHolder;
|
|
||||||
private LinearLayout mTitleBarHeaderContainer;
|
|
||||||
private View mAttachmentsContainer;
|
private View mAttachmentsContainer;
|
||||||
private LinearLayout mInsideAttachmentsContainer;
|
|
||||||
private SavedState mSavedState;
|
private SavedState mSavedState;
|
||||||
private ClipboardManager mClipboardManager;
|
private ClipboardManager mClipboardManager;
|
||||||
private String mText;
|
private String mText;
|
||||||
@ -118,13 +115,10 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
activity.registerForContextMenu(mMessageContentView);
|
activity.registerForContextMenu(mMessageContentView);
|
||||||
mMessageContentView.setOnCreateContextMenuListener(this);
|
mMessageContentView.setOnCreateContextMenuListener(this);
|
||||||
|
|
||||||
mHeaderPlaceHolder = (LinearLayout) findViewById(R.id.message_view_header_container);
|
|
||||||
|
|
||||||
mHeaderContainer = (MessageHeader) findViewById(R.id.header_container);
|
mHeaderContainer = (MessageHeader) findViewById(R.id.header_container);
|
||||||
mHeaderContainer.setOnLayoutChangedListener(this);
|
mHeaderContainer.setOnLayoutChangedListener(this);
|
||||||
|
|
||||||
mAttachmentsContainer = findViewById(R.id.attachments_container);
|
mAttachmentsContainer = findViewById(R.id.attachments_container);
|
||||||
mInsideAttachmentsContainer = (LinearLayout) findViewById(R.id.inside_attachments_container);
|
|
||||||
mAttachments = (LinearLayout) findViewById(R.id.attachments);
|
mAttachments = (LinearLayout) findViewById(R.id.attachments);
|
||||||
mHiddenAttachments = (LinearLayout) findViewById(R.id.hidden_attachments);
|
mHiddenAttachments = (LinearLayout) findViewById(R.id.hidden_attachments);
|
||||||
mHiddenAttachments.setVisibility(View.GONE);
|
mHiddenAttachments.setVisibility(View.GONE);
|
||||||
@ -157,7 +151,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
mMessageContentView.setVisibility(View.VISIBLE);
|
mMessageContentView.setVisibility(View.VISIBLE);
|
||||||
mScreenReaderEnabled = false;
|
mScreenReaderEnabled = false;
|
||||||
|
|
||||||
mHeaderPlaceHolder.removeView(mHeaderContainer);
|
|
||||||
// the HTC version of WebView tries to force the background of the
|
// the HTC version of WebView tries to force the background of the
|
||||||
// titlebar, which is really unfair.
|
// titlebar, which is really unfair.
|
||||||
TypedValue outValue = new TypedValue();
|
TypedValue outValue = new TypedValue();
|
||||||
@ -165,10 +158,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
mHeaderContainer.setBackgroundColor(outValue.data);
|
mHeaderContainer.setBackgroundColor(outValue.data);
|
||||||
// also set background of the whole view (including the attachments view)
|
// also set background of the whole view (including the attachments view)
|
||||||
setBackgroundColor(outValue.data);
|
setBackgroundColor(outValue.data);
|
||||||
|
|
||||||
mTitleBarHeaderContainer = new LinearLayout(activity);
|
|
||||||
mMessageContentView.setEmbeddedTitleBarCompat(mTitleBarHeaderContainer);
|
|
||||||
mTitleBarHeaderContainer.addView(mHeaderContainer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mShowHiddenAttachments.setOnClickListener(this);
|
mShowHiddenAttachments.setOnClickListener(this);
|
||||||
@ -647,12 +636,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
boolean showHidden = (show && mHiddenAttachments.getVisibility() == View.GONE &&
|
boolean showHidden = (show && mHiddenAttachments.getVisibility() == View.GONE &&
|
||||||
mHiddenAttachments.getChildCount() > 0);
|
mHiddenAttachments.getChildCount() > 0);
|
||||||
mShowHiddenAttachments.setVisibility(showHidden ? View.VISIBLE : View.GONE);
|
mShowHiddenAttachments.setVisibility(showHidden ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (show) {
|
|
||||||
moveHeaderToLayout();
|
|
||||||
} else {
|
|
||||||
moveHeaderToWebViewTitleBar();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showMessageWebView(boolean show) {
|
public void showMessageWebView(boolean show) {
|
||||||
@ -753,20 +736,6 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
this.attachmentCallback = attachmentCallback;
|
this.attachmentCallback = attachmentCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void moveHeaderToLayout() {
|
|
||||||
if (mTitleBarHeaderContainer != null && mTitleBarHeaderContainer.getChildCount() != 0) {
|
|
||||||
mTitleBarHeaderContainer.removeView(mHeaderContainer);
|
|
||||||
mInsideAttachmentsContainer.addView(mHeaderContainer, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void moveHeaderToWebViewTitleBar() {
|
|
||||||
if (mTitleBarHeaderContainer != null && mTitleBarHeaderContainer.getChildCount() == 0) {
|
|
||||||
mInsideAttachmentsContainer.removeView(mHeaderContainer);
|
|
||||||
mTitleBarHeaderContainer.addView(mHeaderContainer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Parcelable onSaveInstanceState() {
|
public Parcelable onSaveInstanceState() {
|
||||||
Parcelable superState = super.onSaveInstanceState();
|
Parcelable superState = super.onSaveInstanceState();
|
||||||
|
@ -1,367 +0,0 @@
|
|||||||
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.
|
|
||||||
* <p>
|
|
||||||
* <b>Usage</b>
|
|
||||||
* <p>
|
|
||||||
* 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 static boolean checkedForNativeGetVisibleTitleHeightMethod = false;
|
|
||||||
private static Method nativeGetVisibleTitleHeightMethod;
|
|
||||||
private static Method nativeSetEmbeddedTitleBarMethod;
|
|
||||||
private static boolean checkedForNativeSetEmbeddedTitleBarMethod = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <i>Corrects the visual displacement caused by the title bar view.</i>
|
|
||||||
* <p>
|
|
||||||
* {@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.
|
|
||||||
* <p>
|
|
||||||
* 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) {
|
|
||||||
if(checkedForNativeSetEmbeddedTitleBarMethod && nativeSetEmbeddedTitleBarMethod == null) {
|
|
||||||
setEmbeddedTitleBarJellyBean(v);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
|
|
||||||
if (nativeSetEmbeddedTitleBarMethod == null) {
|
|
||||||
nativeSetEmbeddedTitleBarMethod = getClass().getMethod("setEmbeddedTitleBar",
|
|
||||||
View.class);
|
|
||||||
checkedForNativeSetEmbeddedTitleBarMethod = true;
|
|
||||||
}
|
|
||||||
nativeSetEmbeddedTitleBarMethod.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 && nativeGetVisibleTitleHeightMethod != null) {
|
|
||||||
try {
|
|
||||||
return (Integer) nativeGetVisibleTitleHeightMethod
|
|
||||||
.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.
|
|
||||||
* <p>
|
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 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.
|
|
||||||
* <br>
|
|
||||||
* 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.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
@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() {
|
|
||||||
if (checkedForNativeGetVisibleTitleHeightMethod == false) {
|
|
||||||
try {
|
|
||||||
nativeGetVisibleTitleHeightMethod = WebView.class
|
|
||||||
.getDeclaredMethod("getVisibleTitleHeight");
|
|
||||||
} catch(NoSuchMethodException e) {
|
|
||||||
Log.w(TAG,
|
|
||||||
"Could not retrieve native hidden getVisibleTitleHeight method");
|
|
||||||
}
|
|
||||||
checkedForNativeGetVisibleTitleHeightMethod = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 */ }
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user