This commit is contained in:
lunaticbum
2024-11-11 18:12:06 +09:00
parent f2311bd7f9
commit 256a13b7df
219 changed files with 12544 additions and 326 deletions
+14
View File
@@ -0,0 +1,14 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<provider
android:name=".helpers.FileProvider4WebView"
android:authorities="${applicationId}.awesome_web_view.file_provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
</application>
</manifest>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
package com.wuadam.awesomewebview.enums;
/**
* Created by Leonardo on 11/14/15.
*/
public enum Position {
TOP_OF_TOOLBAR,
BOTTOM_OF_TOOLBAR,
TOP_OF_WEBVIEW,
BOTTOM_OF_WEBVIEW;
}
@@ -0,0 +1,25 @@
package com.wuadam.awesomewebview.helpers;
import android.text.TextUtils;
import android.util.Base64;
public class Base64ImgHelper {
private String src;
public Base64ImgHelper(String src) {
this.src = src;
}
public boolean isBase64Img() {
if (!TextUtils.isEmpty(src) && src.startsWith("data:image")) {
return true;
}
return false;
}
public byte[] decode() {
src = src.substring(src.indexOf(","));
byte[] imageAsBytes = Base64.decode(src.getBytes(), 0);
return imageAsBytes;
}
}
@@ -0,0 +1,69 @@
package com.wuadam.awesomewebview.helpers;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
/**
* Created by Leonardo on 11/21/15.
*/
public class BitmapHelper {
private BitmapHelper() {
}
public static Bitmap getColoredBitmap(@NonNull Bitmap bitmap, @ColorInt int color) {
int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
for (int i = 0; i < pixels.length; i++) {
int pixel = pixels[i];
int pixelAlpha = Color.alpha(pixel);
if (pixelAlpha != 0) {
pixels[i] = Color.argb((int) (pixelAlpha * alpha / 256f), red, green, blue);
}
}
Bitmap coloredBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true);
coloredBitmap.setPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
bitmap.getHeight());
return coloredBitmap;
}
public static Bitmap getColoredBitmap(@NonNull Context context, @DrawableRes int drawableRes,
@ColorInt int color) {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), drawableRes);
return getColoredBitmap(bitmap, color);
}
public static Bitmap getGradientBitmap(int width, int height, @ColorInt int color) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
for (int y = 0; y < height; y++) {
int gradientAlpha = (int) ((float) alpha * (float) (height - y) * (float) (height - y)
/ (float) height
/ (float) height);
for (int x = 0; x < width; x++) {
pixels[x + y * width] = Color.argb(gradientAlpha, red, green, blue);
}
}
bitmap.setPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
return bitmap;
}
}
@@ -0,0 +1,21 @@
package com.wuadam.awesomewebview.helpers;
import android.graphics.Color;
/**
* Created by Leonardo on 11/28/15.
*/
public class ColorHelper {
private ColorHelper() {
}
public static int disableColor(int color) {
int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
return Color.argb((int) (alpha * 0.2f), red, green, blue);
}
}
@@ -0,0 +1,248 @@
package com.wuadam.awesomewebview.helpers;
/**
* Created by wuzongheng on 2018/2/18.
*/
import android.os.AsyncTask;
import android.os.Environment;
import android.text.TextUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* 图片下载的工具类
*/
public class DownPicUtil {
/**
* Download the pic
* @param url
*/
public static void downPic(String url, DownFinishListener downFinishListener){
downPic(url, null, null, null, downFinishListener);
}
/**
* Download the pic
* @param url
*/
public static void downPic(String url, String userAgent, String referer, String cookie, DownFinishListener downFinishListener){
// 获取存储卡的目录
String filePath = Environment.getExternalStorageDirectory().getPath();
File file = new File(filePath + File.separator + Environment.DIRECTORY_DOWNLOADS);
if(!file.exists()){
file.mkdirs();
}
loadPic(file.getPath(), url, userAgent, referer, cookie, downFinishListener);
}
private static void loadPic(final String filePath, final String url, final String userAgent, final String referer, final String cookie, final DownFinishListener downFinishListener) {
new AsyncTask<Void,Void,String>(){
String fileName;
InputStream is;
OutputStream out;
@Override
protected String doInBackground(Void... voids) {
// 原文件名
String[] split = url.split("/");
fileName = split[split.length - 1];
// 创建目标文件,使用时间戳作为临时文件名,确保可以不重复
String now = String.valueOf(System.currentTimeMillis());
File picFile = new File(filePath + File.separator + now);
if(! picFile.exists()) {
// if file exists, do not download again
try {
Base64ImgHelper base64ImgHelper = new Base64ImgHelper(url);
if (base64ImgHelper.isBase64Img()) {
fileName = now;
byte[] image = base64ImgHelper.decode();
is = new ByteArrayInputStream(image); //处理服务器的响应结果
} else {
URL picUrl = new URL(url);
//通过图片的链接打开输入流
HttpURLConnection httpURLConnection = (HttpURLConnection) picUrl.openConnection();
httpURLConnection.setConnectTimeout(10000); //设置连接超时时间
httpURLConnection.setReadTimeout(30000);
httpURLConnection.setDoInput(true); //打开输入流,以便从服务器获取数据
httpURLConnection.setDoOutput(false); //Get请求不需要DoOutPut
httpURLConnection.setRequestMethod("GET"); //设置以Get方式请求数据
httpURLConnection.setUseCaches(false); //不使用缓存
//设置请求体的类型是文本类型
httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (!TextUtils.isEmpty(userAgent)) {
httpURLConnection.setRequestProperty("User-Agent", userAgent);
}
if (!TextUtils.isEmpty(referer)) {
httpURLConnection.setRequestProperty("Referer", referer);
}
if (!TextUtils.isEmpty(cookie)) {
httpURLConnection.setRequestProperty("Cookie", cookie);
}
httpURLConnection.connect();
int response = httpURLConnection.getResponseCode(); //获得服务器的响应码
if (response == HttpURLConnection.HTTP_OK || response == HttpURLConnection.HTTP_NOT_MODIFIED) {
is = httpURLConnection.getInputStream(); //处理服务器的响应结果
if (is == null) {
return null;
}
} else {
return null;
}
}
out = new FileOutputStream(picFile);
byte[] b = new byte[1024];
int end;
while ((end = is.read(b)) != -1) {
out.write(b, 0, end);
}
if (is != null) {
is.close();
}
if (out != null) {
out.close();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
// 提取文件格式真实拓展名
String extension = FormatHelper.getExtension(picFile);
// 提取不包含拓展名的原文件名
String[] extensions = fileName.split("\\.");
int splitLength = extensions.length;
String newFileNameNoExtension;
if (splitLength > 1) {
newFileNameNoExtension = fileName.substring(0, fileName.length() - extensions[splitLength - 1].length() - 1);
} else {
newFileNameNoExtension = fileName;
}
// 重命名文件
if (extension == null) {
// 不支持解析的格式,使用原文件名、原拓展名
if (splitLength > 1) {
// 有拓展名,在原文件名基础上递增重命名
return renamePic(picFile, filePath, newFileNameNoExtension, extensions[splitLength - 1], MODE.MODE_INCREMENT);
} else {
// 无拓展名,整个文件名递增重命名
return renamePic(picFile, filePath, newFileNameNoExtension, null, MODE.MODE_INCREMENT);
}
}
// 支持解析的格式,使用md5文件名、真实拓展名
String md5 = Md5Helper.getFileMD5ToString(picFile);
if (TextUtils.isEmpty(md5)) {
return renamePic(picFile, filePath, newFileNameNoExtension, extension, MODE.MODE_INCREMENT);
} else {
return renamePic(picFile, filePath, md5.substring(0, 16), extension, MODE.MODE_IGNORE);
}
}
@Override
protected void onPostExecute(String path) {
super.onPostExecute(path);
if(path!=null){
downFinishListener.onDownFinish(path);
} else {
downFinishListener.onError();
}
}
}.execute();
}
private enum MODE{
/**
* 已有文件名一致的文件,文件名后加上地增量(n)
*/
MODE_INCREMENT,
/**
* 已有文件名一致的文件,不执行
*/
MODE_IGNORE,
/**
* 已有文件名一致的文件,覆盖
*/
MODE_OVERRIDE
}
private static String renamePic(File picFile, String filePath, String newFileNameNoExtension, String extension, MODE mode) {
String extensionWithPoint = TextUtils.isEmpty(extension)? "": "." + extension;
String newFileName = newFileNameNoExtension + extensionWithPoint;
File newFile = new File(filePath + File.separator + newFileName);
switch (mode) {
case MODE_INCREMENT:
int count = 1;
while (newFile.exists()) {
// if file of new name exists, add (count) in filename;
newFileName = newFileNameNoExtension + "(" + count + ")" + extensionWithPoint;
newFile = new File(filePath + File.separator + newFileName);
count ++;
}
break;
case MODE_IGNORE:
if (newFile.exists()) {
return newFile.getPath();
}
break;
case MODE_OVERRIDE:
if (newFile.exists()) {
if (!newFile.delete()) {
return null;
}
}
break;
}
if (rename(picFile, newFileName)) {
return newFile.getPath();
} else {
return null;
}
}
private static boolean rename(final File file, final String newName) {
// file is null then return false
if (file == null) return false;
// file doesn't exist then return false
if (!file.exists()) return false;
// the new name is space then return false
if (TextUtils.isEmpty(newName)) return false;
// the new name equals old name then return true
if (newName.equals(file.getName())) return true;
File newFile = new File(file.getParent() + File.separator + newName);
// the new name of file exists then return false
return !newFile.exists()
&& file.renameTo(newFile);
}
//下载完成回调的接口
public interface DownFinishListener{
void onDownFinish(String path);
void onError();
}
}
@@ -0,0 +1,9 @@
package com.wuadam.awesomewebview.helpers;
import androidx.annotation.Keep;
import androidx.core.content.FileProvider;
@Keep
public final class FileProvider4WebView extends FileProvider {
}
@@ -0,0 +1,94 @@
package com.wuadam.awesomewebview.helpers;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FormatHelper {
private FormatHelper() {
}
public static String getExtension(File file) {
try {
// long fileLength = file.length();
FileInputStream fileInputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
bufferedInputStream.mark(Integer.MAX_VALUE);
byte[] start;
byte[] end;
// https://en.wikipedia.org/wiki/JPEG
start = new byte[2];
// end = new byte[2];
bufferedInputStream.read(start);
// bufferedInputStream.skip(fileLength - start.length - end.length);
// bufferedInputStream.read(end);
if (start[0] == (byte) 0xFF &&
start[1] == (byte) 0xD8
// &&
// end[0] == (byte) 0xFF &&
// end[1] == (byte) 0xD9
) {
return "jpg";
}
bufferedInputStream.reset();
// https://en.wikipedia.org/wiki/Portable_Network_Graphics
start = new byte[8];
bufferedInputStream.read(start);
if (start[0] == (byte) 0x89 &&
start[1] == (byte) 0x50 &&
start[2] == (byte) 0x4E &&
start[3] == (byte) 0x47 &&
start[4] == (byte) 0x0D &&
start[5] == (byte) 0x0A &&
start[6] == (byte) 0x1A &&
start[7] == (byte) 0x0A) {
return "png";
}
bufferedInputStream.reset();
// https://developers.google.com/speed/webp/docs/riff_container
// https://en.wikipedia.org/wiki/WebP
start = new byte[4];
end = new byte[4];
bufferedInputStream.read(start);
bufferedInputStream.skip(4);
bufferedInputStream.read(end);
if (start[0] == (byte) 0x52 &&
start[1] == (byte) 0x49 &&
start[2] == (byte) 0x46 &&
start[3] == (byte) 0x46 &&
end[0] == (byte) 0x57 &&
end[1] == (byte) 0x45 &&
end[2] == (byte) 0x42 &&
end[3] == (byte) 0x50) {
return "webp";
}
bufferedInputStream.reset();
// https://en.wikipedia.org/wiki/File:Empty_GIF_hex.png
start = new byte[6];
bufferedInputStream.read(start);
if (start[0] == (byte) 0x47 &&
start[1] == (byte) 0x49 &&
start[2] == (byte) 0x46 &&
start[3] == (byte) 0x38 &&
start[4] == (byte) 0x39 &&
start[5] == (byte) 0x61) {
return "gif";
}
bufferedInputStream.close();
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
@@ -0,0 +1,69 @@
package com.wuadam.awesomewebview.helpers;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Helper {
/**
* Return the MD5 of file.
*
* @param file The file.
* @return the md5 of file
*/
public static String getFileMD5ToString(final File file) {
return bytes2HexString(getFileMD5(file));
}
/**
* Return the MD5 of file.
*
* @param file The file.
* @return the md5 of file
*/
public static byte[] getFileMD5(final File file) {
if (file == null) return null;
DigestInputStream dis = null;
try {
FileInputStream fis = new FileInputStream(file);
MessageDigest md = MessageDigest.getInstance("MD5");
dis = new DigestInputStream(fis, md);
byte[] buffer = new byte[1024 * 256];
while (true) {
if (!(dis.read(buffer) > 0)) break;
}
md = dis.getMessageDigest();
return md.digest();
} catch (NoSuchAlgorithmException | IOException e) {
e.printStackTrace();
} finally {
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private static final char[] HEX_DIGITS =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
private static String bytes2HexString(final byte[] bytes) {
if (bytes == null) return "";
int len = bytes.length;
if (len <= 0) return "";
char[] ret = new char[len << 1];
for (int i = 0, j = 0; i < len; i++) {
ret[j++] = HEX_DIGITS[bytes[i] >> 4 & 0x0f];
ret[j++] = HEX_DIGITS[bytes[i] & 0x0f];
}
return new String(ret);
}
}
@@ -0,0 +1,78 @@
package com.wuadam.awesomewebview.helpers;
import android.content.Context;
import android.content.pm.PackageManager;
import androidx.core.app.ActivityCompat;
//import com.yanzhenjie.permission.Action;
//import com.yanzhenjie.permission.AndPermission;
import java.util.ArrayList;
import java.util.List;
/**
* Created by wuzongheng on 2016/11/5.
*/
public class PermissionHelper {
public static boolean hasPermissions(Context context, String... permissionName) {
for (String permission : permissionName) {
if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
public static List<String> getGrantedPermissions(Context context, String... permissionName) {
List<String> permissionsGranted = new ArrayList<>();
for (String permission : permissionName) {
if (ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) {
permissionsGranted.add(permission);
}
}
return permissionsGranted;
}
public interface CheckPermissionListener {
void onAllGranted(boolean sync);
/**
* Partly granted(deniedPermissions.size() >= 0) or all denied
*/
void onPartlyGranted(List<String> permissionsDenied, boolean sync);
}
public static void CheckPermissions(final Context context, final CheckPermissionListener checkPermissionListener, String... permissionName) {
if (hasPermissions(context, permissionName)) {
if (checkPermissionListener != null) {
checkPermissionListener.onAllGranted(true);
}
}else {
// AndPermission.with(context)
// .runtime()
// .permission(permissionName)
// .onGranted(new Action<List<String>>() {
// @Override
// public void onAction(List<String> permissions) {
// // 权限申请成功回调。
// if (checkPermissionListener != null) {
// checkPermissionListener.onAllGranted(false);
// }
// }
// })
// .onDenied(new Action<List<String>>() {
// @Override
// public void onAction(List<String> permissions) {
// if (checkPermissionListener != null) {
// checkPermissionListener.onPartlyGranted(permissions, false);
// }
// }
// })
// .start();
}
}
}
@@ -0,0 +1,48 @@
package com.wuadam.awesomewebview.helpers;
import android.content.Context;
import android.graphics.Typeface;
import androidx.collection.SimpleArrayMap;
/**
* Created by Leonardo on 11/14/15.
*/
/*
Each call to Typeface.createFromAsset will load a new instance of the typeface into memory,
and this memory is not consistently get garbage collected
http://code.google.com/p/android/issues/detail?id=9904
(It states released but even on Lollipop you can see the typefaces accumulate even after
multiple GC passes)
You can detect this by running:
adb shell dumpsys meminfo com.your.packagenage
You will see output like:
Asset Allocations
zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Regular.ttf: 123K
zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K
*/
public class TypefaceHelper {
private static final SimpleArrayMap<String, Typeface> cache = new SimpleArrayMap<>();
private TypefaceHelper() {
}
public static Typeface get(Context c, String name) {
synchronized (cache) {
if (!cache.containsKey(name)) {
try {
Typeface t = Typeface.createFromAsset(c.getAssets(), String.format("fonts/%s", name));
cache.put(name, t);
return t;
} catch (RuntimeException e) {
return null;
}
}
return cache.get(name);
}
}
}
@@ -0,0 +1,22 @@
package com.wuadam.awesomewebview.helpers;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Created by Leonardo on 11/23/15.
*/
public class UrlParser {
private UrlParser() {
}
public static String getHost(String url) {
try {
return new URL(url).getHost();
} catch (MalformedURLException e) {
e.printStackTrace();
}
return url;
}
}
@@ -0,0 +1,19 @@
package com.wuadam.awesomewebview.jsInterface;
import android.webkit.JavascriptInterface;
/**
* @Description: Base class for JS interface
* @Author: zongheng.wu
* @Date: 2/23/21 4:10 PM
*/
public abstract class BaseJsInterface {
public BaseJsInterface() {
}
@JavascriptInterface
public String getSimpleName() {
return getClass().getSimpleName();
}
}
@@ -0,0 +1,61 @@
package com.wuadam.awesomewebview.jsInterface;
import android.text.TextUtils;
import android.util.Pair;
import android.webkit.WebView;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: JS interface handler
* @Author: zongheng.wu
* @Date: 2/23/21 3:36 PM
*/
public class CommonJsHelper<T extends BaseJsInterface> {
private static CommonJsHelper commonJsHelper;
public static CommonJsHelper getInstance() {
if (commonJsHelper == null) {
synchronized (CommonJsHelper.class) {
if (commonJsHelper == null) {
commonJsHelper = new CommonJsHelper();
}
}
}
return commonJsHelper;
}
private CommonJsHelper() {
}
private final List<Pair<Class<T>, String>> interfacesInternal = new ArrayList<>(0);
/**
* add new JS interface
* @param ifc
* @param bridge
*/
public void addJavascriptInterface(Class<T> ifc, String bridge) {
interfacesInternal.add(Pair.create(ifc, bridge));
}
/**
* do not call, only for internal
* @param webView
*/
public void addJavascriptInterface(WebView webView) {
for (Pair<Class<T>, String> pair: interfacesInternal) {
if (pair == null || pair.first == null || TextUtils.isEmpty(pair.second)) {
continue;
}
try {
webView.addJavascriptInterface(pair.first.newInstance(), pair.second);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
}
@@ -0,0 +1,67 @@
package com.wuadam.awesomewebview.jsInterface;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import com.wuadam.awesomewebview.views.VideoEnabledWebChromeClient;
public class VideoJsHelper {
private VideoEnabledWebChromeClient videoEnabledWebChromeClient;
private boolean addedJavascriptInterface;
public class JavascriptInterface
{
@android.webkit.JavascriptInterface @SuppressWarnings("unused")
public void notifyVideoEnd() // Must match Javascript interface method of VideoEnabledWebChromeClient
{
Log.d("___", "GOT IT");
if (videoEnabledWebChromeClient != null) {
// This code is not executed in the UI thread, so we must force that to happen
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
videoEnabledWebChromeClient.onHideCustomView();
}
});
}
}
}
/**
* Indicates if the video is being displayed using a custom view (typically full-screen)
* @return true it the video is being displayed using a custom view (typically full-screen)
*/
@SuppressWarnings("unused")
public boolean isVideoFullscreen()
{
return videoEnabledWebChromeClient != null && videoEnabledWebChromeClient.isVideoFullscreen();
}
/**
* Pass only a VideoEnabledWebChromeClient instance.
*/
@SuppressLint("SetJavaScriptEnabled")
public void setWebChromeClient(WebChromeClient client)
{
if (client instanceof VideoEnabledWebChromeClient)
{
this.videoEnabledWebChromeClient = (VideoEnabledWebChromeClient) client;
}
}
public void addJavascriptInterface(WebView webView)
{
if (!addedJavascriptInterface)
{
// Add javascript interface to be called when the video ends (must be done before page load)
//noinspection all
webView.addJavascriptInterface(new JavascriptInterface(), "_VideoEnabledWebView"); // Must match Javascript interface name of VideoEnabledWebChromeClient
addedJavascriptInterface = true;
}
}
}
@@ -0,0 +1,224 @@
package com.wuadam.awesomewebview.listeners;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.util.List;
/**
* Created by TheFinestArtist on 1/26/16.
*/
public class BroadCastManager {
static final String WEBVIEW_EVENT = "WEBVIEW_EVENT";
static final String EXTRA_KEY = "EXTRA_KEY";
static final String EXTRA_TYPE = "EXTRA_TYPE";
static final String EXTRA_URL = "EXTRA_URL";
static final String EXTRA_TITLE = "EXTRA_TITLE";
static final String EXTRA_PROGESS = "EXTRA_PROGESS";
static final String EXTRA_PRECOMPOSED = "EXTRA_PRECOMPOSED";
static final String EXTRA_USER_AGENT = "EXTRA_USER_AGENT";
static final String EXTRA_CONTENT_DISPOSITION = "EXTRA_CONTENT_DISPOSITION";
static final String EXTRA_MIME_TYPE = "EXTRA_MIME_TYPE";
static final String EXTRA_CONTENT_LENGTH = "EXTRA_CONTENT_LENGTH";
static final String EXTRA_MENU_CODE = "EXTRA_MENU_CODE";
static final String EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL";
protected int key;
protected List<WebViewListener> listeners;
protected LocalBroadcastManager manager;
protected BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (context == null || intent == null) return;
int key = intent.getIntExtra(EXTRA_KEY, Integer.MIN_VALUE);
if (BroadCastManager.this.key == key) handleIntent(intent);
}
};
public BroadCastManager(Context context, int key, @NonNull List<WebViewListener> listeners) {
this.key = key;
this.listeners = listeners;
manager = LocalBroadcastManager.getInstance(context);
manager.registerReceiver(receiver, new IntentFilter(WEBVIEW_EVENT));
}
// Base Static Methods
private static Intent getBaseIntent(int key, Type type) {
return new Intent(BroadCastManager.WEBVIEW_EVENT).putExtra(EXTRA_KEY, key)
.putExtra(EXTRA_TYPE, type);
}
private static void sendBroadCast(Context context, Intent intent) {
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
// Handle Each Event Type
public static void onProgressChanged(Context context, int key, int progress) {
Intent intent = getBaseIntent(key, Type.PROGRESS_CHANGED).putExtra(EXTRA_PROGESS, progress);
sendBroadCast(context, intent);
}
public static void onReceivedTitle(Context context, int key, String title) {
Intent intent = getBaseIntent(key, Type.RECEIVED_TITLE).putExtra(EXTRA_TITLE, title);
sendBroadCast(context, intent);
}
public static void onReceivedTouchIconUrl(Context context, int key, String url,
boolean precomposed) {
Intent intent = getBaseIntent(key, Type.RECEIVED_TOUCH_ICON_URL).putExtra(EXTRA_URL, url)
.putExtra(EXTRA_PRECOMPOSED, precomposed);
sendBroadCast(context, intent);
}
public static void onPageStarted(Context context, int key, String url) {
Intent intent = getBaseIntent(key, Type.PAGE_STARTED).putExtra(EXTRA_URL, url);
sendBroadCast(context, intent);
}
public static void onPageFinished(Context context, int key, String url) {
Intent intent = getBaseIntent(key, Type.PAGE_FINISHED).putExtra(EXTRA_URL, url);
sendBroadCast(context, intent);
}
public static void onLoadResource(Context context, int key, String url) {
Intent intent = getBaseIntent(key, Type.LOAD_RESOURCE).putExtra(EXTRA_URL, url);
sendBroadCast(context, intent);
}
public static void onPageCommitVisible(Context context, int key, String url) {
Intent intent = getBaseIntent(key, Type.PAGE_COMMIT_VISIBLE).putExtra(EXTRA_URL, url);
sendBroadCast(context, intent);
}
public static void onDownloadStart(Context context, int key, String url, String userAgent,
String contentDisposition, String mimeType, long contentLength) {
Intent intent = getBaseIntent(key, Type.DOWNLOADED_START).putExtra(EXTRA_URL, url)
.putExtra(EXTRA_USER_AGENT, userAgent)
.putExtra(EXTRA_CONTENT_DISPOSITION, contentDisposition)
.putExtra(EXTRA_MIME_TYPE, mimeType)
.putExtra(EXTRA_CONTENT_LENGTH, contentLength);
sendBroadCast(context, intent);
}
public static void onCustomMenuClick(Context context, int key, String menuCode) {
Intent intent = getBaseIntent(key, Type.CUSTOM_MENU_CLICK).putExtra(EXTRA_MENU_CODE, menuCode);
sendBroadCast(context, intent);
}
public static void onClickImage(Context context, int key, String imageUrl) {
Intent intent = getBaseIntent(key, Type.CLICK_IMAGE).putExtra(EXTRA_IMAGE_URL, imageUrl);
sendBroadCast(context, intent);
}
public static void unregister(Context context, int key) {
Intent intent = getBaseIntent(key, Type.UNREGISTER);
sendBroadCast(context, intent);
}
private void handleIntent(@NonNull Intent intent) {
Type type = (Type) intent.getSerializableExtra(EXTRA_TYPE);
switch (type) {
case PROGRESS_CHANGED:
onProgressChanged(intent);
break;
case RECEIVED_TITLE:
onReceivedTitle(intent);
break;
case RECEIVED_TOUCH_ICON_URL:
onReceivedTouchIconUrl(intent);
break;
case PAGE_STARTED:
onPageStarted(intent);
break;
case PAGE_FINISHED:
onPageFinished(intent);
break;
case LOAD_RESOURCE:
onLoadResource(intent);
break;
case PAGE_COMMIT_VISIBLE:
onPageCommitVisible(intent);
break;
case DOWNLOADED_START:
onDownloadStart(intent);
break;
case CUSTOM_MENU_CLICK:
onCustomMenuClick(intent);
break;
case CLICK_IMAGE:
onClickImage(intent);
break;
case UNREGISTER:
unregister();
break;
}
}
public void onProgressChanged(Intent intent) {
for (WebViewListener listener : listeners)
listener.onProgressChanged(intent.getIntExtra(EXTRA_PROGESS, 0));
}
public void onReceivedTitle(Intent intent) {
for (WebViewListener listener : listeners)
listener.onReceivedTitle(intent.getStringExtra(EXTRA_TITLE));
}
public void onReceivedTouchIconUrl(Intent intent) {
for (WebViewListener listener : listeners)
listener.onReceivedTouchIconUrl(intent.getStringExtra(EXTRA_URL),
intent.getBooleanExtra(EXTRA_PRECOMPOSED, false));
}
public void onPageStarted(Intent intent) {
for (WebViewListener listener : listeners)
listener.onPageStarted(intent.getStringExtra(EXTRA_URL));
}
public void onPageFinished(Intent intent) {
for (WebViewListener listener : listeners)
listener.onPageFinished(intent.getStringExtra(EXTRA_URL));
}
public void onLoadResource(Intent intent) {
for (WebViewListener listener : listeners)
listener.onLoadResource(intent.getStringExtra(EXTRA_URL));
}
public void onPageCommitVisible(Intent intent) {
for (WebViewListener listener : listeners)
listener.onPageCommitVisible(intent.getStringExtra(EXTRA_URL));
}
public void onDownloadStart(Intent intent) {
for (WebViewListener listener : listeners)
listener.onDownloadStart(intent.getStringExtra(EXTRA_URL),
intent.getStringExtra(EXTRA_USER_AGENT), intent.getStringExtra(EXTRA_CONTENT_DISPOSITION),
intent.getStringExtra(EXTRA_MIME_TYPE), intent.getLongExtra(EXTRA_CONTENT_LENGTH, 0l));
}
public void onCustomMenuClick(Intent intent) {
for (WebViewListener listener: listeners) {
listener.onCustomMenuClick(intent.getStringExtra(EXTRA_MENU_CODE));
}
}
public void onClickImage(Intent intent) {
for (WebViewListener listener: listeners) {
listener.onClickImage(intent.getStringExtra(EXTRA_IMAGE_URL));
}
}
private void unregister() {
if (manager != null && receiver != null) manager.unregisterReceiver(receiver);
}
public enum Type {
PROGRESS_CHANGED, RECEIVED_TITLE, RECEIVED_TOUCH_ICON_URL, PAGE_STARTED, PAGE_FINISHED, LOAD_RESOURCE, PAGE_COMMIT_VISIBLE, DOWNLOADED_START, CUSTOM_MENU_CLICK, CLICK_IMAGE, UNREGISTER
}
}
@@ -0,0 +1,38 @@
package com.wuadam.awesomewebview.listeners;
/**
* Created by TheFinestArtist on 1/26/16.
*/
public abstract class WebViewListener {
public void onProgressChanged(int progress) {
}
public void onReceivedTitle(String title) {
}
public void onReceivedTouchIconUrl(String url, boolean precomposed) {
}
public void onPageStarted(String url) {
}
public void onPageFinished(String url) {
}
public void onLoadResource(String url) {
}
public void onPageCommitVisible(String url) {
}
public void onDownloadStart(String url, String userAgent, String contentDisposition,
String mimeType, long contentLength) {
}
public void onCustomMenuClick(String menuCode) {
}
public void onClickImage(String imageUrl) {
}
}
@@ -0,0 +1,23 @@
package com.wuadam.awesomewebview.objects;
import androidx.annotation.StringRes;
import java.io.Serializable;
public class CustomMenu implements Serializable {
private int titleRes;
private String code;
public CustomMenu(@StringRes int titleRes, String code) {
this.titleRes = titleRes;
this.code = code;
}
public int getTitleRes() {
return titleRes;
}
public String getCode() {
return code;
}
}
@@ -0,0 +1,187 @@
package com.wuadam.awesomewebview.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import androidx.core.content.ContextCompat;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import com.wuadam.awesomewebview.R;
/**
* Created by Leonardo on 11/26/15.
*/
public class ShadowLayout extends FrameLayout {
private int shadowColor;
private float shadowSize;
private float cornerRadius;
private float dx;
private float dy;
public ShadowLayout(Context context) {
super(context);
setWillNotDraw(false);
initAttributes(null);
setPadding();
}
public ShadowLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
initAttributes(attrs);
setPadding();
}
public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setWillNotDraw(false);
initAttributes(attrs);
setPadding();
}
private void initAttributes(AttributeSet attrs) {
TypedArray attr = getContext().obtainStyledAttributes(attrs, R.styleable.ShadowLayout, 0, 0);
if (attr == null) return;
try {
cornerRadius = attr.getDimension(R.styleable.ShadowLayout_slCornerRadius,
getResources().getDimension(R.dimen.defaultMenuDropShadowCornerRadius));
shadowSize = attr.getDimension(R.styleable.ShadowLayout_slShadowSize,
getResources().getDimension(R.dimen.defaultMenuDropShadowSize));
dx = attr.getDimension(R.styleable.ShadowLayout_slDx, 0);
dy = attr.getDimension(R.styleable.ShadowLayout_slDy, 0);
shadowColor = attr.getColor(R.styleable.ShadowLayout_slShadowColor,
ContextCompat.getColor(getContext(), R.color.finestBlack10));
} finally {
attr.recycle();
}
}
private void setPadding() {
int xPadding = (int) (shadowSize + Math.abs(dx));
int yPadding = (int) (shadowSize + Math.abs(dy));
setPadding(xPadding, yPadding, xPadding, yPadding);
}
public void setShadowColor(int shadowColor) {
this.shadowColor = shadowColor;
invalidate();
}
public void setShadowSize(float shadowSize) {
this.shadowSize = shadowSize;
setPadding();
}
public void setCornerRadius(float cornerRadius) {
this.cornerRadius = cornerRadius;
invalidate();
}
public void setDx(float dx) {
this.dx = dx;
setPadding();
}
public void setDy(float dy) {
this.dy = dy;
setPadding();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// RoundRectShape rss = new RoundRectShape(new float[]{12f, 12f, 12f,
// 12f, 12f, 12f, 12f, 12f}, null, null);
// ShapeDrawable sds = new ShapeDrawable(rss);
// sds.setShaderFactory(new ShapeDrawable.ShaderFactory() {
//
// @Override
// public Shader resize(int width, int height) {
// LinearGradient lg = new LinearGradient(0, 0, 0, height,
// new int[]{Color.parseColor("#e5e5e5"),
// Color.parseColor("#e5e5e5"),
// Color.parseColor("#e5e5e5"),
// Color.parseColor("#e5e5e5")}, new float[]{0,
// 0.50f, 0.50f, 1}, Shader.TileMode.REPEAT);
// return lg;
// }
// });
//
// LayerDrawable ld = new LayerDrawable(new Drawable[]{sds, sds});
// ld.setLayerInset(0, 5, 5, 0, 0); // inset the shadow so it doesn't start right at the left/top
// ld.setLayerInset(1, 0, 0, 5, 5); // inset the top drawable so we can leave a bit of space for the shadow to use
setBackgroundCompat(canvas.getWidth(), canvas.getHeight());
}
@SuppressWarnings("deprecation")
private void setBackgroundCompat(int w, int h) {
Bitmap bitmap =
createShadowBitmap(w, h, cornerRadius, shadowSize, dx, dy, shadowColor, Color.TRANSPARENT);
// Bitmap coloredBitmap = BitmapHelper.getColoredBitmap(getContext(), bitmap, shadowColor);
BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
setBackgroundDrawable(drawable);
} else {
setBackground(drawable);
}
}
private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight, float cornerRadius,
float shadowSize, float dx, float dy, int shadowColor, int fillColor) {
Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ALPHA_8);
Canvas canvas = new Canvas(output);
RectF shadowRect =
new RectF(shadowSize, shadowSize, shadowWidth - shadowSize, shadowHeight - shadowSize);
if (dy > 0) {
shadowRect.top += dy;
shadowRect.bottom -= dy;
} else if (dy < 0) {
shadowRect.top += Math.abs(dy);
shadowRect.bottom -= Math.abs(dy);
}
if (dx > 0) {
shadowRect.left += dx;
shadowRect.right -= dx;
} else if (dx < 0) {
shadowRect.left += Math.abs(dx);
shadowRect.right -= Math.abs(dx);
}
Paint shadowPaint = new Paint();
shadowPaint.setAntiAlias(true);
shadowPaint.setColor(fillColor);
shadowPaint.setStyle(Paint.Style.FILL);
shadowPaint.setShadowLayer(shadowSize, dx, dy, shadowColor);
canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, shadowPaint);
return output;
}
@Override
protected int getSuggestedMinimumWidth() {
return 0;
}
@Override
protected int getSuggestedMinimumHeight() {
return 0;
}
}
@@ -0,0 +1,283 @@
package com.wuadam.awesomewebview.views;
import android.media.MediaPlayer;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.widget.FrameLayout;
/**
* This class serves as a WebChromeClient to be set to a WebView, allowing it to play video.
* Video will play differently depending on target API level (in-line, fullscreen, or both).
*
* It has been tested with the following video classes:
* - android.widget.VideoView (typically API level <11)
* - android.webkit.HTML5VideoFullScreen$VideoSurfaceView/VideoTextureView (typically API level 11-18)
* - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView (typically API level 19+)
*
* Important notes:
* - For API level 11+, android:hardwareAccelerated="true" must be set in the application manifest.
* - The invoking activity must call VideoEnabledWebChromeClient's onBackPressed() inside of its own onBackPressed().
* - Tested in Android API levels 8-19. Only tested on http://m.youtube.com.
*
* @author Cristian Perez (http://cpr.name)
*
*/
public class VideoEnabledWebChromeClient extends WebChromeClient implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener
{
public interface ToggledFullscreenCallback
{
void toggledFullscreen(boolean fullscreen);
}
private View activityNonVideoView;
private ViewGroup activityVideoView;
private View loadingView;
private WebView webView;
private boolean isVideoFullscreen; // Indicates if the video is being displayed using a custom view (typically full-screen)
private FrameLayout videoViewContainer;
private CustomViewCallback videoViewCallback;
private ToggledFullscreenCallback toggledFullscreenCallback;
/**
* Never use this constructor alone.
* This constructor allows this class to be defined as an inline inner class in which the user can override methods
*/
@SuppressWarnings("unused")
public VideoEnabledWebChromeClient()
{
}
/**
* Builds a video enabled WebChromeClient.
* @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen.
* @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout.
*/
@SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView)
{
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = null;
this.webView = null;
this.isVideoFullscreen = false;
}
/**
* Builds a video enabled WebChromeClient.
* @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen.
* @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout.
* @param loadingView A View to be shown while the video is loading (typically only used in API level <11). Must be already inflated and not attached to a parent view.
*/
@SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView)
{
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = loadingView;
this.webView = null;
this.isVideoFullscreen = false;
}
/**
* Builds a video enabled WebChromeClient.
* @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen.
* @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout.
* @param loadingView A View to be shown while the video is loading (typically only used in API level <11). Must be already inflated and not attached to a parent view.
* @param webView The owner VideoEnabledWebView. Passing it will enable the VideoEnabledWebChromeClient to detect the HTML5 video ended event and exit full-screen.
* Note: The web page must only contain one video tag in order for the HTML5 video ended event to work. This could be improved if needed (see Javascript code).
*/
@SuppressWarnings("unused")
public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView, WebView webView)
{
this.activityNonVideoView = activityNonVideoView;
this.activityVideoView = activityVideoView;
this.loadingView = loadingView;
this.webView = webView;
this.isVideoFullscreen = false;
}
/**
* Indicates if the video is being displayed using a custom view (typically full-screen)
* @return true it the video is being displayed using a custom view (typically full-screen)
*/
public boolean isVideoFullscreen()
{
return isVideoFullscreen;
}
/**
* Set a callback that will be fired when the video starts or finishes displaying using a custom view (typically full-screen)
* @param callback A VideoEnabledWebChromeClient.ToggledFullscreenCallback callback
*/
@SuppressWarnings("unused")
public void setOnToggledFullscreen(ToggledFullscreenCallback callback)
{
this.toggledFullscreenCallback = callback;
}
@Override
public void onShowCustomView(View view, CustomViewCallback callback)
{
if (view instanceof FrameLayout)
{
// A video wants to be shown
FrameLayout frameLayout = (FrameLayout) view;
View focusedChild = frameLayout.getFocusedChild();
// Save video related variables
this.isVideoFullscreen = true;
this.videoViewContainer = frameLayout;
this.videoViewCallback = callback;
// Hide the non-video view, add the video view, and show it
activityNonVideoView.setVisibility(View.INVISIBLE);
activityVideoView.setVisibility(View.VISIBLE);
activityVideoView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
activityVideoView.setVisibility(View.VISIBLE);
if (focusedChild instanceof android.widget.VideoView)
{
// android.widget.VideoView (typically API level <11)
android.widget.VideoView videoView = (android.widget.VideoView) focusedChild;
// Handle all the required events
videoView.setOnPreparedListener(this);
videoView.setOnCompletionListener(this);
videoView.setOnErrorListener(this);
}
else
{
// Other classes, including:
// - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 11-18)
// - android.webkit.HTML5VideoFullScreen$VideoTextureView, which inherits from android.view.TextureView (typically API level 11-18)
// - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 19+)
// Handle HTML5 video ended event only if the class is a SurfaceView
// Test case: TextureView of Sony Xperia T API level 16 doesn't work fullscreen when loading the javascript below
if (webView != null && webView.getSettings().getJavaScriptEnabled() && focusedChild instanceof SurfaceView)
{
// Run javascript code that detects the video end and notifies the Javascript interface
String js = "javascript:";
js += "var _ytrp_html5_video_last;";
js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];";
js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {";
{
js += "_ytrp_html5_video_last = _ytrp_html5_video;";
js += "function _ytrp_html5_video_ended() {";
{
js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must match Javascript interface name and method of VideoEnableWebView
}
js += "}";
js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);";
}
js += "}";
webView.loadUrl(js);
}
}
// Notify full-screen change
if (toggledFullscreenCallback != null)
{
toggledFullscreenCallback.toggledFullscreen(true);
}
}
}
@Override @SuppressWarnings("deprecation")
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) // Available in API level 14+, deprecated in API level 18+
{
onShowCustomView(view, callback);
}
@Override
public void onHideCustomView()
{
// This method should be manually called on video end in all cases because it's not always called automatically.
// This method must be manually called on back key press (from this class' onBackPressed() method).
if (isVideoFullscreen)
{
// Hide the video view, remove it, and show the non-video view
activityVideoView.setVisibility(View.INVISIBLE);
activityVideoView.removeView(videoViewContainer);
activityNonVideoView.setVisibility(View.VISIBLE);
// Call back (only in API level <19, because in API level 19+ with chromium webview it crashes)
if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium."))
{
videoViewCallback.onCustomViewHidden();
}
// Reset video related variables
isVideoFullscreen = false;
videoViewContainer = null;
videoViewCallback = null;
// Notify full-screen change
if (toggledFullscreenCallback != null)
{
toggledFullscreenCallback.toggledFullscreen(false);
}
}
}
@Override
public View getVideoLoadingProgressView() // Video will start loading
{
if (loadingView != null)
{
loadingView.setVisibility(View.VISIBLE);
return loadingView;
}
else
{
return super.getVideoLoadingProgressView();
}
}
@Override
public void onPrepared(MediaPlayer mp) // Video will start playing, only called in the case of android.widget.VideoView (typically API level <11)
{
if (loadingView != null)
{
loadingView.setVisibility(View.GONE);
}
}
@Override
public void onCompletion(MediaPlayer mp) // Video finished playing, only called in the case of android.widget.VideoView (typically API level <11)
{
onHideCustomView();
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) // Error while playing video, only called in the case of android.widget.VideoView (typically API level <11)
{
return false; // By returning false, onCompletion() will be called
}
/**
* Notifies the class that the back key has been pressed by the user.
* This must be called from the Activity's onBackPressed(), and if it returns false, the activity itself should handle it. Otherwise don't do anything.
* @return Returns true if the event was handled, and false if was not (video view is not visible)
*/
@SuppressWarnings("unused")
public boolean onBackPressed()
{
if (isVideoFullscreen)
{
onHideCustomView();
return true;
}
else
{
return false;
}
}
}
@@ -0,0 +1,101 @@
package com.wuadam.awesomewebview.views;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import com.wuadam.awesomewebview.jsInterface.VideoJsHelper;
import java.util.Map;
/**
* This class serves as a WebView to be used in conjunction with a VideoEnabledWebChromeClient.
* It makes possible:
* - To detect the HTML5 video ended event so that the VideoEnabledWebChromeClient can exit full-screen.
*
* Important notes:
* - Javascript is enabled by default and must not be disabled with getSettings().setJavaScriptEnabled(false).
* - setWebChromeClient() must be called before any loadData(), loadDataWithBaseURL() or loadUrl() method.
*
* @author Cristian Perez (http://cpr.name)
*
*/
public class VideoEnabledWebView extends WebView
{
private VideoJsHelper videoJsHelper;
@SuppressWarnings("unused")
public VideoEnabledWebView(Context context)
{
super(context);
videoJsHelper = new VideoJsHelper();
}
@SuppressWarnings("unused")
public VideoEnabledWebView(Context context, AttributeSet attrs)
{
super(context, attrs);
videoJsHelper = new VideoJsHelper();
}
@SuppressWarnings("unused")
public VideoEnabledWebView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
videoJsHelper = new VideoJsHelper();
}
/**
* Indicates if the video is being displayed using a custom view (typically full-screen)
* @return true it the video is being displayed using a custom view (typically full-screen)
*/
@SuppressWarnings("unused")
public boolean isVideoFullscreen()
{
return videoJsHelper.isVideoFullscreen();
}
/**
* Pass a VideoEnabledWebChromeClient instance to enable video full-screen.
*/
@Override @SuppressLint("SetJavaScriptEnabled")
public void setWebChromeClient(WebChromeClient client)
{
getSettings().setJavaScriptEnabled(true);
videoJsHelper.setWebChromeClient(client);
super.setWebChromeClient(client);
}
@Override
public void loadData(String data, String mimeType, String encoding)
{
videoJsHelper.addJavascriptInterface(this);
super.loadData(data, mimeType, encoding);
}
@Override
public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl)
{
videoJsHelper.addJavascriptInterface(this);
super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl);
}
@Override
public void loadUrl(String url)
{
videoJsHelper.addJavascriptInterface(this);
super.loadUrl(url);
}
@Override
public void loadUrl(String url, Map<String, String> additionalHttpHeaders)
{
videoJsHelper.addJavascriptInterface(this);
super.loadUrl(url, additionalHttpHeaders);
}
}
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="1.5"/>
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="2.0"/>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<accelerateInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="2.5"/>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/black"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="250"
android:fromAlpha="0.2"
android:interpolator="@anim/accelerate_cubic"
android:toAlpha="1.0"/>
<scale
android:duration="250"
android:fromXScale="0.9"
android:fromYScale="0.9"
android:interpolator="@anim/accelerate_cubic"
android:pivotX="50.0%p"
android:pivotY="50.0%p"
android:toXScale="1.0"
android:toYScale="1.0"/>
</set>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
<translate
android:duration="250"
android:fromXDelta="0.0%p"
android:interpolator="@anim/accelerate_cubic"
android:toXDelta="100.0%p"/>
</set>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
<translate
android:duration="250"
android:fromXDelta="100.0%p"
android:interpolator="@anim/decelerate_cubic"
android:toXDelta="0.0%p"/>
</set>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/black"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:shareInterpolator="false"
android:zAdjustment="normal">
<alpha
android:duration="250"
android:fromAlpha="1.0"
android:interpolator="@anim/decelerate_cubic"
android:toAlpha="0.2"/>
<scale
android:duration="250"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@anim/decelerate_cubic"
android:pivotX="50.0%p"
android:pivotY="50.0%p"
android:toXScale="0.9"
android:toYScale="0.9"/>
</set>
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<decelerateInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="1.5"/>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<decelerateInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="2.0"/>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<decelerateInterpolator
xmlns:android="http://schemas.android.com/apk/res/android"
android:factor="2.5"/>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="250"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="400"
android:fromAlpha="0.0"
android:toAlpha="1.0"/>
</set>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="250"
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="800"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_interpolator"
android:toAlpha="0.0"/>
</set>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="150"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:fromAlpha="1.0"
android:interpolator="@anim/accelerate_quart"
android:startOffset="100"
android:toAlpha="0.0"/>
<translate
android:duration="250"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:fromYDelta="0.0%"
android:interpolator="@anim/accelerate_quint"
android:toYDelta="-4.999995%"/>
</set>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="150"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:fromAlpha="1.0"
android:interpolator="@anim/accelerate_quart"
android:startOffset="100"
android:toAlpha="0.0"/>
<translate
android:duration="250"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:fromYDelta="0.0%"
android:interpolator="@anim/accelerate_quint"
android:toYDelta="4.999995%"/>
</set>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="200"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:fromAlpha="0.0"
android:interpolator="@anim/decelerate_quart"
android:toAlpha="1.0"/>
<translate
android:duration="350"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:fromYDelta="8.000004%"
android:interpolator="@anim/decelerate_quint"
android:toYDelta="0.0"/>
</set>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="200"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:fromAlpha="0.0"
android:interpolator="@anim/decelerate_quart"
android:toAlpha="1.0"/>
<translate
android:duration="350"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:fromYDelta="-8.000004%"
android:interpolator="@anim/decelerate_quint"
android:toYDelta="0.0"/>
</set>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="800"
android:fromYDelta="0.0%p"
android:toYDelta="0.0%p"/>
</set>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/black"
android:fillAfter="true"
android:fillBefore="true"
android:fillEnabled="true"
android:shareInterpolator="false"
android:zAdjustment="top">
<alpha
android:duration="250"
android:fromAlpha="0.2"
android:interpolator="@anim/accelerate_cubic"
android:toAlpha="1.0"/>
<scale
android:duration="250"
android:fromXScale="0.9"
android:fromYScale="0.9"
android:interpolator="@anim/accelerate_cubic"
android:pivotX="50.0%p"
android:pivotY="50.0%p"
android:toXScale="1.0"
android:toYScale="1.0"/>
</set>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
<translate
android:duration="250"
android:fromYDelta="0.0%p"
android:interpolator="@anim/accelerate_cubic"
android:toYDelta="100.0%p"/>
</set>
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:zAdjustment="top">
<translate
android:duration="250"
android:fromYDelta="100.0%p"
android:interpolator="@anim/decelerate_cubic"
android:toYDelta="0.0%p"/>
</set>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/black"
android:fillAfter="true"
android:fillBefore="false"
android:fillEnabled="true"
android:shareInterpolator="false"
android:zAdjustment="normal">
<alpha
android:duration="250"
android:fromAlpha="1.0"
android:interpolator="@anim/decelerate_cubic"
android:toAlpha="0.2"/>
<scale
android:duration="250"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:interpolator="@anim/decelerate_cubic"
android:pivotX="50.0%p"
android:pivotY="50.0%p"
android:toXScale="0.9"
android:toYScale="0.9"/>
</set>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="0"
android:fromAlpha="1.0"
android:interpolator="@android:anim/decelerate_interpolator"
android:toAlpha="1.0"/>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="125">
<scale
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="100%"
android:pivotY="0%"
android:toXScale="0.15"
android:toYScale="0.0"/>
<alpha
android:fromAlpha="1.0"
android:toAlpha="0.0"/>
</set>
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="125">
<scale
android:fromXScale="0.15"
android:fromYScale="0.0"
android:pivotX="100%"
android:pivotY="0%"
android:toXScale="1.0"
android:toYScale="1.0"/>
<alpha
android:fromAlpha="0.2"
android:startOffset="60"
android:toAlpha="1.0"/>
</set>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:fromYDelta="0.0%"
android:toYDelta="100.0%"/>
</set>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="100.0%p"
android:toXDelta="0.0"/>
</set>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="300"
android:fromXDelta="0.0%"
android:toXDelta="100.0%p"/>
</set>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<set
xmlns:android="http://schemas.android.com/apk/res/android">
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="350"
android:fromYDelta="100.0%"
android:toYDelta="0.0%"/>
</set>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/finestWhite30">
<item android:drawable="@drawable/selector_dark_theme_holo"/>
</ripple>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/finestWhite10"/>
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/finestWhite10"/>
</shape>
</item>
<item android:drawable="@android:color/transparent"/>
</selector>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/finestBlack30">
<item android:drawable="@drawable/selector_light_theme_holo"/>
</ripple>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/finestGray10"/>
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/finestGray10"/>
</shape>
</item>
<item android:drawable="@android:color/transparent"/>
</selector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:viewportHeight="144.0"
android:viewportWidth="144.0"
android:width="48dp">
<path
android:fillColor="#FF000000"
android:pathData="M84.5,89.3L64.4,70.5l20.2,-18.8c1.2,-1.1 1.3,-3 0.2,-4.2c-1.1,-1.2 -3,-1.3 -4.2,-0.2L58,68.3c-0.6,0.6 -1,1.4 -1,2.2c0,0.8 0.3,1.6 1,2.2l22.5,21c1.2,1.1 3.1,1.1 4.2,-0.1C85.8,92.3 85.8,90.4 84.5,89.3L84.5,89.3z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:viewportHeight="144.0"
android:viewportWidth="144.0"
android:width="48dp">
<path
android:fillColor="#FF000000"
android:pathData="M76.8,72l16.3,-15.8c1.2,-1.2 1.2,-3.1 0.1,-4.2c-1.2,-1.2 -3.1,-1.2 -4.2,-0.1l-16.4,16l-16.4,-16c-1.2,-1.2 -3.1,-1.1 -4.2,0.1c-1.2,1.2 -1.1,3.1 0.1,4.2L68.2,72L51.9,87.8c-1.2,1.2 -1.2,3.1 -0.1,4.2c1.2,1.2 3.1,1.2 4.2,0.1l16.4,-16l16.4,16c1.2,1.2 3.1,1.1 4.2,-0.1c1.2,-1.2 1.1,-3.1 -0.1,-4.2L76.8,72z"/>
</vector>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:viewportHeight="144.0"
android:viewportWidth="144.0"
android:width="48dp">
<path
android:fillColor="#FF000000"
android:pathData="M65.5,93.7l22.5,-21c0.6,-0.6 1,-1.4 1,-2.2c0,-0.8 -0.3,-1.6 -1,-2.2l-22.6,-21c-1.2,-1.1 -3.1,-1.1 -4.2,0.2c-1.1,1.2 -1.1,3.1 0.2,4.2l20.2,18.8L61.5,89.3c-1.2,1.1 -1.3,3 -0.1,4.2C62.4,94.8 64.3,94.8 65.5,93.7L65.5,93.7z"/>
</vector>
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="48dp"
android:viewportHeight="144.0"
android:viewportWidth="144.0"
android:width="48dp">
<path
android:fillColor="#FF000000"
android:pathData="M72,54m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M72,72m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
<path
android:fillColor="#FF000000"
android:pathData="M72,90m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0"/>
</vector>
@@ -0,0 +1,12 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<color android:color="@android:color/transparent"/>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<solid android:color="@android:color/black"/>
</shape>
</clip>
</item>
</layer-list>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/finestWhite20"/>
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/finestWhite20"/>
</shape>
</item>
<item android:drawable="@android:color/transparent"/>
</selector>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/finestGray20"/>
</shape>
</item>
<item android:state_focused="true">
<shape>
<solid android:color="@color/finestGray20"/>
</shape>
</item>
<item android:drawable="@android:color/transparent"/>
</selector>
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/finestWhite">
<FrameLayout
android:id="@+id/webLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/toolbarHeight" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="0dp"
app:contentInsetEnd="0dp"
app:contentInsetLeft="0dp"
app:contentInsetRight="0dp"
app:contentInsetStart="0dp">
<include layout="@layout/toolbar_content" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<View
android:id="@+id/gradient"
android:layout_width="match_parent"
android:layout_height="@dimen/defaultDividerHeight"
android:layout_marginTop="@dimen/toolbarHeight"
android:background="@color/finestBlack10" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/defaultProgressBarHeight"
android:layout_marginTop="@dimen/toolbarHeight"
android:indeterminate="false"
android:progressDrawable="@drawable/progress_drawable" />
<include layout="@layout/menus" />
<!-- View where the video will be shown when video goes fullscreen -->
<RelativeLayout
android:id="@+id/videoLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"
android:visibility="invisible">
<!-- View that will be shown while the fullscreen video loads. For example you could include
a "Loading..." message. However, in this particular example this view is actually defined in
view_loading_video.xml and inflated programmatically in ExampleActivity.java. The reason for
this is to keep the main layout (this file) cleaner. -->
<!-- <View
android:id="@+id/videoLoading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:visibility="invisible" /> -->
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
+145
View File
@@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/menuLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:onClick="onClick"
android:orientation="vertical"
android:visibility="gone">
<com.wuadam.awesomewebview.views.ShadowLayout
android:id="@+id/shadowLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:clipChildren="false"
android:clipToPadding="false"
app:slCornerRadius="@dimen/defaultMenuDropShadowCornerRadius"
app:slShadowColor="@color/finestBlack10"
app:slShadowSize="@dimen/defaultMenuDropShadowSize">
<LinearLayout
android:id="@+id/menuBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp"
android:paddingTop="8dp">
<LinearLayout
android:id="@+id/menuRefresh"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/selector_light_theme"
android:gravity="center_vertical|left"
android:onClick="onClick">
<TextView
android:id="@+id/menuRefreshTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:singleLine="true"
android:text="@string/refresh"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultMenuTextSize" />
</LinearLayout>
<LinearLayout
android:id="@+id/menuFind"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/selector_light_theme"
android:gravity="center_vertical|left"
android:onClick="onClick">
<TextView
android:id="@+id/menuFindTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:singleLine="true"
android:text="@string/find"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultMenuTextSize" />
</LinearLayout>
<LinearLayout
android:id="@+id/menuShareVia"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/selector_light_theme"
android:gravity="center_vertical|left"
android:onClick="onClick">
<TextView
android:id="@+id/menuShareViaTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:singleLine="true"
android:text="@string/share_via"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultMenuTextSize" />
</LinearLayout>
<LinearLayout
android:id="@+id/menuCopyLink"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/selector_light_theme"
android:gravity="center_vertical|left"
android:onClick="onClick">
<TextView
android:id="@+id/menuCopyLinkTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:singleLine="true"
android:text="@string/copy_link"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultMenuTextSize" />
</LinearLayout>
<LinearLayout
android:id="@+id/menuOpenWith"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/selector_light_theme"
android:gravity="center_vertical|left"
android:onClick="onClick">
<TextView
android:id="@+id/menuOpenWithTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:singleLine="true"
android:text="@string/open_with"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultMenuTextSize" />
</LinearLayout>
</LinearLayout>
</com.wuadam.awesomewebview.views.ShadowLayout>
</RelativeLayout>
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/toolbarLayout"
android:layout_width="match_parent"
android:layout_height="@dimen/toolbarHeight"
android:minHeight="@dimen/toolbarHeight">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/close"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:background="@drawable/selector_light_theme"
android:src="@drawable/close" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/back"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_toLeftOf="@+id/centerLayout"
android:background="@drawable/selector_light_theme"
android:src="@drawable/back"
android:visibility="gone" />
<LinearLayout
android:id="@id/centerLayout"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="1.5dp"
android:layout_marginTop="1.5dp"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:singleLine="true"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultTitleSize" />
<TextView
android:id="@+id/url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="1.5dp"
android:layout_marginTop="1.5dp"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:singleLine="true"
android:textColor="@color/finestSilver"
android:textSize="@dimen/defaultUrlSize" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/forward"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_toRightOf="@+id/centerLayout"
android:background="@drawable/selector_light_theme"
android:src="@drawable/forward"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/more"
android:layout_width="48dp"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:background="@drawable/selector_light_theme"
android:src="@drawable/more" />
</RelativeLayout>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="@dimen/defaultDividerHeight"
android:background="@color/finestSilver" />
</LinearLayout>
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/customMenuLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/selector_light_theme"
android:gravity="center_vertical|left">
<TextView
android:id="@+id/customMenu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:singleLine="true"
android:text="@string/copy_link"
android:textColor="@color/finestBlack"
android:textSize="@dimen/defaultMenuTextSize" />
</LinearLayout>
</FrameLayout>
@@ -0,0 +1,31 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_centerInParent="true" >
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminate="true"
style="?android:attr/progressBarStyleLarge" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="24dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="#a3a3a3"
android:text="@string/loading_video" />
</LinearLayout>
</RelativeLayout>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="refresh">Recargar</string>
<string name="find">Encontrar</string>
<string name="share_via">Compartir</string>
<string name="copy_link">Copiar enlace</string>
<string name="open_with">Abrir con</string>
<string name="copied_to_clipboard">Copiado al portapapeles</string>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="refresh">새로고침</string>
<string name="find">찾기</string>
<string name="share_via">주소 공유하기</string>
<string name="copy_link">주소 복사하기</string>
<string name="open_with">다른 앱으로 열기</string>
<string name="copied_to_clipboard">주소가 복사 되었습니다</string>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="toolbarHeight">48dp</dimen>
<dimen name="menusWidth">224dp</dimen>
</resources>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="is_right_to_left">true</bool>
</resources>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="refresh">Atualizar</string>
<string name="find">Procurar</string>
<string name="share_via">Compartilhar</string>
<string name="copy_link">Copiar link</string>
<string name="open_with">Abrir com</string>
<string name="copied_to_clipboard">Copiado para a área de transferência</string>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="is_right_to_left">false</bool>
</resources>
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="finestWhite10">#1AFFFFFF</color>
<color name="finestWhite20">#33FFFFFF</color>
<color name="finestWhite30">#4DFFFFFF</color>
<color name="finestGray10">#1AD2D2D2</color>
<color name="finestGray20">#33D2D2D2</color>
<color name="finestBlack05">#0D202020</color>
<color name="finestBlack10">#1A202020</color>
<color name="finestBlack20">#33202020</color>
<color name="finestBlack30">#4D202020</color>
<color name="finestBlack40">#66202020</color>
<color name="finestWhite">#FFFFFF</color>
<color name="finestBlack">#000000</color>
<color name="finestSilver">#A5A5A5</color>
<color name="finestGray">#E7E7E7</color>
</resources>
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="toolbarHeight">56dp</dimen>
<dimen name="menusWidth">168dp</dimen>
<dimen name="defaultDividerHeight">4dp</dimen>
<dimen name="defaultProgressBarHeight">1dp</dimen>
<dimen name="defaultTitleSize">14dp</dimen>
<dimen name="defaultUrlSize">10dp</dimen>
<dimen name="defaultMenuDropShadowCornerRadius">2dp</dimen>
<dimen name="defaultMenuDropShadowSize">4dp</dimen>
<dimen name="defaultMenuTextSize">16dp</dimen>
<dimen name="defaultMenuTextPaddingLeft">16dp</dimen>
<dimen name="defaultMenuTextPaddingRight">32dp</dimen>
<dimen name="defaultMenuLayoutMargin">4dp</dimen>
<dimen name="defaultMenuCornerRadius">2dp</dimen>
</resources>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="refresh">Refresh</string>
<string name="find">Find</string>
<string name="share_via">Share via</string>
<string name="copy_link">Copy Link</string>
<string name="open_with">Open with</string>
<string name="save_photo">Save Photo</string>
<string name="photo_saved_to">Photo has been saved to </string>
<string name="photo_save_failed">Photo save failed</string>
<string name="file_chooser">File Chooser</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="loading_video">Loading video…</string>
</resources>
+39
View File
@@ -0,0 +1,39 @@
<resources>
<style name="FinestWebViewTheme" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/finestWhite</item>
<item name="colorPrimaryDark">@color/finestGray</item>
<item name="colorAccent">@color/finestBlack</item>
<item name="android:textColorPrimary">@color/finestBlack</item>
<item name="android:textColorSecondary">@color/finestSilver</item>
<item name="windowActionModeOverlay">true</item>
</style>
<style name="FinestWebViewTheme.Fullscreen" parent="FinestWebViewTheme">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowFullscreen">true</item>
</style>
<style name="FinestWebViewTheme.Light" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/finestWhite</item>
<item name="colorPrimaryDark">@color/finestGray</item>
<item name="colorAccent">@color/finestBlack</item>
<item name="android:textColorPrimary">@color/finestBlack</item>
<item name="android:textColorSecondary">@color/finestSilver</item>
<item name="windowActionModeOverlay">true</item>
</style>
<style name="FinestWebViewTheme.Light.Fullscreen" parent="FinestWebViewTheme.Light">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowFullscreen">true</item>
</style>
<declare-styleable name="ShadowLayout">
<attr format="dimension" name="slCornerRadius"/>
<attr format="dimension" name="slShadowSize"/>
<attr format="color" name="slShadowColor"/>
<attr format="dimension" name="slDx"/>
<attr format="dimension" name="slDy"/>
</declare-styleable>
</resources>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="external_files"
path="." />
</paths>