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("androidx.viewpager2:viewpager2:1.0.0")
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-core:1.0.1")
// implementation ("androidx.window:window:1.0.0")

View File

@ -120,6 +120,8 @@
</intent-filter>
</activity>
<service
android:name=".feeds.rss.RssService"
android:permission="android.permission.BIND_JOB_SERVICE"
@ -151,9 +153,15 @@
</intent-filter>
</receiver>
<activity
android:name="com.wuadam.awesomewebview.AwesomeWebViewActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:hardwareAccelerated="true"
android:theme="@style/FinestWebViewTheme.Light" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:authorities="bums.lunatic.launcher.fileprovider"
android:exported="false"
android:enabled="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.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
import bums.lunatic.launcher.R
import bums.lunatic.launcher.databinding.ListItemWithBinding
import bums.lunatic.launcher.home.RssViewer
import bums.lunatic.launcher.model.RssData
import bums.lunatic.launcher.model.RssDataInterface
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.openYouTube
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.gson.Gson
import com.squareup.picasso.Picasso
import com.wuadam.awesomewebview.AwesomeWebView
import io.realm.kotlin.UpdatePolicy
import java.text.SimpleDateFormat
import java.util.Date
internal class RssItemAdapter (
internal class RssItemAdapter (
private val context: Context) : RecyclerView.Adapter<RssHolder>() {
companion object {
@SuppressLint("SimpleDateFormat")
@ -74,16 +74,39 @@ internal class RssItemAdapter (
} else {
if (RssDataType.REDDIT_NSFW.equals(rss.category())) {
openReddit(rss.originPage())
// RssViewer().apply {
// show(lActivity!!.supportFragmentManager,rss.originPage)
// }
} else {
openOpera(rss.originPage())
RssViewer().apply {
show(lActivity!!.supportFragmentManager,rss.originPage)
}
// openOpera(rss.originPage())
}
}
}
}
RssDataType.REDDIT -> { openReddit(rss.originPage()) }
RssDataType.DOTAX -> { openDotax(rss.originPage()) }
RssDataType.YOUTUBE -> { openYouTube(rss.originPage()) }
else -> { openNews(rss.originPage()) }
RssDataType.REDDIT -> {
RssViewer().apply {
show(lActivity!!.supportFragmentManager,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(
"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://biz.heraldcorp.com/common_prog/rssdisp.php?ct=010000000000.xml",
"https://rss.inews24.com/rss/news_inews.xml",
"https://rss.nocutnews.co.kr/category/it.xml",
"https://rss.nocutnews.co.kr/news/news.xml",
// "https://rss.inews24.com/rss/news_inews.xml",
// "https://rss.nocutnews.co.kr/category/it.xml",
// "https://rss.nocutnews.co.kr/news/news.xml",
"https://rss.nocutnews.co.kr/news/top.xml",
)

View File

@ -26,12 +26,12 @@ class ArcaGetter : BaseGetter {
temp.clear()
val urls = arrayListOf(
"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/namuhotnow",
"https://arca.live/b/society",
// "https://arca.live/b/replay",
// "https://arca.live/b/breaking"
"https://arca.live/b/breaking"
)
urls.forEach {
Jsoup.connect(it)

View File

@ -70,7 +70,7 @@ class ClienGetter : BaseGetter {
RssDataType.CLIEN.isOn {
try {
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 ->
Jsoup.connect(url)
.userAgent(USAGT)

View File

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

View File

@ -48,7 +48,7 @@ class TheQooGetter : BaseGetter {
override fun realWork(): Result {
RssDataType.THEQOO.isOn {
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 ->
Jsoup.connect(url)
.userAgent(USAGT)

View File

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

View File

@ -62,21 +62,21 @@
</androidx.appcompat.widget.Toolbar>
<androidx.recyclerview.widget.RecyclerView
android:layout_gravity="bottom|center_horizontal"
android:id="@+id/calendarDateView"
android:background="#ddd000"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
android:orientation="vertical"
app:spanCount="7"
android:layout_width="match_parent"
android:layout_height="match_parent"
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
android:layout_gravity="bottom|center_horizontal"
android:id="@+id/calendarDateView"
android:background="#ddd000"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
android:orientation="vertical"
app:spanCount="7"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="bums.lunatic.launcher.behavior.behaviorimpl.BehaviorRecT"
/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_main"

View File

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

View File

@ -17,6 +17,24 @@ plugins {
tasks.register<Delete>("clean") {
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 {
// mavenCentral()
// maven {

View File

@ -24,3 +24,4 @@ android.useAndroidX=true
android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=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