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

1
annotations/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

15
annotations/build.gradle Normal file
View File

@ -0,0 +1,15 @@
apply plugin: 'java'
//apply plugin: 'com.novoda.bintray-release'
//publish {
// userOrg = 'thefinestartist'
// groupId = 'com.thefinestartist'
// artifactId = 'annotations'
// publishVersion = rootProject.ext.versionName
// desc = 'Context free and basic utils to build Android project conveniently.'
// website = 'https://github.com/TheFinestArtist/AndroidBaseUtils'
//}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@ -102,6 +102,8 @@ dependencies {
implementation("com.squareup.retrofit2:converter-scalars:2.6.4") implementation("com.squareup.retrofit2:converter-scalars:2.6.4")
implementation("androidx.viewpager2:viewpager2:1.0.0") implementation("androidx.viewpager2:viewpager2:1.0.0")
implementation("com.squareup.picasso:picasso:2.71828") implementation("com.squareup.picasso:picasso:2.71828")
implementation("com.github.delight-im:Android-AdvancedWebView:v3.2.1")
implementation(project(":library"))
// implementation ("me.everything:providers-android:1.0.1") // implementation ("me.everything:providers-android:1.0.1")
// implementation ("me.everything:providers-core:1.0.1") // implementation ("me.everything:providers-core:1.0.1")
// implementation ("androidx.window:window:1.0.0") // implementation ("androidx.window:window:1.0.0")

View File

@ -120,6 +120,8 @@
</intent-filter> </intent-filter>
</activity> </activity>
<service <service
android:name=".feeds.rss.RssService" android:name=".feeds.rss.RssService"
android:permission="android.permission.BIND_JOB_SERVICE" android:permission="android.permission.BIND_JOB_SERVICE"
@ -151,9 +153,15 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<activity
android:name="com.wuadam.awesomewebview.AwesomeWebViewActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:hardwareAccelerated="true"
android:theme="@style/FinestWebViewTheme.Light" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="bums.lunatic.launcher.fileprovider"
android:exported="false" android:exported="false"
android:enabled="true" android:enabled="true"
android:grantUriPermissions="true"> android:grantUriPermissions="true">

View File

@ -0,0 +1,121 @@
/*
* Lunar Launcher
* Copyright (C) 2022 Md Rasel Hossain
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package bums.lunatic.launcher.home
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Environment
import android.print.PDFPrint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.FileProvider
import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
import bums.lunatic.launcher.R
import bums.lunatic.launcher.databinding.RssViewerBinding
import bums.lunatic.launcher.utils.BLog
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import java.io.File
import java.io.IOException
internal class RssViewer : DialogFragment() {
private lateinit var binding: RssViewerBinding
private lateinit var packageName: String
private lateinit var packageManager: PackageManager
private lateinit var defAppName: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.FilterFullScreenDialog)
}
override fun onStart() {
super.onStart()
if (dialog != null) {
// val metrics = resources.displayMetrics
// val screenHeight = metrics.heightPixels
// val bottomSheet =
// dialog!!.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
// bottomSheet.layoutParams.height = (screenHeight * 0.95).toInt()
// val behavior = BottomSheetBehavior.from(bottomSheet)
// behavior.state = BottomSheetBehavior.STATE_EXPANDED
// behavior.skipCollapsed = true
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = RssViewerBinding.inflate(inflater, container, false)
binding.webview.loadUrl(tag!!)
binding.webview.setDesktopMode(false)
binding.pdfPrint.setOnClickListener { pdfPring() }
return binding.root
}
fun pdfPring() {
val fileName = tag?.toUri()?.path?.replace("/","_")?.replace(".","_")
val path = File(Environment.getExternalStorageDirectory(),"bums")
if (path.exists() == false) {
path.mkdirs()
}
val file = File(path, fileName.plus(".pdf"))
BLog.LOGE("file >>> ${file.absolutePath}")
try {
PDFPrint.generatePDFFromWebView(file,binding.webview, object : PDFPrint.OnPDFPrintListener {
override fun onSuccess(file: File?) {
BLog.LOGE("file >>>> ${file!!.absolutePath}")
val shareIntent: Intent = Intent().apply {
action = Intent.ACTION_SEND
this.`package` = "com.synology.dsdrive"
val imageUri = FileProvider.getUriForFile(
lActivity!!,
"bums.lunatic.launcher.fileprovider", //(use your app signature + ".provider" )
file
)
putExtra(Intent.EXTRA_STREAM, imageUri)
type = "pdf"
}
lActivity!!.startActivity(shareIntent)
}
override fun onError(exception: java.lang.Exception?) {
Toast.makeText(lActivity!!,
"Pdf Save Failk ${exception?.localizedMessage}", Toast.LENGTH_LONG).show()
exception?.printStackTrace()
}
} )
} catch (e: IOException) {
e.printStackTrace()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// (requireDialog() as BottomSheetDialog).dismissWithAnimation = true
}
}

View File

@ -28,14 +28,13 @@ import androidx.core.net.toUri
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
import bums.lunatic.launcher.R import bums.lunatic.launcher.R
import bums.lunatic.launcher.databinding.ListItemWithBinding import bums.lunatic.launcher.databinding.ListItemWithBinding
import bums.lunatic.launcher.home.RssViewer
import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataInterface import bums.lunatic.launcher.model.RssDataInterface
import bums.lunatic.launcher.model.RssDataType import bums.lunatic.launcher.model.RssDataType
import bums.lunatic.launcher.openDotax
import bums.lunatic.launcher.openNews
import bums.lunatic.launcher.openOpera
import bums.lunatic.launcher.openReddit import bums.lunatic.launcher.openReddit
import bums.lunatic.launcher.openYouTube import bums.lunatic.launcher.openYouTube
import bums.lunatic.launcher.utils.BLog import bums.lunatic.launcher.utils.BLog
@ -43,12 +42,13 @@ import bums.lunatic.launcher.workers.WorkersDb
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import com.google.gson.Gson import com.google.gson.Gson
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import com.wuadam.awesomewebview.AwesomeWebView
import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.UpdatePolicy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
internal class RssItemAdapter (
internal class RssItemAdapter (
private val context: Context) : RecyclerView.Adapter<RssHolder>() { private val context: Context) : RecyclerView.Adapter<RssHolder>() {
companion object { companion object {
@SuppressLint("SimpleDateFormat") @SuppressLint("SimpleDateFormat")
@ -74,16 +74,39 @@ internal class RssItemAdapter (
} else { } else {
if (RssDataType.REDDIT_NSFW.equals(rss.category())) { if (RssDataType.REDDIT_NSFW.equals(rss.category())) {
openReddit(rss.originPage()) openReddit(rss.originPage())
// RssViewer().apply {
// show(lActivity!!.supportFragmentManager,rss.originPage)
// }
} else { } else {
openOpera(rss.originPage()) RssViewer().apply {
show(lActivity!!.supportFragmentManager,rss.originPage)
}
// openOpera(rss.originPage())
} }
} }
} }
} }
RssDataType.REDDIT -> { openReddit(rss.originPage()) } RssDataType.REDDIT -> {
RssDataType.DOTAX -> { openDotax(rss.originPage()) } RssViewer().apply {
RssDataType.YOUTUBE -> { openYouTube(rss.originPage()) } show(lActivity!!.supportFragmentManager,rss.originPage)
else -> { openNews(rss.originPage()) } }
// openReddit(rss.originPage())
}
RssDataType.DOTAX -> {
RssViewer().apply {
AwesomeWebView.Builder(lActivity!!).show(rss.originPage!!)
// show(lActivity!!.supportFragmentManager,rss.originPage)
}
// openDotax(rss.originPage())
}
RssDataType.YOUTUBE -> { openYouTube(rss.originPage())
}
else -> {
RssViewer().apply {
show(lActivity!!.supportFragmentManager,rss.originPage)
}
// openNews(rss.originPage())
}
} }
} }
} }

View File

@ -19,12 +19,12 @@ object RssList {
) )
val newsFeeds = arrayListOf( val newsFeeds = arrayListOf(
"https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko", "https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko",
"https://imnews.imbc.com/rss/google_news/narrativeNews.rss", // "https://imnews.imbc.com/rss/google_news/narrativeNews.rss",
"http://www.hani.co.kr/rss", "http://www.hani.co.kr/rss",
"http://biz.heraldcorp.com/common_prog/rssdisp.php?ct=010000000000.xml", "http://biz.heraldcorp.com/common_prog/rssdisp.php?ct=010000000000.xml",
"https://rss.inews24.com/rss/news_inews.xml", // "https://rss.inews24.com/rss/news_inews.xml",
"https://rss.nocutnews.co.kr/category/it.xml", // "https://rss.nocutnews.co.kr/category/it.xml",
"https://rss.nocutnews.co.kr/news/news.xml", // "https://rss.nocutnews.co.kr/news/news.xml",
"https://rss.nocutnews.co.kr/news/top.xml", "https://rss.nocutnews.co.kr/news/top.xml",
) )

View File

@ -26,12 +26,12 @@ class ArcaGetter : BaseGetter {
temp.clear() temp.clear()
val urls = arrayListOf( val urls = arrayListOf(
"https://arca.live/b/singbung?mode=best", "https://arca.live/b/singbung?mode=best",
// "https://arca.live/b/headline", "https://arca.live/b/headline",
// "https://arca.live/b/live", // "https://arca.live/b/live",
"https://arca.live/b/namuhotnow", "https://arca.live/b/namuhotnow",
"https://arca.live/b/society", "https://arca.live/b/society",
// "https://arca.live/b/replay", // "https://arca.live/b/replay",
// "https://arca.live/b/breaking" "https://arca.live/b/breaking"
) )
urls.forEach { urls.forEach {
Jsoup.connect(it) Jsoup.connect(it)

View File

@ -70,7 +70,7 @@ class ClienGetter : BaseGetter {
RssDataType.CLIEN.isOn { RssDataType.CLIEN.isOn {
try { try {
temp.clear() temp.clear()
val testUrl2 = arrayListOf("https://www.clien.net/service/group/community") val testUrl2 = arrayListOf("https://www.clien.net/service/group/community","https://www.clien.net/service/board/park","https://www.clien.net/service/board/news","https://www.clien.net/service/board/useful","https://www.clien.net/service/board/pds")
testUrl2.forEach { url -> testUrl2.forEach { url ->
Jsoup.connect(url) Jsoup.connect(url)
.userAgent(USAGT) .userAgent(USAGT)

View File

@ -23,7 +23,7 @@ class DotaxGetter : BaseGetter {
temp.clear() temp.clear()
val dotaxUrls = arrayListOf("https://m.cafe.daum.net/dotax", val dotaxUrls = arrayListOf("https://m.cafe.daum.net/dotax",
"https://m.cafe.daum.net/dotax/_rec?page=2", "https://m.cafe.daum.net/dotax/_rec?page=2",
"https://m.cafe.daum.net/dotax/_rec?page=3" // "https://m.cafe.daum.net/dotax/_rec?page=3"
) )
dotaxUrls?.forEach { dotaxUrls?.forEach {
Jsoup.connect(it).userAgent(USAGT).get()?.let { dotax -> Jsoup.connect(it).userAgent(USAGT).get()?.let { dotax ->

View File

@ -48,7 +48,7 @@ class TheQooGetter : BaseGetter {
override fun realWork(): Result { override fun realWork(): Result {
RssDataType.THEQOO.isOn { RssDataType.THEQOO.isOn {
try { try {
val testUrl2 = arrayListOf("https://theqoo.net/hot") val testUrl2 = arrayListOf("https://theqoo.net/hot","https://theqoo.net/hot/category/512000937","https://theqoo.net/hot/category/24784","https://theqoo.net/hot/category/24788")
testUrl2.forEach { url -> testUrl2.forEach { url ->
Jsoup.connect(url) Jsoup.connect(url)
.userAgent(USAGT) .userAgent(USAGT)

View File

@ -85,7 +85,9 @@ object WorkersDb {
try { try {
getRealm().writeBlocking { getRealm().writeBlocking {
try { try {
if(query<RssData>("chosung == $0",it.chosung).find().size == 0) {
this.copyToRealm(it, UpdatePolicy.ERROR) this.copyToRealm(it, UpdatePolicy.ERROR)
}
} catch (e : Exception) { } catch (e : Exception) {
} }

View File

@ -62,10 +62,6 @@
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:layout_gravity="bottom|center_horizontal" android:layout_gravity="bottom|center_horizontal"
android:id="@+id/calendarDateView" android:id="@+id/calendarDateView"
@ -77,6 +73,10 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_behavior="bums.lunatic.launcher.behavior.behaviorimpl.BehaviorRecT" app:layout_behavior="bums.lunatic.launcher.behavior.behaviorimpl.BehaviorRecT"
/> />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_main" android:id="@+id/rv_main"

View File

@ -228,12 +228,16 @@
android:visibility="gone" android:visibility="gone"
android:background="@drawable/base_bg" android:background="@drawable/base_bg"
android:scrollbars="none" android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
android:layout_alignParentTop="true" android:layout_alignParentTop="true"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_alignParentBottom="true" android:layout_alignParentBottom="true"
/> />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
android:layout_margin="@dimen/default_layout_margin" android:layout_margin="@dimen/default_layout_margin"
android:id="@+id/smsList" android:id="@+id/smsList"
android:layout_width="0dp" android:layout_width="0dp"
@ -251,6 +255,7 @@
/> />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_margin="@dimen/default_layout_margin" android:layout_margin="@dimen/default_layout_margin"
android:id="@+id/infoList" android:id="@+id/infoList"
android:layout_width="0dp" android:layout_width="0dp"
@ -269,6 +274,7 @@
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:layout_margin="@dimen/default_layout_margin" android:layout_margin="@dimen/default_layout_margin"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:id="@+id/notiList" android:id="@+id/notiList"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:background="#22000000"
android:padding="@dimen/default_padding"
android:clickable="true"
android:focusableInTouchMode="true">
<im.delight.android.webview.AdvancedWebView
android:layout_margin="@dimen/default_layout_margin"
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageButton
app:layout_constraintTop_toBottomOf="@id/webview"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/pdf_print"
android:src="@drawable/ic_down"
android:layout_width="60dp"
android:layout_height="60dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -88,4 +88,11 @@
<item name="fontFamily">sans-serif-light</item> <item name="fontFamily">sans-serif-light</item>
</style> </style>
<style name="FilterFullScreenDialog" parent="Theme.AppCompat.Dialog">
<item name="android:windowIsFloating">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:statusBarColor">#22000000</item>
</style>
</resources> </resources>

View File

@ -17,6 +17,24 @@ plugins {
tasks.register<Delete>("clean") { tasks.register<Delete>("clean") {
delete(rootProject.buildDir) delete(rootProject.buildDir)
} }
ext {
// minSdkVersion = 7
// targetSdkVersion = 23
// compileSdkVersion = 23
// buildToolsVersion = '23.0.2'
//
// sourceCompatibility = JavaVersion.VERSION_1_7
// targetCompatibility = JavaVersion.VERSION_1_7
//
// versionCode = 1
// versionName = '0.9.5'
//
// supportLibVersion = '23.3.0'
// playLibVersion = '8.4.0'
}
//repositories { //repositories {
// mavenCentral() // mavenCentral()
// maven { // maven {

View File

@ -24,3 +24,4 @@ android.useAndroidX=true
android.nonTransitiveRClass=true android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=true android.nonFinalResIds=true
android.enableJetifier=true

2
library/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
*.iml

42
library/build.gradle Normal file
View File

@ -0,0 +1,42 @@
apply plugin: 'com.android.library'
apply from: 'maven_publish.gradle'
android {
compileSdk 34
defaultConfig {
minSdk 14
//noinspection ExpiredTargetSdkVersion
targetSdk 31
vectorDrawables.useSupportLibrary = true
consumerProguardFiles 'proguard-rules.pro'
}
namespace 'com.wuadam.awesomewebview'
lint {
abortOnError false
}
// https://developer.android.com/build/publish-library/configure-pub-variants?hl=zh-cn#single-pub-var
// https://stackoverflow.com/a/71366104
publishing {
singleVariant('release') {
withSourcesJar()
withJavadocJar()
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.annotation:annotation:1.7.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.10.0'
implementation 'com.nineoldandroids:library:2.4.0'
implementation project(':utils')
// implementation("com.thefinestartist:utils:0.9.5")
// api ('com.yanzhenjie:permission:2.0.3') {
// exclude group: 'com.android.support'
// }
}

View File

@ -0,0 +1,16 @@
apply plugin: 'maven-publish'
// https://developer.android.com/build/publish-library/upload-library?hl=zh-cn#create-pub
publishing {
publications {
release(MavenPublication) {
groupId = 'com.github.hzw1199'
artifactId = 'AwesomeWebView-Android'
version = '2.1.0'
afterEvaluate {
from components.release
}
}
}
}

25
library/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Leonardo/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# AndPermission
-dontwarn com.yanzhenjie.permission.**
# JavascriptInterface
-keepclassmembers class com.wuadam.awesomewebview.helpers.VideoJsHelper$JavascriptInterface {
public *;
}

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

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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 {
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -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) {
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

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"/>

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"/>

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"/>

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>

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>

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>

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>

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"/>

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"/>

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"/>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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"/>

View File

@ -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>

View File

@ -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>

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>

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="300"
android:fromXDelta="100.0%p"
android:toXDelta="0.0"/>
</set>

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="300"
android:fromXDelta="0.0%"
android:toXDelta="100.0%p"/>
</set>

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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Some files were not shown because too many files have changed in this diff Show More