Anthony Restaino
9 years ago
48 changed files with 4523 additions and 1834 deletions
@ -1,139 +0,0 @@
@@ -1,139 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<module external.linked.project.id=":app" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" external.system.module.group="Lightning-Browser" external.system.module.version="unspecified" type="JAVA_MODULE" version="4"> |
||||
<component name="FacetManager"> |
||||
<facet type="android-gradle" name="Android-Gradle"> |
||||
<configuration> |
||||
<option name="GRADLE_PROJECT_PATH" value=":app" /> |
||||
</configuration> |
||||
</facet> |
||||
<facet type="android" name="Android"> |
||||
<configuration> |
||||
<option name="SELECTED_BUILD_VARIANT" value="lightningPlusDebug" /> |
||||
<option name="SELECTED_TEST_ARTIFACT" value="_android_test_" /> |
||||
<option name="ASSEMBLE_TASK_NAME" value="assembleLightningPlusDebug" /> |
||||
<option name="COMPILE_JAVA_TASK_NAME" value="compileLightningPlusDebugSources" /> |
||||
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleLightningPlusDebugAndroidTest" /> |
||||
<option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileLightningPlusDebugAndroidTestSources" /> |
||||
<afterSyncTasks> |
||||
<task>generateLightningPlusDebugAndroidTestSources</task> |
||||
<task>generateLightningPlusDebugSources</task> |
||||
</afterSyncTasks> |
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" /> |
||||
<option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> |
||||
<option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> |
||||
<option name="RES_FOLDERS_RELATIVE_PATH" value="file://$MODULE_DIR$/src/main/res" /> |
||||
<option name="ASSETS_FOLDER_RELATIVE_PATH" value="/src/main/assets" /> |
||||
</configuration> |
||||
</facet> |
||||
</component> |
||||
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false"> |
||||
<output url="file://$MODULE_DIR$/build/intermediates/classes/lightningPlus/debug" /> |
||||
<output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/lightningPlus/debug" /> |
||||
<exclude-output /> |
||||
<content url="file://$MODULE_DIR$"> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/apt/lightningPlus/debug" isTestSource="false" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/lightningPlus/debug" isTestSource="false" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/lightningPlus/debug" isTestSource="false" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/lightningPlus/debug" isTestSource="false" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/lightningPlus/debug" isTestSource="false" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/lightningPlus/debug" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/lightningPlus/debug" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/res" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/resources" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/assets" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/aidl" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/java" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/jni" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/lightningPlusDebug/rs" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/lightningPlus/debug" isTestSource="true" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/lightningPlus/debug" isTestSource="true" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/lightningPlus/debug" isTestSource="true" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/lightningPlus/debug" isTestSource="true" generated="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/lightningPlus/debug" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/lightningPlus/debug" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/res" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/resources" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/assets" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/aidl" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/java" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/jni" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/LightningPlus/rs" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/res" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/resources" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/assets" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/aidl" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/java" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/jni" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTestLightningPlus/rs" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/res" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/resources" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/assets" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/aidl" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/java" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/jni" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/debug/rs" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/res" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/assets" type="java-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/aidl" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/jni" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/jni" isTestSource="true" /> |
||||
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/assets" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/classes" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/coverage-instrumented-classes" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.1/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.1/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/palette-v7/23.0.1/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/recyclerview-v7/23.0.1/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.1/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.squareup.leakcanary/leakcanary-android/1.3.1/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/net.i2p.android/client/0.7/jars" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/libs" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/lint" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/manifests" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/ndk" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/pre-dexed" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/proguard" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/res" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/rs" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/symbols" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/outputs" /> |
||||
<excludeFolder url="file://$MODULE_DIR$/build/tmp" /> |
||||
</content> |
||||
<orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> |
||||
<orderEntry type="sourceFolder" forTests="false" /> |
||||
<orderEntry type="library" exported="" name="client-0.7" level="project" /> |
||||
<orderEntry type="library" exported="" name="leakcanary-analyzer-1.3.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="jsr250-api-1.0" level="project" /> |
||||
<orderEntry type="library" exported="" name="butterknife-7.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="dagger-2.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="leakcanary-watcher-1.3.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="otto-1.3.8" level="project" /> |
||||
<orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="haha-1.3" level="project" /> |
||||
<orderEntry type="library" exported="" name="palette-v7-23.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="design-23.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="javax.inject-1" level="project" /> |
||||
<orderEntry type="library" exported="" name="jsoup-1.8.3" level="project" /> |
||||
<orderEntry type="library" exported="" name="leakcanary-android-1.3.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="recyclerview-v7-23.0.1" level="project" /> |
||||
<orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" /> |
||||
<orderEntry type="module" module-name="libnetcipher" exported="" /> |
||||
</component> |
||||
</module> |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,222 @@
@@ -0,0 +1,222 @@
|
||||
package acr.browser.lightning.activity; |
||||
|
||||
import android.app.Activity; |
||||
import android.content.Context; |
||||
import android.support.annotation.Nullable; |
||||
import android.webkit.WebView; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
import javax.inject.Inject; |
||||
import javax.inject.Singleton; |
||||
|
||||
import acr.browser.lightning.controller.BrowserController; |
||||
import acr.browser.lightning.preference.PreferenceManager; |
||||
import acr.browser.lightning.utils.Utils; |
||||
import acr.browser.lightning.view.LightningView; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/14 |
||||
*/ |
||||
@Singleton |
||||
public class TabsManager { |
||||
|
||||
private final List<LightningView> mWebViewList = new ArrayList<>(); |
||||
private LightningView mCurrentTab; |
||||
|
||||
@Inject |
||||
PreferenceManager mPreferenceManager; |
||||
|
||||
@Inject |
||||
public TabsManager() { |
||||
} |
||||
|
||||
public void restoreTabs(BrowserActivity activity, boolean darkTheme, boolean incognito) { |
||||
mWebViewList.clear(); |
||||
mCurrentTab = null; |
||||
if (!incognito && mPreferenceManager.getRestoreLostTabsEnabled()) { |
||||
final String mem = mPreferenceManager.getMemoryUrl(); |
||||
mPreferenceManager.setMemoryUrl(""); |
||||
String[] array = Utils.getArray(mem); |
||||
int count = 0; |
||||
for (String urlString : array) { |
||||
if (!urlString.isEmpty()) { |
||||
newTab(activity, urlString, darkTheme, incognito); |
||||
} |
||||
} |
||||
} |
||||
if (mWebViewList.size() == 0) { |
||||
newTab(activity, null, darkTheme, incognito); |
||||
} |
||||
// mCurrentTab = mWebViewList.get(0);
|
||||
} |
||||
|
||||
/** |
||||
* Return a clone of the current tabs list. The list will not be updated, the user has to fetch |
||||
* a new copy when notified. |
||||
* |
||||
* @return a copy of the current tabs list |
||||
*/ |
||||
public List<LightningView> getTabsList() { |
||||
return new ArrayList<>(mWebViewList); |
||||
} |
||||
|
||||
/** |
||||
* Return the tab at the given position in tabs list, or null if position is not in tabs list |
||||
* range. |
||||
* |
||||
* @param position the index in tabs list |
||||
* @return the corespondent {@link LightningView}, or null if the index is invalid |
||||
*/ |
||||
@Nullable |
||||
public LightningView getTabAtPosition(final int position) { |
||||
if (position < 0 || position >= mWebViewList.size()) { |
||||
return null; |
||||
} |
||||
|
||||
return mWebViewList.get(position); |
||||
} |
||||
|
||||
/** |
||||
* Try to low memory pressure |
||||
*/ |
||||
public void freeMemory() { |
||||
for (LightningView tab: mWebViewList) { |
||||
tab.freeMemory(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Shutdown the manager |
||||
*/ |
||||
public synchronized void shutdown() { |
||||
for (LightningView tab: mWebViewList) { |
||||
tab.onDestroy(); |
||||
} |
||||
mWebViewList.clear(); |
||||
} |
||||
|
||||
/** |
||||
* Resume the tabs |
||||
* |
||||
* @param context |
||||
*/ |
||||
public synchronized void resume(final Context context) { |
||||
for (LightningView tab: mWebViewList) { |
||||
tab.initializePreferences(null, context); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Forward network connection status to the webviews. |
||||
* |
||||
* @param isConnected |
||||
*/ |
||||
public synchronized void notifyConnectioneStatus(final boolean isConnected) { |
||||
for (LightningView tab: mWebViewList) { |
||||
final WebView webView = tab.getWebView(); |
||||
if (webView != null) { |
||||
webView.setNetworkAvailable(isConnected); |
||||
} |
||||
} |
||||
} |
||||
/** |
||||
* @return The number of currently opened tabs |
||||
*/ |
||||
public int size() { |
||||
return mWebViewList.size(); |
||||
} |
||||
|
||||
/** |
||||
* Create and return a new tab. The tab is automatically added to the tabs list. |
||||
* |
||||
* @param activity |
||||
* @param url |
||||
* @param darkTheme |
||||
* @param isIncognito |
||||
* @return |
||||
*/ |
||||
public synchronized LightningView newTab(final BrowserActivity activity, |
||||
final String url, final boolean darkTheme, |
||||
final boolean isIncognito) { |
||||
final LightningView tab = new LightningView(activity, url, darkTheme, isIncognito); |
||||
mWebViewList.add(tab); |
||||
return tab; |
||||
} |
||||
|
||||
/** |
||||
* Remove a tab and return its reference or null if the position is not in tabs range |
||||
* |
||||
* @param position The position of the tab to remove |
||||
* @return The removed tab reference or null |
||||
*/ |
||||
@Nullable |
||||
public synchronized LightningView deleteTab(final int position) { |
||||
if (position >= mWebViewList.size()) { |
||||
return null; |
||||
} |
||||
final LightningView tab = mWebViewList.remove(position); |
||||
tab.onDestroy(); |
||||
return tab; |
||||
} |
||||
|
||||
/** |
||||
* Return the position of the given tab |
||||
* @param tab |
||||
* @return |
||||
*/ |
||||
public int positionOf(final LightningView tab) { |
||||
return mWebViewList.indexOf(tab); |
||||
} |
||||
|
||||
/** |
||||
* @return A string representation of the currently opened tabs |
||||
*/ |
||||
public String tabsString() { |
||||
final StringBuilder builder = new StringBuilder(); |
||||
for (LightningView tab: mWebViewList) { |
||||
final String url = tab.getUrl(); |
||||
if (url != null && !url.isEmpty()) { |
||||
builder.append(url).append("|$|SEPARATOR|$|"); |
||||
} |
||||
} |
||||
return builder.toString(); |
||||
} |
||||
|
||||
/** |
||||
* Return the {@link WebView} associated to the current tab, or null if there is no current tab |
||||
* @return a {@link WebView} or null |
||||
*/ |
||||
@Nullable |
||||
public WebView getCurrentWebView() { |
||||
return mCurrentTab != null ? mCurrentTab.getWebView() : null; |
||||
} |
||||
|
||||
/** |
||||
* TODO We should remove also this, but probably not |
||||
* @return |
||||
*/ |
||||
public LightningView getCurrentTab() { |
||||
return mCurrentTab; |
||||
} |
||||
|
||||
/** |
||||
* Switch the current tab to the one at the given position. It returns the selected. After this |
||||
* call {@link TabsManager#getCurrentTab()} return the same reference returned by this method if |
||||
* position is valid. |
||||
* |
||||
* @return the selected tab or null if position is out of tabs range |
||||
*/ |
||||
@Nullable |
||||
public LightningView switchToTab(final int position) { |
||||
if (position < 0 || position >= mWebViewList.size()) { |
||||
return null; |
||||
} else { |
||||
final LightningView tab = mWebViewList.get(position); |
||||
mCurrentTab = tab; |
||||
return tab; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
package acr.browser.lightning.bus; |
||||
|
||||
/** |
||||
* Collections of navigation events, like go back or go forward |
||||
* |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/15 |
||||
*/ |
||||
public class NavigationEvents { |
||||
private NavigationEvents() { |
||||
// No instances please
|
||||
} |
||||
|
||||
/** |
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses back |
||||
* button. |
||||
*/ |
||||
public static class GoBack { |
||||
} |
||||
|
||||
/** |
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses forward |
||||
* button. |
||||
*/ |
||||
public static class GoForward { |
||||
} |
||||
|
||||
/** |
||||
* Fired by {@link acr.browser.lightning.fragment.TabsFragment} when the user presses the home |
||||
* button. |
||||
*/ |
||||
public static class GoHome { |
||||
} |
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
package acr.browser.lightning.bus; |
||||
|
||||
/** |
||||
* A collection of events been sent by {@link acr.browser.lightning.fragment.TabsFragment} |
||||
* |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/14 |
||||
*/ |
||||
public final class TabEvents { |
||||
|
||||
private TabEvents() { |
||||
// No instances
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the |
||||
* tab exit button |
||||
*/ |
||||
public static class CloseTab { |
||||
public final int position; |
||||
|
||||
public CloseTab(int position) { |
||||
this.position = position; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user click on the |
||||
* tab itself. |
||||
*/ |
||||
public static class ShowTab { |
||||
public final int position; |
||||
|
||||
public ShowTab(int position) { |
||||
this.position = position; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long press on the |
||||
* tab itself. |
||||
*/ |
||||
public static class ShowCloseDialog { |
||||
public final int position; |
||||
|
||||
public ShowCloseDialog(int position) { |
||||
this.position = position; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user want to create a |
||||
* new tab. |
||||
*/ |
||||
public static class NewTab { |
||||
} |
||||
|
||||
/** |
||||
* Sended by {@link acr.browser.lightning.fragment.TabsFragment} when the user long presses on |
||||
* new tab button. |
||||
*/ |
||||
public static class NewTabLongPress { |
||||
} |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
package acr.browser.lightning.fragment; |
||||
|
||||
import android.os.Bundle; |
||||
import android.preference.PreferenceFragment; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.preference.PreferenceManager; |
||||
|
||||
/** |
||||
* Simplify {@link PreferenceManager} inject in all the PreferenceFragments |
||||
* |
||||
* @author Stefano Pacifici |
||||
* @date 2015/09/16 |
||||
*/ |
||||
public class LightningPreferenceFragment extends PreferenceFragment { |
||||
|
||||
@Inject |
||||
PreferenceManager mPreferenceManager; |
||||
|
||||
@Override |
||||
public void onCreate(Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
BrowserApp.getAppComponent().inject(this); |
||||
} |
||||
} |
@ -0,0 +1,417 @@
@@ -0,0 +1,417 @@
|
||||
package acr.browser.lightning.fragment; |
||||
|
||||
import android.animation.ArgbEvaluator; |
||||
import android.animation.ValueAnimator; |
||||
import android.content.Context; |
||||
import android.content.res.Resources; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.Color; |
||||
import android.graphics.ColorFilter; |
||||
import android.graphics.ColorMatrix; |
||||
import android.graphics.ColorMatrixColorFilter; |
||||
import android.graphics.Paint; |
||||
import android.graphics.PorterDuff; |
||||
import android.graphics.drawable.BitmapDrawable; |
||||
import android.graphics.drawable.ColorDrawable; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Build; |
||||
import android.os.Bundle; |
||||
import android.support.annotation.IdRes; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.v4.app.Fragment; |
||||
import android.support.v4.view.ViewCompat; |
||||
import android.support.v7.graphics.Palette; |
||||
import android.support.v7.widget.LinearLayoutManager; |
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.support.v7.widget.RecyclerView.LayoutManager; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.view.Window; |
||||
import android.widget.FrameLayout; |
||||
import android.widget.ImageView; |
||||
import android.widget.LinearLayout; |
||||
import android.widget.TextView; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
import com.squareup.otto.Subscribe; |
||||
|
||||
import java.util.List; |
||||
|
||||
import javax.inject.Inject; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.activity.TabsManager; |
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.bus.NavigationEvents; |
||||
import acr.browser.lightning.bus.TabEvents; |
||||
import acr.browser.lightning.preference.PreferenceManager; |
||||
import acr.browser.lightning.utils.ThemeUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
import acr.browser.lightning.view.LightningView; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici based on Anthony C. Restaino's code |
||||
* @date 2015/09/14 |
||||
*/ |
||||
public class TabsFragment extends Fragment implements View.OnClickListener, View.OnLongClickListener { |
||||
|
||||
private static final String TAG = TabsFragment.class.getSimpleName(); |
||||
|
||||
/** |
||||
* Arguments boolean to tell the fragment it is displayed in the drawner or on the tab strip |
||||
* If true, the fragment is in the left drawner in the strip otherwise. |
||||
*/ |
||||
public static final String VERTICAL_MODE = TAG + ".VERTICAL_MODE"; |
||||
public static final String IS_INCOGNITO = TAG + ".IS_INCOGNITO"; |
||||
|
||||
private boolean mIsIncognito, mDarkTheme; |
||||
private int mIconColor; |
||||
private boolean mColorMode = true; |
||||
private boolean mShowInNavigationDrawer; |
||||
private int mCurrentUiColor = Color.BLACK; // TODO Only temporary
|
||||
|
||||
private RecyclerView mRecyclerView; |
||||
private LightningViewAdapter mTabsAdapter; |
||||
|
||||
@Inject |
||||
TabsManager tabsManager; |
||||
|
||||
@Inject |
||||
Bus bus; |
||||
|
||||
@Inject |
||||
PreferenceManager mPreferences; |
||||
|
||||
public TabsFragment() { |
||||
BrowserApp.getAppComponent().inject(this); |
||||
} |
||||
|
||||
@Override |
||||
public void onCreate(@Nullable Bundle savedInstanceState) { |
||||
super.onCreate(savedInstanceState); |
||||
final Bundle arguments = getArguments(); |
||||
final Context context = getContext(); |
||||
mIsIncognito = arguments.getBoolean(IS_INCOGNITO, false); |
||||
mShowInNavigationDrawer = arguments.getBoolean(VERTICAL_MODE, true); |
||||
mDarkTheme = mPreferences.getUseTheme() != 0 || mIsIncognito; |
||||
mColorMode = mPreferences.getColorModeEnabled(); |
||||
mColorMode &= !mDarkTheme; |
||||
mIconColor = mDarkTheme ? |
||||
ThemeUtils.getIconDarkThemeColor(context) : |
||||
ThemeUtils.getIconLightThemeColor(context); |
||||
} |
||||
|
||||
@Nullable |
||||
@Override |
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { |
||||
final Bundle arguments = getArguments(); |
||||
final View view; |
||||
final LayoutManager layoutManager; |
||||
if (mShowInNavigationDrawer) { |
||||
view = inflater.inflate(R.layout.tab_drawer, container, false); |
||||
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); |
||||
setupFrameLayoutButton(view, R.id.new_tab_button, R.id.icon_plus); |
||||
setupFrameLayoutButton(view, R.id.action_back, R.id.icon_back); |
||||
setupFrameLayoutButton(view, R.id.action_forward, R.id.icon_forward); |
||||
setupFrameLayoutButton(view, R.id.action_home, R.id.icon_home); |
||||
} else { |
||||
view = inflater.inflate(R.layout.tab_strip, container, false); |
||||
layoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false); |
||||
} |
||||
mRecyclerView = (RecyclerView) view.findViewById(R.id.tabs_list); |
||||
mRecyclerView.setLayoutManager(layoutManager); |
||||
mTabsAdapter = new LightningViewAdapter(mShowInNavigationDrawer); |
||||
mRecyclerView.setAdapter(mTabsAdapter); |
||||
mRecyclerView.setHasFixedSize(true); |
||||
return view; |
||||
} |
||||
|
||||
private void setupFrameLayoutButton(@NonNull final View root, @IdRes final int buttonId, |
||||
@IdRes final int imageId) { |
||||
final View frameButton = root.findViewById(buttonId); |
||||
final ImageView buttonImage = (ImageView) root.findViewById(imageId); |
||||
frameButton.setOnClickListener(this); |
||||
frameButton.setOnLongClickListener(this); |
||||
buttonImage.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
||||
} |
||||
|
||||
@Override |
||||
public void onDestroyView() { |
||||
super.onDestroyView(); |
||||
mRecyclerView = null; |
||||
mTabsAdapter = null; |
||||
} |
||||
|
||||
@Override |
||||
public void onStart() { |
||||
super.onStart(); |
||||
bus.register(this); |
||||
} |
||||
|
||||
@Override |
||||
public void onResume() { |
||||
super.onResume(); |
||||
// Force adapter refresh
|
||||
mTabsAdapter.notifyDataSetChanged(); |
||||
} |
||||
|
||||
@Override |
||||
public void onStop() { |
||||
super.onStop(); |
||||
bus.unregister(this); |
||||
} |
||||
|
||||
@Subscribe |
||||
public void tabsChanged(final BrowserEvents.TabsChanged event) { |
||||
if (mTabsAdapter != null) { |
||||
mTabsAdapter.notifyDataSetChanged(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onClick(View v) { |
||||
switch (v.getId()) { |
||||
case R.id.new_tab_button: |
||||
bus.post(new TabEvents.NewTab()); |
||||
break; |
||||
case R.id.action_back: |
||||
bus.post(new NavigationEvents.GoBack()); |
||||
break; |
||||
case R.id.action_forward: |
||||
bus.post(new NavigationEvents.GoForward()); |
||||
break; |
||||
case R.id.action_home: |
||||
bus.post(new NavigationEvents.GoHome()); |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean onLongClick(View v) { |
||||
switch (v.getId()) { |
||||
case R.id.action_new_tab: |
||||
bus.post(new TabEvents.NewTabLongPress()); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
public class LightningViewAdapter extends RecyclerView.Adapter<LightningViewAdapter.LightningViewHolder> { |
||||
|
||||
private final int layoutResourceId; |
||||
private final Drawable mBackgroundTabDrawable; |
||||
private final Drawable mForegroundTabDrawable; |
||||
private final Bitmap mForegroundTabBitmap; |
||||
private ColorMatrix mColorMatrix; |
||||
private Paint mPaint; |
||||
private ColorFilter mFilter; |
||||
private static final float DESATURATED = 0.5f; |
||||
|
||||
private final boolean vertical; |
||||
|
||||
public LightningViewAdapter(final boolean vertical) { |
||||
this.layoutResourceId = vertical ? R.layout.tab_list_item : R.layout.tab_list_item_horizontal; |
||||
this.vertical = vertical; |
||||
|
||||
if (vertical) { |
||||
mBackgroundTabDrawable = null; |
||||
mForegroundTabBitmap = null; |
||||
mForegroundTabDrawable = ThemeUtils.getSelectedBackground(getContext(), mDarkTheme); |
||||
} else { |
||||
int backgroundColor = Utils.mixTwoColors(ThemeUtils.getPrimaryColor(getContext()), Color.BLACK, 0.75f); |
||||
Bitmap backgroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); |
||||
Utils.drawTrapezoid(new Canvas(backgroundTabBitmap), backgroundColor, true); |
||||
mBackgroundTabDrawable = new BitmapDrawable(getResources(), backgroundTabBitmap); |
||||
|
||||
int foregroundColor = ThemeUtils.getPrimaryColor(getContext()); |
||||
mForegroundTabBitmap = Bitmap.createBitmap(Utils.dpToPx(175), Utils.dpToPx(30), Bitmap.Config.ARGB_8888); |
||||
Utils.drawTrapezoid(new Canvas(mForegroundTabBitmap), foregroundColor, false); |
||||
mForegroundTabDrawable = null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public LightningViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { |
||||
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); |
||||
View view = inflater.inflate(layoutResourceId, viewGroup, false); |
||||
return new LightningViewHolder(view); |
||||
} |
||||
|
||||
@Override |
||||
public void onBindViewHolder(final LightningViewHolder holder, int position) { |
||||
holder.exitButton.setTag(position); |
||||
|
||||
ViewCompat.jumpDrawablesToCurrentState(holder.exitButton); |
||||
|
||||
LightningView web = tabsManager.getTabAtPosition(position); |
||||
holder.txtTitle.setText(web.getTitle()); |
||||
|
||||
final Bitmap favicon = web.getFavicon(); |
||||
if (web.isForegroundTab()) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
holder.txtTitle.setTextAppearance(R.style.boldText); |
||||
} else { |
||||
holder.txtTitle.setTextAppearance(getContext(), R.style.boldText); |
||||
} |
||||
Drawable foregroundDrawable; |
||||
if (!vertical) { |
||||
foregroundDrawable = new BitmapDrawable(getResources(), mForegroundTabBitmap); |
||||
if (!mIsIncognito && mColorMode) { |
||||
foregroundDrawable.setColorFilter(mCurrentUiColor, PorterDuff.Mode.SRC_IN); |
||||
} |
||||
} else { |
||||
foregroundDrawable = mForegroundTabDrawable; |
||||
} |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
||||
holder.layout.setBackground(foregroundDrawable); |
||||
} else { |
||||
holder.layout.setBackgroundDrawable(foregroundDrawable); |
||||
} |
||||
if (!mIsIncognito && mColorMode) { |
||||
changeToolbarBackground(favicon, foregroundDrawable); |
||||
} |
||||
holder.favicon.setImageBitmap(favicon); |
||||
} else { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
holder.txtTitle.setTextAppearance(R.style.normalText); |
||||
} else { |
||||
holder.txtTitle.setTextAppearance(getContext(), R.style.normalText); |
||||
} |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { |
||||
holder.layout.setBackground(mBackgroundTabDrawable); |
||||
} else { |
||||
holder.layout.setBackgroundDrawable(mBackgroundTabDrawable); |
||||
} |
||||
holder.favicon.setImageBitmap(getDesaturatedBitmap(favicon)); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return tabsManager.size(); |
||||
} |
||||
|
||||
public Bitmap getDesaturatedBitmap(Bitmap favicon) { |
||||
Bitmap grayscaleBitmap = Bitmap.createBitmap(favicon.getWidth(), |
||||
favicon.getHeight(), Bitmap.Config.ARGB_8888); |
||||
|
||||
Canvas c = new Canvas(grayscaleBitmap); |
||||
if (mColorMatrix == null || mFilter == null || mPaint == null) { |
||||
mPaint = new Paint(); |
||||
mColorMatrix = new ColorMatrix(); |
||||
mColorMatrix.setSaturation(DESATURATED); |
||||
mFilter = new ColorMatrixColorFilter(mColorMatrix); |
||||
mPaint.setColorFilter(mFilter); |
||||
} |
||||
|
||||
c.drawBitmap(favicon, 0, 0, mPaint); |
||||
return grayscaleBitmap; |
||||
} |
||||
|
||||
/** |
||||
* Animates the color of the toolbar from one color to another. Optionally animates |
||||
* the color of the tab background, for use when the tabs are displayed on the top |
||||
* of the screen. |
||||
* |
||||
* @param favicon the Bitmap to extract the color from |
||||
* @param tabBackground the optional LinearLayout to color |
||||
*/ |
||||
private void changeToolbarBackground(@NonNull Bitmap favicon, @Nullable final Drawable tabBackground) { |
||||
if (mShowInNavigationDrawer) { |
||||
return; |
||||
} |
||||
|
||||
final int defaultColor; |
||||
final Resources resources = getResources(); |
||||
final ColorDrawable mBackground = new ColorDrawable(); |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { |
||||
defaultColor = resources.getColor(R.color.primary_color, null); |
||||
} else { |
||||
defaultColor = resources.getColor(R.color.primary_color); |
||||
} |
||||
if (mCurrentUiColor == Color.BLACK) { |
||||
mCurrentUiColor = defaultColor; |
||||
} |
||||
Palette.from(favicon).generate(new Palette.PaletteAsyncListener() { |
||||
@Override |
||||
public void onGenerated(Palette palette) { |
||||
|
||||
// OR with opaque black to remove transparency glitches
|
||||
int color = 0xff000000 | palette.getVibrantColor(defaultColor); |
||||
|
||||
int finalColor = Utils.mixTwoColors(defaultColor, color, 0.25f); |
||||
|
||||
ValueAnimator anim = ValueAnimator.ofInt(mCurrentUiColor, finalColor); |
||||
anim.setEvaluator(new ArgbEvaluator()); |
||||
// final Window window = getWindow();
|
||||
// TODO Check this
|
||||
// if (!mShowInNavigationDrawer) {
|
||||
// window.setBackgroundDrawable(new ColorDrawable(Color.BLACK));
|
||||
// }
|
||||
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
||||
|
||||
@Override |
||||
public void onAnimationUpdate(ValueAnimator animation) { |
||||
final int color = (Integer) animation.getAnimatedValue(); |
||||
if (tabBackground != null) { |
||||
tabBackground.setColorFilter(color, PorterDuff.Mode.SRC_IN); |
||||
} |
||||
mCurrentUiColor = color; |
||||
} |
||||
|
||||
}); |
||||
anim.setDuration(300); |
||||
anim.start(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public class LightningViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { |
||||
|
||||
public LightningViewHolder(View view) { |
||||
super(view); |
||||
txtTitle = (TextView) view.findViewById(R.id.textTab); |
||||
favicon = (ImageView) view.findViewById(R.id.faviconTab); |
||||
exit = (ImageView) view.findViewById(R.id.deleteButton); |
||||
layout = (LinearLayout) view.findViewById(R.id.tab_item_background); |
||||
exitButton = (FrameLayout) view.findViewById(R.id.deleteAction); |
||||
exit.setColorFilter(mIconColor, PorterDuff.Mode.SRC_IN); |
||||
|
||||
exitButton.setOnClickListener(this); |
||||
layout.setOnClickListener(this); |
||||
layout.setOnLongClickListener(this); |
||||
} |
||||
|
||||
final TextView txtTitle; |
||||
final ImageView favicon; |
||||
final ImageView exit; |
||||
final FrameLayout exitButton; |
||||
final LinearLayout layout; |
||||
|
||||
@Override |
||||
public void onClick(View v) { |
||||
if (v == exitButton) { |
||||
// Close tab
|
||||
bus.post(new TabEvents.CloseTab(getAdapterPosition())); |
||||
} |
||||
if (v == layout) { |
||||
bus.post(new TabEvents.ShowTab(getAdapterPosition())); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean onLongClick(View v) { |
||||
// Show close dialog
|
||||
bus.post(new TabEvents.ShowCloseDialog(getAdapterPosition())); |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -1,30 +0,0 @@
@@ -1,30 +0,0 @@
|
||||
/* |
||||
* Copyright 2014 A.C.R. Development |
||||
*/ |
||||
package acr.browser.lightning.object; |
||||
|
||||
import android.content.Context; |
||||
import android.os.Handler; |
||||
import android.os.Message; |
||||
|
||||
import acr.browser.lightning.controller.BrowserController; |
||||
|
||||
public class ClickHandler extends Handler { |
||||
|
||||
private BrowserController mBrowserController; |
||||
|
||||
public ClickHandler(Context context) { |
||||
try { |
||||
mBrowserController = (BrowserController) context; |
||||
} catch (ClassCastException e) { |
||||
throw new ClassCastException(context + " must implement BrowserController"); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void handleMessage(Message msg) { |
||||
super.handleMessage(msg); |
||||
String url = msg.getData().getString("url"); |
||||
mBrowserController.longClickPage(url); |
||||
} |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.graphics.Bitmap; |
||||
import android.net.Uri; |
||||
import android.util.Log; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
|
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Anthony C. Restaino |
||||
* @date 2015/09/29 |
||||
*/ |
||||
class IconCacheTask implements Runnable{ |
||||
private final Uri uri; |
||||
private final Bitmap icon; |
||||
|
||||
public IconCacheTask(Uri uri, Bitmap icon) { |
||||
this.uri = uri; |
||||
this.icon = icon; |
||||
} |
||||
|
||||
@Override |
||||
public void run() { |
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); |
||||
fos = new FileOutputStream(image); |
||||
icon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,250 @@
@@ -0,0 +1,250 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.Manifest; |
||||
import android.app.Activity; |
||||
import android.content.DialogInterface; |
||||
import android.content.res.Resources; |
||||
import android.graphics.Bitmap; |
||||
import android.graphics.BitmapFactory; |
||||
import android.net.Uri; |
||||
import android.os.Message; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.util.Log; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.webkit.GeolocationPermissions; |
||||
import android.webkit.ValueCallback; |
||||
import android.webkit.WebChromeClient; |
||||
import android.webkit.WebView; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.activity.BrowserActivity; |
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.utils.PermissionsManager; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici based on Anthony C. Restaino code |
||||
* @date 2015/09/21 |
||||
*/ |
||||
class LightningChromeClient extends WebChromeClient { |
||||
|
||||
private static final String[] PERMISSIONS = new String[]{Manifest.permission.ACCESS_FINE_LOCATION}; |
||||
|
||||
private final BrowserActivity mActivity; |
||||
private final LightningView mLightningView; |
||||
private final Bus eventBus; |
||||
|
||||
LightningChromeClient(BrowserActivity activity, LightningView lightningView) { |
||||
mActivity = activity; |
||||
mLightningView = lightningView; |
||||
eventBus = BrowserApp.getAppComponent().getBus(); |
||||
} |
||||
|
||||
@Override |
||||
public void onProgressChanged(WebView view, int newProgress) { |
||||
if (mLightningView.isShown()) { |
||||
mActivity.updateProgress(newProgress); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onReceivedIcon(WebView view, Bitmap icon) { |
||||
if (icon == null) |
||||
return; |
||||
mLightningView.mTitle.setFavicon(icon); |
||||
eventBus.post(new BrowserEvents.TabsChanged()); ; |
||||
cacheFavicon(view.getUrl(), icon); |
||||
} |
||||
|
||||
/** |
||||
* Naive caching of the favicon according to the domain name of the URL |
||||
* @param icon the icon to cache |
||||
*/ |
||||
private void cacheFavicon(final String url, final Bitmap icon) { |
||||
if (icon == null) return; |
||||
final Uri uri = Uri.parse(url); |
||||
if (uri.getHost() == null) { |
||||
return; |
||||
} |
||||
new Thread(new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
String hash = String.valueOf(uri.getHost().hashCode()); |
||||
Log.d(Constants.TAG, "Caching icon for " + uri.getHost()); |
||||
FileOutputStream fos = null; |
||||
try { |
||||
File image = new File(BrowserApp.getAppContext().getCacheDir(), hash + ".png"); |
||||
fos = new FileOutputStream(image); |
||||
icon.compress(Bitmap.CompressFormat.PNG, 100, fos); |
||||
fos.flush(); |
||||
} catch (IOException e) { |
||||
e.printStackTrace(); |
||||
} finally { |
||||
Utils.close(fos); |
||||
} |
||||
} |
||||
}).start(); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public void onReceivedTitle(WebView view, String title) { |
||||
if (title != null && !title.isEmpty()) { |
||||
mLightningView.mTitle.setTitle(title); |
||||
} else { |
||||
mLightningView.mTitle.setTitle(mActivity.getString(R.string.untitled)); |
||||
} |
||||
eventBus.post(new BrowserEvents.TabsChanged()); |
||||
if (view != null) { |
||||
mActivity.updateHistory(title, view.getUrl()); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onGeolocationPermissionsShowPrompt(final String origin, |
||||
final GeolocationPermissions.Callback callback) { |
||||
PermissionsManager.getInstance().requestPermissionsIfNecessary(mActivity, PERMISSIONS); |
||||
final boolean remember = true; |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
builder.setTitle(mActivity.getString(R.string.location)); |
||||
String org; |
||||
if (origin.length() > 50) { |
||||
org = origin.subSequence(0, 50) + "..."; |
||||
} else { |
||||
org = origin; |
||||
} |
||||
builder.setMessage(org + mActivity.getString(R.string.message_location)) |
||||
.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.action_allow), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
callback.invoke(origin, true, remember); |
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_dont_allow), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
callback.invoke(origin, false, remember); |
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
alert.show(); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, |
||||
Message resultMsg) { |
||||
mActivity.onCreateWindow(resultMsg); |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public void onCloseWindow(WebView window) { |
||||
mActivity.onCloseWindow(mLightningView); |
||||
} |
||||
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg) { |
||||
mActivity.openFileChooser(uploadMsg); |
||||
} |
||||
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType) { |
||||
mActivity.openFileChooser(uploadMsg); |
||||
} |
||||
|
||||
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { |
||||
mActivity.openFileChooser(uploadMsg); |
||||
} |
||||
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, |
||||
WebChromeClient.FileChooserParams fileChooserParams) { |
||||
mActivity.showFileChooser(filePathCallback); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Obtain an image that is displayed as a placeholder on a video until the video has initialized |
||||
* and can begin loading. |
||||
* |
||||
* @return a Bitmap that can be used as a place holder for videos. |
||||
*/ |
||||
@Override |
||||
public Bitmap getDefaultVideoPoster() { |
||||
final Resources resources = mActivity.getResources(); |
||||
return BitmapFactory.decodeResource(resources, android.R.drawable.spinner_background); |
||||
} |
||||
|
||||
/** |
||||
* Inflate a view to send to a LightningView when it needs to display a video and has to |
||||
* show a loading dialog. Inflates a progress view and returns it. |
||||
* |
||||
* @return A view that should be used to display the state |
||||
* of a video's loading progress. |
||||
*/ |
||||
@Override |
||||
public View getVideoLoadingProgressView() { |
||||
LayoutInflater inflater = LayoutInflater.from(mActivity); |
||||
return inflater.inflate(R.layout.video_loading_progress, null); |
||||
} |
||||
|
||||
@Override |
||||
public void onHideCustomView() { |
||||
mActivity.onHideCustomView(); |
||||
super.onHideCustomView(); |
||||
} |
||||
|
||||
@Override |
||||
public void onShowCustomView(View view, CustomViewCallback callback) { |
||||
// While these lines might look like they work, in practice,
|
||||
// Full-screen videos won't work correctly. I may test this out some
|
||||
// more
|
||||
// if (view instanceof FrameLayout) {
|
||||
// FrameLayout frame = (FrameLayout) view;
|
||||
// if (frame.getFocusedChild() instanceof VideoView) {
|
||||
// VideoView video = (VideoView) frame.getFocusedChild();
|
||||
// video.stopPlayback();
|
||||
// frame.removeView(video);
|
||||
// video.setVisibility(View.GONE);
|
||||
// }
|
||||
// } else {
|
||||
mActivity.onShowCustomView(view, callback); |
||||
|
||||
// }
|
||||
|
||||
super.onShowCustomView(view, callback); |
||||
} |
||||
|
||||
@Override |
||||
@Deprecated |
||||
public void onShowCustomView(View view, int requestedOrientation, |
||||
CustomViewCallback callback) { |
||||
// While these lines might look like they work, in practice,
|
||||
// Full-screen videos won't work correctly. I may test this out some
|
||||
// more
|
||||
// if (view instanceof FrameLayout) {
|
||||
// FrameLayout frame = (FrameLayout) view;
|
||||
// if (frame.getFocusedChild() instanceof VideoView) {
|
||||
// VideoView video = (VideoView) frame.getFocusedChild();
|
||||
// video.stopPlayback();
|
||||
// frame.removeView(video);
|
||||
// video.setVisibility(View.GONE);
|
||||
// }
|
||||
// } else {
|
||||
mActivity.onShowCustomView(view, callback); |
||||
|
||||
// }
|
||||
|
||||
super.onShowCustomView(view, requestedOrientation, callback); |
||||
} |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.Bitmap; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.utils.ThemeUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici base on Anthony C. Restaino's code |
||||
* @date 2015/09/21 |
||||
*/ |
||||
class LightningViewTitle { |
||||
|
||||
private static Bitmap DEFAULT_ICON = null; |
||||
|
||||
private Bitmap mFavicon; |
||||
private String mTitle; |
||||
|
||||
public LightningViewTitle(Context context, boolean darkTheme) { |
||||
if (DEFAULT_ICON == null) { |
||||
DEFAULT_ICON = ThemeUtils.getThemedBitmap(context, R.drawable.ic_webpage, darkTheme); |
||||
} |
||||
mFavicon = DEFAULT_ICON; |
||||
mTitle = context.getString(R.string.action_new_tab); |
||||
} |
||||
|
||||
public void setFavicon(Bitmap favicon) { |
||||
if (favicon == null) { |
||||
mFavicon = DEFAULT_ICON; |
||||
} else { |
||||
mFavicon = Utils.padFavicon(favicon); |
||||
} |
||||
} |
||||
|
||||
public void setTitle(String title) { |
||||
if (title == null) { |
||||
mTitle = ""; |
||||
} else { |
||||
mTitle = title; |
||||
} |
||||
} |
||||
|
||||
public void setTitleAndFavicon(String title, Bitmap favicon) { |
||||
mTitle = title; |
||||
|
||||
if (favicon == null) { |
||||
mFavicon = DEFAULT_ICON; |
||||
} else { |
||||
mFavicon = Utils.padFavicon(favicon); |
||||
} |
||||
} |
||||
|
||||
public String getTitle() { |
||||
return mTitle; |
||||
} |
||||
|
||||
public Bitmap getFavicon() { |
||||
return mFavicon; |
||||
} |
||||
|
||||
public Bitmap getDefaultIcon() { |
||||
return DEFAULT_ICON; |
||||
} |
||||
} |
@ -0,0 +1,276 @@
@@ -0,0 +1,276 @@
|
||||
package acr.browser.lightning.view; |
||||
|
||||
import android.annotation.SuppressLint; |
||||
import android.content.ActivityNotFoundException; |
||||
import android.content.DialogInterface; |
||||
import android.content.Intent; |
||||
import android.graphics.Bitmap; |
||||
import android.net.MailTo; |
||||
import android.net.http.SslError; |
||||
import android.os.Build; |
||||
import android.os.Message; |
||||
import android.support.annotation.NonNull; |
||||
import android.support.v7.app.AlertDialog; |
||||
import android.text.InputType; |
||||
import android.text.method.PasswordTransformationMethod; |
||||
import android.util.Log; |
||||
import android.webkit.HttpAuthHandler; |
||||
import android.webkit.SslErrorHandler; |
||||
import android.webkit.WebResourceRequest; |
||||
import android.webkit.WebResourceResponse; |
||||
import android.webkit.WebView; |
||||
import android.webkit.WebViewClient; |
||||
import android.widget.EditText; |
||||
import android.widget.LinearLayout; |
||||
|
||||
import com.squareup.otto.Bus; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.net.URISyntaxException; |
||||
|
||||
import acr.browser.lightning.R; |
||||
import acr.browser.lightning.activity.BrowserActivity; |
||||
import acr.browser.lightning.app.BrowserApp; |
||||
import acr.browser.lightning.bus.BrowserEvents; |
||||
import acr.browser.lightning.constant.Constants; |
||||
import acr.browser.lightning.utils.AdBlock; |
||||
import acr.browser.lightning.utils.IntentUtils; |
||||
import acr.browser.lightning.utils.ProxyUtils; |
||||
import acr.browser.lightning.utils.Utils; |
||||
|
||||
/** |
||||
* @author Stefano Pacifici based on Anthony C. Restaino's code |
||||
* @date 2015/09/22 |
||||
*/ |
||||
public class LightningWebClient extends WebViewClient { |
||||
|
||||
private final BrowserActivity mActivity; |
||||
private final LightningView mLightningView; |
||||
private final AdBlock mAdBlock; |
||||
private final Bus mEventBus; |
||||
private final IntentUtils mIntentUtils; |
||||
|
||||
LightningWebClient(BrowserActivity activity, LightningView lightningView) { |
||||
mActivity = activity; |
||||
mLightningView = lightningView; |
||||
mAdBlock = AdBlock.getInstance(activity); |
||||
mAdBlock.updatePreference(); |
||||
mEventBus = BrowserApp.getAppComponent().getBus(); |
||||
mIntentUtils = new IntentUtils(activity); |
||||
} |
||||
|
||||
@Override |
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { |
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { |
||||
if (mAdBlock.isAd(request.getUrl().toString())) { |
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); |
||||
return new WebResourceResponse("text/plain", "utf-8", EMPTY); |
||||
} |
||||
} |
||||
return super.shouldInterceptRequest(view, request); |
||||
} |
||||
|
||||
@Override |
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) { |
||||
if (mAdBlock.isAd(url)) { |
||||
ByteArrayInputStream EMPTY = new ByteArrayInputStream("".getBytes()); |
||||
return new WebResourceResponse("text/plain", "utf-8", EMPTY); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
@SuppressLint("NewApi") |
||||
@Override |
||||
public void onPageFinished(WebView view, String url) { |
||||
if (view.isShown()) { |
||||
mActivity.updateUrl(url, true); |
||||
view.postInvalidate(); |
||||
} |
||||
if (view.getTitle() == null || view.getTitle().isEmpty()) { |
||||
mLightningView.mTitle.setTitle(mActivity.getString(R.string.untitled)); |
||||
} else { |
||||
mLightningView.mTitle.setTitle(view.getTitle()); |
||||
} |
||||
if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT && |
||||
mLightningView.getInvertePage()) { |
||||
view.evaluateJavascript(Constants.JAVASCRIPT_INVERT_PAGE, null); |
||||
} |
||||
mEventBus.post(new BrowserEvents.TabsChanged()); |
||||
} |
||||
|
||||
@Override |
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) { |
||||
if (mLightningView.isShown()) { |
||||
mActivity.updateUrl(url, false); |
||||
mActivity.showActionBar(); |
||||
} |
||||
mEventBus.post(new BrowserEvents.TabsChanged()); |
||||
} |
||||
|
||||
@Override |
||||
public void onReceivedHttpAuthRequest(final WebView view, @NonNull final HttpAuthHandler handler, |
||||
final String host, final String realm) { |
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
final EditText name = new EditText(mActivity); |
||||
final EditText password = new EditText(mActivity); |
||||
LinearLayout passLayout = new LinearLayout(mActivity); |
||||
passLayout.setOrientation(LinearLayout.VERTICAL); |
||||
|
||||
passLayout.addView(name); |
||||
passLayout.addView(password); |
||||
|
||||
name.setHint(mActivity.getString(R.string.hint_username)); |
||||
name.setSingleLine(); |
||||
password.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); |
||||
password.setSingleLine(); |
||||
password.setTransformationMethod(new PasswordTransformationMethod()); |
||||
password.setHint(mActivity.getString(R.string.hint_password)); |
||||
builder.setTitle(mActivity.getString(R.string.title_sign_in)); |
||||
builder.setView(passLayout); |
||||
builder.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.title_sign_in), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
String user = name.getText().toString(); |
||||
String pass = password.getText().toString(); |
||||
handler.proceed(user.trim(), pass.trim()); |
||||
Log.d(Constants.TAG, "Request Login"); |
||||
|
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_cancel), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
handler.cancel(); |
||||
|
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
alert.show(); |
||||
|
||||
} |
||||
|
||||
private boolean mIsRunning = false; |
||||
private float mZoomScale = 0.0f; |
||||
|
||||
@SuppressLint("NewApi") |
||||
@Override |
||||
public void onScaleChanged(final WebView view, final float oldScale, final float newScale) { |
||||
if (view.isShown() && mLightningView.mPreferences.getTextReflowEnabled() && |
||||
Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { |
||||
if (mIsRunning) |
||||
return; |
||||
if (Math.abs(mZoomScale - newScale) > 0.01f) { |
||||
mIsRunning = view.postDelayed(new Runnable() { |
||||
|
||||
@Override |
||||
public void run() { |
||||
mZoomScale = newScale; |
||||
view.evaluateJavascript(Constants.JAVASCRIPT_TEXT_REFLOW, null); |
||||
mIsRunning = false; |
||||
} |
||||
|
||||
}, 100); |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onReceivedSslError(WebView view, @NonNull final SslErrorHandler handler, SslError error) { |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
builder.setTitle(mActivity.getString(R.string.title_warning)); |
||||
builder.setMessage(mActivity.getString(R.string.message_untrusted_certificate)) |
||||
.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.action_yes), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
handler.proceed(); |
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_no), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
handler.cancel(); |
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
if (error.getPrimaryError() == SslError.SSL_UNTRUSTED) { |
||||
alert.show(); |
||||
} else { |
||||
handler.proceed(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void onFormResubmission(WebView view, @NonNull final Message dontResend, final Message resend) { |
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); |
||||
builder.setTitle(mActivity.getString(R.string.title_form_resubmission)); |
||||
builder.setMessage(mActivity.getString(R.string.message_form_resubmission)) |
||||
.setCancelable(true) |
||||
.setPositiveButton(mActivity.getString(R.string.action_yes), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
|
||||
resend.sendToTarget(); |
||||
} |
||||
}) |
||||
.setNegativeButton(mActivity.getString(R.string.action_no), |
||||
new DialogInterface.OnClickListener() { |
||||
@Override |
||||
public void onClick(DialogInterface dialog, int id) { |
||||
|
||||
dontResend.sendToTarget(); |
||||
} |
||||
}); |
||||
AlertDialog alert = builder.create(); |
||||
alert.show(); |
||||
} |
||||
|
||||
@Override |
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) { |
||||
// Check if configured proxy is available
|
||||
if (!ProxyUtils.getInstance().isProxyReady()) { |
||||
// User has been notified
|
||||
return true; |
||||
} |
||||
|
||||
if (mLightningView.mIsIncognitoTab) { |
||||
return super.shouldOverrideUrlLoading(view, url); |
||||
} |
||||
if (url.startsWith("about:")) { |
||||
return super.shouldOverrideUrlLoading(view, url); |
||||
} |
||||
if (url.contains("mailto:")) { |
||||
MailTo mailTo = MailTo.parse(url); |
||||
Intent i = Utils.newEmailIntent(mailTo.getTo(), mailTo.getSubject(), |
||||
mailTo.getBody(), mailTo.getCc()); |
||||
mActivity.startActivity(i); |
||||
view.reload(); |
||||
return true; |
||||
} else if (url.startsWith("intent://")) { |
||||
Intent intent; |
||||
try { |
||||
intent = Intent.parseUri(url, Intent.URI_INTENT_SCHEME); |
||||
} catch (URISyntaxException ex) { |
||||
return false; |
||||
} |
||||
if (intent != null) { |
||||
try { |
||||
mActivity.startActivity(intent); |
||||
} catch (ActivityNotFoundException e) { |
||||
Log.e(Constants.TAG, "ActivityNotFoundException"); |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
return mIntentUtils.startActivityForUrl(view, url); |
||||
} |
||||
} |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="30dp" |
||||
android:background="@color/black" |
||||
android:overScrollMode="never" |
||||
android:scrollbars="none" |
||||
android:id="@+id/tabs_list" /> |
||||
|
Loading…
Reference in new issue