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

@ -120,6 +120,7 @@ import java.math.RoundingMode
import java.net.URLEncoder import java.net.URLEncoder
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
import java.util.Locale
import java.util.regex.Pattern import java.util.regex.Pattern
@ -134,55 +135,58 @@ internal class LauncherHome : Fragment() {
companion object { companion object {
var home: LauncherHome? = null var home: LauncherHome? = null
var lastedFinishedPageUrl: String = "" var lastedFinishedPageUrl: String = ""
// var recentCalls = arrayListOf<RecentCall>()
// var callList = arrayListOf<RecentCall>()
// var smsList = arrayListOf<RecentSms>()
var listTags = arrayListOf<RssDataInterface>() var listTags = arrayListOf<RssDataInterface>()
} }
// private var nReceiver: NotificationReceiver? = null
//
// class NotificationReceiver : BroadcastReceiver() {
// override fun onReceive(context: Context?, intent: Intent) {
// BLog.LOGE("NotificationReceiver >>>> ${intent.extras?.keySet()}")
// }
// }
val UPDATE_DELAY = 5L val UPDATE_DELAY = 5L
val commandHandler = Handler(Looper.getMainLooper()) val commandHandler = Handler(Looper.getMainLooper())
val smsUpdate = Runnable { val infoUpdate = Runnable { chooseAdpater() }
val notiUpdate = Runnable { chooseAdpater() }
chooseAdpater() var weatherJob: Job? = null
} var result: RealmResults<WeatherForcast>? = null
val hideListViewTime = 1000L * 60L * 15L
val callUpdate = Runnable { val hideListView = {
// binding.notiList.visibility = View.GONE
chooseAdpater() // binding.mainList.visibility = View.GONE
} // binding.infoList.visibility = View.GONE
// binding.smsList.visibility = View.GONE
// binding.otherCheck.isSelected = false
val infoUpdate = Runnable { // binding.recentSms.isSelected = false
chooseAdpater() // binding.missedCalls.isSelected = false
} // binding.notice.isSelected = false
val notiUpdate = Runnable {
chooseAdpater()
} }
val nomoreShowCount = 5
var rssStateVote = false
var lasted: List<RssData>? = null var lasted: List<RssData>? = null
var lastedNoti: List<NotificationItem>? = null var lastedNoti: List<NotificationItem>? = null
var infosJob: Job? = null
var noticeJob: Job? = null
lateinit var mRecentCallsAdapter: RecentCallsAdapter
lateinit var mSmsLogsAdapter: SmsLogsAdapter
lateinit var mRssAdapter: RssItemAdapter
lateinit var mNotiAdapter: NotificationItemAdapter
var mWeatherAdapter: WeatherAdapter? = null
var weatherDressAdapter: WeatherDressAdatper? = null
var weatherHourlyAdapter: WeatherHourlyAdapter? = null
var mRssDataResult: RealmResults<RssData>? = null
var mNotificationResult: RealmResults<NotificationItem>? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
home = this home = this
// BLog.LOGE("${this} ::::: onCreate >>>> ")
} }
var mWeatherResult : RealmResults<WeatherForcast>? = null
var musicJob: Job? = null var musicJob: Job? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(
// BLog.LOGE("${this} ::::: onCreateView >>>> ") inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = LauncherHomeBinding.inflate(inflater, container, false) binding = LauncherHomeBinding.inflate(inflater, container, false)
fragManager = lActivity!!.supportFragmentManager fragManager = lActivity!!.supportFragmentManager
settingsPrefs = requireContext().getSharedPreferences(PREFS_SETTINGS, 0) settingsPrefs = requireContext().getSharedPreferences(PREFS_SETTINGS, 0)
@ -198,8 +202,14 @@ internal class LauncherHome : Fragment() {
var weatherPages = arrayListOf<Int>() var weatherPages = arrayListOf<Int>()
var weatherAdapter = arrayListOf<RecyclerView.Adapter<out RecyclerView.ViewHolder>?>() var weatherAdapter = arrayListOf<RecyclerView.Adapter<out RecyclerView.ViewHolder>?>()
PrefBoolean.weatherDress.get(false).letTrue { weatherPages.add(R.layout.hourly_weather); weatherAdapter.add(weatherDressAdapter!!)} PrefBoolean.weatherDress.get(false).letTrue {
PrefBoolean.weatherState.get(false).letTrue { weatherPages.add(R.layout.recommended_hourly_dress); weatherAdapter.add(weatherHourlyAdapter!!)} weatherPages.add(R.layout.hourly_weather); weatherAdapter.add(weatherDressAdapter!!)
}
PrefBoolean.weatherState.get(false).letTrue {
weatherPages.add(R.layout.recommended_hourly_dress); weatherAdapter.add(
weatherHourlyAdapter!!
)
}
if (weatherPages.size > 0) { if (weatherPages.size > 0) {
mWeatherAdapter = WeatherAdapter( mWeatherAdapter = WeatherAdapter(
weatherPages, weatherPages,
@ -219,10 +229,6 @@ internal class LauncherHome : Fragment() {
binding.smsList.visibility = View.GONE binding.smsList.visibility = View.GONE
binding.infoList.visibility = View.GONE binding.infoList.visibility = View.GONE
binding.notiList.layoutManager = LinearLayoutManager(requireContext())
binding.mainList.layoutManager = GridLayoutManager(requireContext(),2)
binding.smsList.layoutManager = GridLayoutManager(requireContext(),2)
binding.infoList.layoutManager = LinearLayoutManager(requireContext())
binding.noticeSummary.weatherViewPager.orientation = ViewPager2.ORIENTATION_VERTICAL binding.noticeSummary.weatherViewPager.orientation = ViewPager2.ORIENTATION_VERTICAL
binding.mainList.adapter = mRecentCallsAdapter binding.mainList.adapter = mRecentCallsAdapter
@ -230,7 +236,8 @@ internal class LauncherHome : Fragment() {
binding.infoList.adapter = mRssAdapter binding.infoList.adapter = mRssAdapter
binding.notiList.adapter = mNotiAdapter binding.notiList.adapter = mNotiAdapter
binding.noticeSummary.weatherViewPager.adapter = mWeatherAdapter binding.noticeSummary.weatherViewPager.adapter = mWeatherAdapter
binding.noticeSummary.weatherViewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { binding.noticeSummary.weatherViewPager.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
// 페이지가 변경될 때 호출됨 // 페이지가 변경될 때 호출됨
@ -238,16 +245,6 @@ internal class LauncherHome : Fragment() {
} }
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
// 스크롤 상태가 변경될 때 호출됨
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
// 페이지 스크롤이 발생할 때 호출됨
}
}) })
binding.favAppsGroup.setOnLongClickListener { binding.favAppsGroup.setOnLongClickListener {
binding.otherCheck.isSelected = true binding.otherCheck.isSelected = true
@ -274,17 +271,26 @@ internal class LauncherHome : Fragment() {
QuickAccess().show(fragManager, BOTTOM_SHEET_TAG) QuickAccess().show(fragManager, BOTTOM_SHEET_TAG)
} }
} }
arrayListOf(binding.mainList,binding.smsList,binding.infoList,binding.notiList).forEach {
try {
it.removeOnScrollListener(onScrChanged)
} catch (e: Exception) {
e.printStackTrace()
}
it.addOnScrollListener(onScrChanged)
}
try{binding.mainList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()}
binding.mainList.addOnScrollListener(onScrChanged)
try{binding.smsList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()}
binding.smsList.addOnScrollListener(onScrChanged)
try{binding.infoList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()}
binding.infoList.addOnScrollListener(onScrChanged)
try{binding.notiList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()}
binding.notiList.addOnScrollListener(onScrChanged)
binding.currentMusic?.setOnClickListener {
queryInfos()
queryNotice()
queryWeather()
setMusicFunction()
return binding.root
}
fun setMusicFunction() {
binding.currentMusic.setOnClickListener {
lActivity?.apply { lActivity?.apply {
packageManager?.apply { packageManager?.apply {
startActivity(getLaunchIntentForPackage("com.google.android.apps.youtube.music")) startActivity(getLaunchIntentForPackage("com.google.android.apps.youtube.music"))
@ -294,12 +300,13 @@ internal class LauncherHome : Fragment() {
musicJob?.cancel() musicJob?.cancel()
musicJob = CoroutineScope(Dispatchers.Default).launch { musicJob = CoroutineScope(Dispatchers.Default).launch {
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
query<CurrentPlayItem>().find().asFlow().collect { changes: ResultsChange<CurrentPlayItem> -> query<CurrentPlayItem>().find().asFlow()
binding.currentMusic?.postDelayed({ .collect { changes: ResultsChange<CurrentPlayItem> ->
binding.currentMusic.postDelayed({
if (changes.list.size > 0) { if (changes.list.size > 0) {
PrefBoolean.showNowPlaying.get(false).letTrue { PrefBoolean.showNowPlaying.get(false).letTrue {
changes.list?.first()?.let { changes.list.first()?.let {
binding.currentMusic.visibility = View.VISIBLE binding.currentMusic.visibility = View.VISIBLE
binding.nextPlay.visibility = View.GONE binding.nextPlay.visibility = View.GONE
binding.artist.text = it.artists binding.artist.text = it.artists
@ -318,12 +325,12 @@ internal class LauncherHome : Fragment() {
} else { } else {
binding.currentMusic.visibility = View.GONE binding.currentMusic.visibility = View.GONE
binding.nextPlay.visibility = View.VISIBLE binding.nextPlay.visibility = View.VISIBLE
}}, 150L) }
}, 150L)
} }
} }
} }
musicJob?.start() musicJob?.start()
// BLog.LOGE("onCreateView()")
binding.nextBtn.setOnClickListener { binding.nextBtn.setOnClickListener {
val mAudioManager = val mAudioManager =
requireContext().getSystemService(AUDIO_SERVICE) as AudioManager requireContext().getSystemService(AUDIO_SERVICE) as AudioManager
@ -352,21 +359,24 @@ internal class LauncherHome : Fragment() {
KeyEvent(eventtime, eventtime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY, 0) KeyEvent(eventtime, eventtime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY, 0)
mAudioManager.dispatchMediaKeyEvent(upEvent) mAudioManager.dispatchMediaKeyEvent(upEvent)
} }
queryInfos()
queryNotice()
queryWeather()
return binding.root
} }
@SuppressLint("NotifyDataSetChanged") @SuppressLint("NotifyDataSetChanged")
fun queryWeatherWithLoc() { fun queryWeatherWithLoc() {
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
var latR = latitudeRange(BigDecimal.valueOf(LocationGetter.latitude).setScale(6,RoundingMode.HALF_UP).toDouble(), 200) var latR = latitudeRange(
var lonR = longitudeRange(BigDecimal.valueOf(LocationGetter.latitude).setScale(6,RoundingMode.HALF_UP).toDouble(),BigDecimal.valueOf(LocationGetter.longitude).setScale(6,RoundingMode.HALF_UP).toDouble(), 200) BigDecimal.valueOf(LocationGetter.latitude).setScale(6, RoundingMode.HALF_UP)
.toDouble(), 200
)
var lonR = longitudeRange(
BigDecimal.valueOf(LocationGetter.latitude).setScale(6, RoundingMode.HALF_UP)
.toDouble(),
BigDecimal.valueOf(LocationGetter.longitude).setScale(6, RoundingMode.HALF_UP)
.toDouble(),
200
)
query<Hour>() query<Hour>()
.query("lat >= $0 AND lat <= $1 AND lon >= $2 AND lon <= $3 AND time_epoch >= $4", .query(
"lat >= $0 AND lat <= $1 AND lon >= $2 AND lon <= $3 AND time_epoch >= $4",
latR.first(), latR.last(), latR.first(), latR.last(),
lonR.first(), lonR.last(), lonR.first(), lonR.last(),
(System.currentTimeMillis() / 1000L).toLong() (System.currentTimeMillis() / 1000L).toLong()
@ -398,7 +408,8 @@ internal class LauncherHome : Fragment() {
it.notifyDataSetChanged() it.notifyDataSetChanged()
} }
mWeatherAdapter?.let { mWeatherAdapter?.let {
binding.noticeSummary.textLocation = if (hours.isNotEmpty()) WeatherInfoManager.getShowingInfo(hours.first()).textLocation else "도시 / 나라" binding.noticeSummary.textLocation =
if (hours.isNotEmpty()) WeatherInfoManager.getShowingInfo(hours.first()).textLocation else "도시 / 나라"
it.notifyDataSetChanged() it.notifyDataSetChanged()
} }
} }
@ -421,8 +432,7 @@ internal class LauncherHome : Fragment() {
// } // }
} }
var weatherJob : Job? = null
var result : RealmResults<WeatherForcast>? = null
// lateinit var weatherJob : Job // lateinit var weatherJob : Job
// @SuppressLint("NotifyDataSetChanged") // @SuppressLint("NotifyDataSetChanged")
private fun queryWeather() { private fun queryWeather() {
@ -442,19 +452,6 @@ internal class LauncherHome : Fragment() {
weatherJob?.start() weatherJob?.start()
} }
val hideListViewTime = 1000L * 60L * 15L
val hideListView = {
// binding.notiList.visibility = View.GONE
// binding.mainList.visibility = View.GONE
// binding.infoList.visibility = View.GONE
// binding.smsList.visibility = View.GONE
// binding.otherCheck.isSelected = false
// binding.recentSms.isSelected = false
// binding.missedCalls.isSelected = false
// binding.notice.isSelected = false
}
val onScrChanged = object : RecyclerView.OnScrollListener() { val onScrChanged = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
@ -464,8 +461,10 @@ internal class LauncherHome : Fragment() {
RecyclerView.SCROLL_STATE_IDLE -> { RecyclerView.SCROLL_STATE_IDLE -> {
commandHandler.postDelayed(hideListView, hideListViewTime) commandHandler.postDelayed(hideListView, hideListViewTime)
} }
RecyclerView.SCROLL_STATE_DRAGGING -> { RecyclerView.SCROLL_STATE_DRAGGING -> {
} }
RecyclerView.SCROLL_STATE_SETTLING -> { RecyclerView.SCROLL_STATE_SETTLING -> {
} }
} }
@ -478,60 +477,57 @@ internal class LauncherHome : Fragment() {
} }
private fun queryNotice() { private fun queryNotice() {
try { noticeJob?.cancel() } catch (e:Exception) {e.printStackTrace()}
mNotificationResult = null mNotificationResult = null
try { clearJob(noticeJob)
System.gc()
}catch (e : Exception){
e.printStackTrace()
}
mNotificationResult = WorkersDb.getRealm().query<NotificationItem>().sort("postTime", Sort.DESCENDING).find() mNotificationResult = WorkersDb.getRealm().query<NotificationItem>().sort("postTime", Sort.DESCENDING).find()
noticeJob = CoroutineScope(Dispatchers.Default).launch { noticeJob = CoroutineScope(Dispatchers.Default).launch {
mNotificationResult?.asFlow()?.collect { changes: ResultsChange<NotificationItem> -> mNotificationResult?.asFlow()?.collect { changes: ResultsChange<NotificationItem> ->
commandHandler.removeCallbacks(hideListView) commandHandler.removeCallbacks(hideListView)
when (changes) { when (changes) {
is UpdatedResults -> { is UpdatedResults -> {
// BLog.LOGE("ResultsChange onNotificationPosted")
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
lastedNoti = copyFromRealm(changes.list) lastedNoti = copyFromRealm(changes.list)
} }
commandHandler.removeCallbacks(notiUpdate) commandHandler.removeCallbacks(notiUpdate)
commandHandler.postDelayed(notiUpdate, UPDATE_DELAY) commandHandler.postDelayed(notiUpdate, UPDATE_DELAY)
} }
else -> { else -> {
// types other than UpdatedResults are not changes -- ignore them
} }
} }
} }
} }
noticeJob?.start() noticeJob?.start()
} }
val nomoreShowCount = 5 fun clearJob(job : Job?) {
try { job?.cancel() } catch (e: Exception) { e.printStackTrace() }
try { System.gc() } catch (e: Exception) { e.printStackTrace() }
}
fun beforeQuery() { fun beforeQuery() {
mRssDataResult = null mRssDataResult = null
try { infosJob?.cancel() } catch (e:Exception) {e.printStackTrace()} clearJob(infosJob)
try { System.gc() }catch (e : Exception){e.printStackTrace()}
WorkersDb.getRealm().writeBlocking { WorkersDb.getRealm().writeBlocking {
delete(query<RssData>().query("pubDate < $0",beforeDay(Date(),3)).query("category != $0 AND category != $1 ", RssDataType.GURU.name,RssDataType.MOST.name).query("vote != $0", true).find()) delete(
query<RssData>()
.query("pubDate < $0", beforeDay(Date(), 3))
.query("category != $0 AND category != $1 ", RssDataType.GURU.name, RssDataType.MOST.name)
.query("vote != $0", true).find()
)
} }
} }
fun updateQuery(q: RealmQuery<RssData>) { fun updateQuery(q: RealmQuery<RssData>) {
mRssDataResult = q.sort("pubDate ", Sort.DESCENDING).limit(300).find() mRssDataResult = q.sort("pubDate ", Sort.DESCENDING).limit(300).distinct("chosung").find()
infosJob = CoroutineScope(Dispatchers.Default).launch { infosJob = CoroutineScope(Dispatchers.Default).launch {
mRssDataResult?.asFlow()?.collect { changes: ResultsChange<RssData> -> mRssDataResult?.asFlow()?.collect { changes: ResultsChange<RssData> ->
commandHandler.removeCallbacks(hideListView) commandHandler.removeCallbacks(hideListView)
commandHandler.removeCallbacks(infoUpdate) commandHandler.removeCallbacks(infoUpdate)
BLog.LOGE("enableSwipeToDeleteAndUndo in changes")
when (changes) { when (changes) {
is InitialResults, is UpdatedResults -> { is InitialResults, is UpdatedResults -> {
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
lasted = copyFromRealm(changes.list) lasted = copyFromRealm(changes.list)
BLog.LOGE("enableSwipeToDeleteAndUndo in new querys")
} }
commandHandler.postDelayed(infoUpdate, UPDATE_DELAY) commandHandler.postDelayed(infoUpdate, UPDATE_DELAY)
BLog.LOGE("enableSwipeToDeleteAndUndo postDelayed")
} }
else -> { else -> {
} }
@ -540,7 +536,8 @@ internal class LauncherHome : Fragment() {
} }
infosJob?.start() infosJob?.start()
} }
var rssStateVote = false
fun queryVotes() { fun queryVotes() {
beforeQuery() beforeQuery()
var rQ = WorkersDb.getRealm().query<RssData>().query("vote == $0", true) var rQ = WorkersDb.getRealm().query<RssData>().query("vote == $0", true)
@ -548,46 +545,46 @@ internal class LauncherHome : Fragment() {
rssStateVote = true rssStateVote = true
} }
fun queryInfos(filter: Collection<RssDataType>? = arrayListOf(RssDataType.GURU,RssDataType.MOST,RssDataType.REDDIT_NSFW), noLimit : Boolean = false) { fun queryInfos(
filter: Collection<RssDataType>? = arrayListOf(
RssDataType.GURU,
RssDataType.MOST,
RssDataType.REDDIT_NSFW
), noLimit: Boolean = false
) {
beforeQuery() beforeQuery()
var rQ = WorkersDb.getRealm().query<RssData>().query("read < $0", nomoreShowCount) var rQ = WorkersDb.getRealm().query<RssData>().query("read < $0", nomoreShowCount)
if (!noLimit) rQ.query("pubDate > $0", beforeDay(Date(), 3)) if (!noLimit) rQ.query("pubDate > $0", beforeDay(Date(), 3))
filter!!.forEach { filter!!.forEach {
rQ = rQ.query("category != $0", it.name) rQ = rQ.query("category != $0", it.name)
} }
updateQuery(rQ) updateQuery(rQ)
rssStateVote = false rssStateVote = false
} }
fun queryInfos(keyword : String, category : ArrayList<String> = arrayListOf(), noLimit : Boolean = false) { fun queryInfos(
keyword: String,
category: ArrayList<String> = arrayListOf(),
noLimit: Boolean = false
) {
beforeQuery() beforeQuery()
var rQ = WorkersDb.getRealm().query<RssData>() var rQ = WorkersDb.getRealm().query<RssData>()
if (!noLimit) rQ.query("pubDate > $0", beforeDay(Date(), 3)) if (!noLimit) rQ.query("pubDate > $0", beforeDay(Date(), 3))
if(keyword.length > 0) { keyword.isNotEmpty().letTrue {
// BLog.LOGE("queryInfos it >>> ${keyword}")
if (JamoUtils.CHOSUNG.contains(keyword.split("")[0])) { if (JamoUtils.CHOSUNG.contains(keyword.split("")[0])) {
rQ = rQ.query( rQ = rQ.query("title CONTAINS $0 OR chosung CONTAINS $1 ", keyword, keyword)
"title CONTAINS $0 OR chosung CONTAINS $1 ",
keyword,
keyword
)
} else if (Pattern.matches("^[가-힣]*\$", keyword)) { } else if (Pattern.matches("^[가-힣]*\$", keyword)) {
rQ = rQ.query( rQ = rQ.query("title CONTAINS $0", keyword)
"title CONTAINS $0",
keyword
)
} else { } else {
rQ = rQ.query( rQ = rQ.query(
"title CONTAINS $0 OR title CONTAINS $1", "title CONTAINS $0 OR title CONTAINS $1",
keyword.toUpperCase(), keyword.uppercase(Locale.getDefault()),
keyword.toLowerCase() keyword.lowercase(Locale.getDefault())
) )
} }
} }
var queryString = "" var queryString = ""
if (category.size > 0) { category.isNotEmpty().letTrue {
category.forEachIndexed { idx, it -> category.forEachIndexed { idx, it ->
if (idx == 0) { if (idx == 0) {
queryString = queryString.plus("category == '${it}'") queryString = queryString.plus("category == '${it}'")
@ -595,9 +592,9 @@ internal class LauncherHome : Fragment() {
queryString = queryString.plus(" OR category == '${it}' ") queryString = queryString.plus(" OR category == '${it}' ")
} }
} }
rQ = rQ.query(queryString) rQ = rQ.query(queryString)
} }
if (keyword.length == 0 && category.size == 0) { if (keyword.length == 0 && category.size == 0) {
rQ = rQ.query("read < $0", 3).query("vote != $0", true) rQ = rQ.query("read < $0", 3).query("vote != $0", true)
} }
@ -605,37 +602,31 @@ internal class LauncherHome : Fragment() {
rssStateVote = false rssStateVote = false
} }
var infosJob : Job? = null
var noticeJob : Job? = null
lateinit var mRecentCallsAdapter : RecentCallsAdapter
lateinit var mSmsLogsAdapter : SmsLogsAdapter
lateinit var mRssAdapter : RssItemAdapter
lateinit var mNotiAdapter : NotificationItemAdapter
var mWeatherAdapter: WeatherAdapter? =null
var weatherDressAdapter: WeatherDressAdatper? = null
var weatherHourlyAdapter: WeatherHourlyAdapter? = null
var mRssDataResult : RealmResults<RssData>? = null
var mNotificationResult : RealmResults<NotificationItem>? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
// BLog.LOGE("${this} ::::: onViewCreated >>>> ")
rootViewGestures() rootViewGestures()
batteryProgressGestures() batteryProgressGestures()
todosGestures() todosGestures()
BLog.LOGE("onViewCreated()") BLog.LOGE("onViewCreated()")
binding.missedCalls.visibility = if (PrefBoolean.showCallHistory.get(false)) View.VISIBLE else View.GONE binding.missedCalls.visibility =
binding.recentSms.visibility = if (PrefBoolean.showSMSHistory.get(false)) View.VISIBLE else View.GONE if (PrefBoolean.showCallHistory.get(false)) View.VISIBLE else View.GONE
binding.notice.visibility = if (PrefBoolean.showNotificationHistory.get(false)) View.VISIBLE else View.GONE binding.recentSms.visibility =
binding.otherCheck.visibility = if (PrefBoolean.showNewsHistory.get(false)) View.VISIBLE else View.GONE if (PrefBoolean.showSMSHistory.get(false)) View.VISIBLE else View.GONE
binding.notice.visibility =
if (PrefBoolean.showNotificationHistory.get(false)) View.VISIBLE else View.GONE
binding.otherCheck.visibility =
if (PrefBoolean.showNewsHistory.get(false)) View.VISIBLE else View.GONE
binding.summaryChoose.visibility = View.GONE binding.summaryChoose.visibility = View.GONE
(PrefBoolean.showCallHistory.get(false) || PrefBoolean.showSMSHistory.get(false) || PrefBoolean.showNotificationHistory.get(false) || PrefBoolean.showNewsHistory.get(false)).letTrue { binding.summaryChoose.visibility = View.VISIBLE } (PrefBoolean.showCallHistory.get(false) || PrefBoolean.showSMSHistory.get(false) || PrefBoolean.showNotificationHistory.get(
false
) || PrefBoolean.showNewsHistory.get(false)).letTrue {
binding.summaryChoose.visibility = View.VISIBLE
}
// mWorkManager.
/* refresh the to-do list after getting back from TodoManager */
fragManager.addOnBackStackChangedListener { fragManager.addOnBackStackChangedListener {
BLog.LOGE("addOnBackStackChangedListener()") BLog.LOGE("addOnBackStackChangedListener()")
shouldResume = if (fragManager.backStackEntryCount == 0) { shouldResume = if (fragManager.backStackEntryCount == 0) {
@ -650,11 +641,18 @@ internal class LauncherHome : Fragment() {
var checkListner = object : View.OnClickListener { var checkListner = object : View.OnClickListener {
override fun onClick(v: View?) { override fun onClick(v: View?) {
commandHandler.removeCallbacks(hideListView) commandHandler.removeCallbacks(hideListView)
var views = arrayListOf(binding.mainList, binding.smsList, binding.infoList, binding.notiList) var views = arrayListOf(
var chechboxs = arrayListOf(binding.missedCalls, binding.mainList,
binding.smsList,
binding.infoList,
binding.notiList
)
var chechboxs = arrayListOf(
binding.missedCalls,
binding.recentSms, binding.recentSms,
binding.otherCheck, binding.otherCheck,
binding.notice) binding.notice
)
chechboxs.remove(v) chechboxs.remove(v)
when (v) { when (v) {
binding.missedCalls -> { binding.missedCalls -> {
@ -667,6 +665,7 @@ internal class LauncherHome : Fragment() {
binding.mainList.visibility = View.VISIBLE binding.mainList.visibility = View.VISIBLE
} }
} }
binding.recentSms -> { binding.recentSms -> {
if (binding.recentSms.isSelected) { if (binding.recentSms.isSelected) {
binding.recentSms.isSelected = false binding.recentSms.isSelected = false
@ -677,6 +676,7 @@ internal class LauncherHome : Fragment() {
binding.smsList.visibility = View.VISIBLE binding.smsList.visibility = View.VISIBLE
} }
} }
binding.otherCheck -> { binding.otherCheck -> {
if (binding.otherCheck.isSelected) { if (binding.otherCheck.isSelected) {
if (rssStateVote) { if (rssStateVote) {
@ -692,6 +692,7 @@ internal class LauncherHome : Fragment() {
binding.infoList.visibility = View.VISIBLE binding.infoList.visibility = View.VISIBLE
} }
} }
binding.notice -> { binding.notice -> {
if (binding.notice.isSelected) { if (binding.notice.isSelected) {
binding.notice.isSelected = false binding.notice.isSelected = false
@ -719,8 +720,6 @@ internal class LauncherHome : Fragment() {
} }
enableSwipeToDeleteAndUndo() enableSwipeToDeleteAndUndo()
} }
// https://www.youtube.com/results?search_query=sds
fun searchData() { fun searchData() {
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
@ -728,7 +727,8 @@ internal class LauncherHome : Fragment() {
val viewInflated: View = LayoutInflater.from(context) val viewInflated: View = LayoutInflater.from(context)
.inflate(R.layout.search_layout, view as ViewGroup?, false) .inflate(R.layout.search_layout, view as ViewGroup?, false)
val input = viewInflated.findViewById<View>(R.id.input) as EditText val input = viewInflated.findViewById<View>(R.id.input) as EditText
val categoryz = viewInflated.findViewById<TableRadioGroup>(R.id.categoryz) as TableRadioGroup val categoryz =
viewInflated.findViewById<TableRadioGroup>(R.id.categoryz) as TableRadioGroup
categoryz.setMaxColumns(5) categoryz.setMaxColumns(5)
categoryz.setMaxRows(5) categoryz.setMaxRows(5)
categoryz.setOnCheckedChangeListener(object : TableRadioGroup.OnCheckedChangeListener { categoryz.setOnCheckedChangeListener(object : TableRadioGroup.OnCheckedChangeListener {
@ -774,14 +774,20 @@ internal class LauncherHome : Fragment() {
} }
fun showAl() { fun showAl() {
binding.alcholKatalkT.visibility = View.VISIBLE binding.alcholKatalkT.visibility = View.VISIBLE
binding.alcholKatalkT.setOnClickListener { binding.alcholKatalkT.setOnClickListener {
openSearchApps("kakaot://taxi?dest_lat=${URLEncoder.encode("37.467696")}&dest_lng=${URLEncoder.encode("127.101063")}","com.kakao.taxi") openSearchApps(
"kakaot://taxi?dest_lat=${URLEncoder.encode("37.467696")}&dest_lng=${
URLEncoder.encode(
"127.101063"
)
}", "com.kakao.taxi"
)
} }
} }
fun openSearchApps(schemeString: String, pakage: String? = null) { fun openSearchApps(schemeString: String, pakage: String? = null) {
val gmmIntentUri = Uri.parse(schemeString) val gmmIntentUri = Uri.parse(schemeString)
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
@ -790,6 +796,7 @@ internal class LauncherHome : Fragment() {
} }
startActivity(mapIntent) startActivity(mapIntent)
} }
fun hideAl() { fun hideAl() {
binding.alcholKatalkT.visibility = View.GONE binding.alcholKatalkT.visibility = View.GONE
binding.alcholKatalkT.setOnClickListener { binding.alcholKatalkT.setOnClickListener {
@ -807,7 +814,8 @@ internal class LauncherHome : Fragment() {
var dateParam = beforeDay(Date(), 30).toString() var dateParam = beforeDay(Date(), 30).toString()
if (binding.missedCalls.isSelected) { if (binding.missedCalls.isSelected) {
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
val result = query<RecentCall>().query("callDayTime >= $0", dateParam).sort("callDayTime", Sort.DESCENDING).find() val result = query<RecentCall>().query("callDayTime >= $0", dateParam)
.sort("callDayTime", Sort.DESCENDING).find()
val list = copyFromRealm(result) val list = copyFromRealm(result)
binding.missedCalls.text = "통화 목록 [${list.size}]" binding.missedCalls.text = "통화 목록 [${list.size}]"
binding.mainList.visibility = View.VISIBLE binding.mainList.visibility = View.VISIBLE
@ -841,8 +849,7 @@ internal class LauncherHome : Fragment() {
binding.infoList.visibility = View.VISIBLE binding.infoList.visibility = View.VISIBLE
binding.otherCheck.text = "글타래 [${lasted?.size ?: "-"}]" binding.otherCheck.text = "글타래 [${lasted?.size ?: "-"}]"
lasted?.let { mRssAdapter.updateData(it) } lasted?.let { mRssAdapter.updateData(it) }
} } else if (binding.notice.isSelected) {
else if(binding.notice.isSelected) {
binding.missedCalls.isSelected = false binding.missedCalls.isSelected = false
binding.recentSms.isSelected = false binding.recentSms.isSelected = false
binding.otherCheck.isSelected = false binding.otherCheck.isSelected = false
@ -854,21 +861,31 @@ internal class LauncherHome : Fragment() {
} }
private fun enableSwipeToDeleteAndUndo() { private fun enableSwipeToDeleteAndUndo() {
val swipeToDeleteCallback: SwipeToDeleteCallback = object : SwipeToDeleteCallback(requireContext()) { val swipeToDeleteCallback: SwipeToDeleteCallback =
object : SwipeToDeleteCallback(requireContext()) {
override fun onSwiped(@NonNull viewHolder: RecyclerView.ViewHolder, i: Int) { override fun onSwiped(@NonNull viewHolder: RecyclerView.ViewHolder, i: Int) {
(viewHolder.itemView.getTag() as? RssData)?.let { rss -> (viewHolder.itemView.tag as? RssData)?.let { rss ->
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
writeBlocking { writeBlocking {
BLog.LOGE("enableSwipeToDeleteAndUndo in ") if(query<RssData>("chosung == $0",rss.chosung).find().size == 1) {
if (rssStateVote && rss.vote) { if (rssStateVote && rss.vote) {
rss.vote = false rss.vote = false
rss.read = 0 rss.read = 0
} else { } else {
rss.read += nomoreShowCount rss.read += nomoreShowCount
} }
BLog.LOGE("enableSwipeToDeleteAndUndo in before updated ${rss.read}")
copyToRealm(rss, UpdatePolicy.ALL) copyToRealm(rss, UpdatePolicy.ALL)
BLog.LOGE("enableSwipeToDeleteAndUndo in updated") } else {
query<RssData>("chosung == $0",rss.chosung).find().forEach { it ->
if (rssStateVote && rss.vote) {
it.vote = false
it.read = 0
} else {
it.read += nomoreShowCount
}
copyToRealm(it, UpdatePolicy.ALL)
}
}
} }
} }
} }
@ -878,13 +895,17 @@ internal class LauncherHome : Fragment() {
val itemTouchhelper = ItemTouchHelper(swipeToDeleteCallback) val itemTouchhelper = ItemTouchHelper(swipeToDeleteCallback)
itemTouchhelper.attachToRecyclerView(binding.infoList) itemTouchhelper.attachToRecyclerView(binding.infoList)
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
// BLog.LOGE("${this} ::::: onResume >>>> ") // BLog.LOGE("${this} ::::: onResume >>>> ")
if (shouldResume) { if (shouldResume) {
/* register battery changes */ /* register battery changes */
requireContext().registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) requireContext().registerReceiver(
batteryReceiver,
IntentFilter(Intent.ACTION_BATTERY_CHANGED)
)
/* time and date */ /* time and date */
// binding.time.textLocale = Locale.US // binding.time.textLocale = Locale.US
// binding.date.textLocale = Locale.US // binding.date.textLocale = Locale.US
@ -902,11 +923,6 @@ internal class LauncherHome : Fragment() {
} }
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
/* unregister battery changes */ /* unregister battery changes */
@ -925,12 +941,19 @@ internal class LauncherHome : Fragment() {
4 -> { 4 -> {
lActivity!!.startActivity(Intent(requireContext(), Behavior::class.java)) lActivity!!.startActivity(Intent(requireContext(), Behavior::class.java))
} }
3 -> QuickAccess().show(fragManager, BOTTOM_SHEET_TAG) 3 -> QuickAccess().show(fragManager, BOTTOM_SHEET_TAG)
2 -> { 2 -> {
var startIntene = Intent(Intent.ACTION_MAIN) var startIntene = Intent(Intent.ACTION_MAIN)
startIntene.setComponent(ComponentName("com.mime.dualscreenview","com.mime.dualscreenview.activity.Intro")) startIntene.setComponent(
ComponentName(
"com.mime.dualscreenview",
"com.mime.dualscreenview.activity.Intro"
)
)
startActivity(startIntene) startActivity(startIntene)
} }
else -> {} else -> {}
} }
return false return false
@ -944,19 +967,32 @@ internal class LauncherHome : Fragment() {
): Boolean { ): Boolean {
when (fingers) { when (fingers) {
2 -> 2 ->
if (targetView?.equals(binding.batteryProgress) ?: false) { if (targetView.equals(binding.batteryProgress)) {
expandNotificationPanel(requireContext()) expandNotificationPanel(requireContext())
} else { } else {
expandNotificationPanel(requireContext()) expandNotificationPanel(requireContext())
} }
4 -> { 4 -> {
var startIntene = Intent(Intent.ACTION_MAIN) var startIntene = Intent(Intent.ACTION_MAIN)
startIntene.setComponent(ComponentName("com.samsung.android.app.interpreter","com.samsung.android.app.interpreter.interpretation.view.InterpretationActivity")) startIntene.setComponent(
ComponentName(
"com.samsung.android.app.interpreter",
"com.samsung.android.app.interpreter.interpretation.view.InterpretationActivity"
)
)
startActivity(startIntene) startActivity(startIntene)
} }
3 -> { 3 -> {
lActivity!!.startActivity(Intent(requireContext(), SettingsActivity::class.java)) lActivity!!.startActivity(
Intent(
requireContext(),
SettingsActivity::class.java
)
)
} }
else -> {} else -> {}
} }
return false return false
@ -1009,24 +1045,36 @@ internal class LauncherHome : Fragment() {
override fun onDoubleTap(targetView: View, fingers: Int): Boolean { override fun onDoubleTap(targetView: View, fingers: Int): Boolean {
when (fingers) { when (fingers) {
1 -> lockMethod(settingsPrefs.getInt(KEY_LOCK_METHOD, 0), requireContext(), binding.favAppsGroup) 1 -> lockMethod(
settingsPrefs.getInt(KEY_LOCK_METHOD, 0),
requireContext(),
binding.favAppsGroup
)
else -> {} else -> {}
} }
return false return false
} }
override fun onLongPress(targetView: View, fingers: Int): Boolean { override fun onLongPress(targetView: View, fingers: Int): Boolean {
if (view?.equals(binding.batteryProgress) ?: false) { if (view?.equals(binding.batteryProgress) == true) {
lActivity!!.startActivity(Intent(requireContext(), SettingsActivity::class.java)) lActivity!!.startActivity(Intent(requireContext(), SettingsActivity::class.java))
} else if (view?.equals(binding.mainList) ?: false) { } else if (view?.equals(binding.mainList) == true) {
when (settingsPrefs.getBoolean(KEY_TODO_LOCK, false)) { when (settingsPrefs.getBoolean(KEY_TODO_LOCK, false)) {
false -> launchTodoManager() false -> launchTodoManager()
/* show authentication screen if lock is on */ /* show authentication screen if lock is on */
true -> { true -> {
if (canAuthenticate(requireContext())) { if (canAuthenticate(requireContext())) {
val biometricPrompt = BiometricPrompt(lActivity!!, authenticationCallback) val biometricPrompt =
BiometricPrompt(lActivity!!, authenticationCallback)
try { try {
biometricPrompt.authenticate(biometricPromptInfo(lActivity!!.getString(R.string.todo_manager))) biometricPrompt.authenticate(
biometricPromptInfo(
lActivity!!.getString(
R.string.todo_manager
)
)
)
} catch (exception: Exception) { } catch (exception: Exception) {
exception.printStackTrace() exception.printStackTrace()
} }
@ -1041,15 +1089,21 @@ internal class LauncherHome : Fragment() {
// BLog.LOGE("onClick ${view} , fingers ${fingers}") // BLog.LOGE("onClick ${view} , fingers ${fingers}")
when (fingers) { when (fingers) {
1 -> { 1 -> {
if (view?.equals(binding.batteryProgress) ?: false && fingers == 1) { if (view?.equals(binding.batteryProgress) == true && fingers == 1) {
requireContext().startActivity( requireContext().startActivity(
Intent(AlarmClock.ACTION_SHOW_ALARMS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) Intent(AlarmClock.ACTION_SHOW_ALARMS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
) )
} }
} }
2 -> { 2 -> {
lockMethod(settingsPrefs.getInt(KEY_LOCK_METHOD, 0), requireContext(), binding.favAppsGroup) lockMethod(
settingsPrefs.getInt(KEY_LOCK_METHOD, 0),
requireContext(),
binding.favAppsGroup
)
} }
else -> { else -> {
} }
@ -1062,50 +1116,54 @@ internal class LauncherHome : Fragment() {
fun jsonObjLog(pkey: String, key: String, jsonObject: JSONObject) { fun jsonObjLog(pkey: String, key: String, jsonObject: JSONObject) {
if (jsonObject?.has(key) == true && jsonObject?.get(key) is String) { if (jsonObject.has(key) == true && jsonObject.get(key) is String) {
BLog.LOGE("jsonObjLog $pkey String in $key >> ${jsonObject?.getString(key)}") BLog.LOGE("jsonObjLog $pkey String in $key >> ${jsonObject.getString(key)}")
} } else if (jsonObject.has(key) == true && jsonObject.get(key) is JSONObject) {
else if (jsonObject?.has(key) == true && jsonObject?.get(key) is JSONObject) { var obj = jsonObject.getJSONObject(key)
var obj = jsonObject?.getJSONObject(key)
BLog.LOGE("jsonObjLog $pkey JSONObject in $key >> ${obj}") BLog.LOGE("jsonObjLog $pkey JSONObject in $key >> ${obj}")
obj?.keys()?.forEach { obj?.keys()?.forEach {
// jsonObjLog(key,it, obj) // jsonObjLog(key,it, obj)
} }
} } else if (jsonObject.has(key) == true && jsonObject.get(key) is JSONArray) {
else if (jsonObject?.has(key) == true && jsonObject?.get(key) is JSONArray) { BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${jsonObject.getJSONArray(key)}")
BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${jsonObject?.getJSONArray(key)}") var array = jsonObject.getJSONArray(key)
var array = jsonObject?.getJSONArray(key)
for (i in 0..<(array?.length() ?: 0)) { for (i in 0..<(array?.length() ?: 0)) {
if (array?.get(i) is String) { if (array?.get(i) is String) {
} else if (array?.get(i) is JSONObject) { } else if (array?.get(i) is JSONObject) {
var child = array?.getJSONObject(i) var child = array.getJSONObject(i)
child?.keys()?.forEach { child?.keys()?.forEach {
// jsonObjLog(key, it, child) // jsonObjLog(key, it, child)
} }
} else if (array?.get(i) is JSONArray) { } else if (array?.get(i) is JSONArray) {
BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${array?.getJSONArray(i)}") BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${array.getJSONArray(i)}")
} }
} }
} else { } else {
BLog.LOGE("jsonObjLog $pkey else in $key >> ${if(jsonObject?.has(key) == true) (jsonObject?.get(key)) else ("")}") BLog.LOGE(
"jsonObjLog $pkey else in $key >> ${
if (jsonObject.has(key) == true) (jsonObject.get(
key
)) else ("")
}"
)
} }
} }
/* gestures on root view */ /* gestures on root view */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun rootViewGestures() { private fun rootViewGestures() {
binding.root.setOnTouchListener(SimpleFingerGestures(context = requireContext(), binding.root , mFingerGestureListener)) binding.root.setOnTouchListener(
SimpleFingerGestures(
context = requireContext(),
binding.root,
mFingerGestureListener
)
)
} }
/* gestures on battery progress indicator area */ /* gestures on battery progress indicator area */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun batteryProgressGestures() { private fun batteryProgressGestures() {
@ -1117,7 +1175,13 @@ internal class LauncherHome : Fragment() {
/* gestures on to-do area */ /* gestures on to-do area */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun todosGestures() { private fun todosGestures() {
binding.mainList.setOnTouchListener(SimpleFingerGestures(context = requireContext(), binding.mainList , mFingerGestureListener)) binding.mainList.setOnTouchListener(
SimpleFingerGestures(
context = requireContext(),
binding.mainList,
mFingerGestureListener
)
)
} }
/* authentication callback for TodoManager lock */ /* authentication callback for TodoManager lock */
@ -1125,11 +1189,21 @@ internal class LauncherHome : Fragment() {
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
launchTodoManager() launchTodoManager()
} }
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
Toast.makeText(requireContext(), lActivity!!.getString(R.string.authentication_error), Toast.LENGTH_SHORT).show() Toast.makeText(
requireContext(),
lActivity!!.getString(R.string.authentication_error),
Toast.LENGTH_SHORT
).show()
} }
override fun onAuthenticationFailed() { override fun onAuthenticationFailed() {
Toast.makeText(requireContext(), lActivity!!.getString(R.string.authentication_failed), Toast.LENGTH_SHORT).show() Toast.makeText(
requireContext(),
lActivity!!.getString(R.string.authentication_failed),
Toast.LENGTH_SHORT
).show()
} }
} }
@ -1140,9 +1214,9 @@ internal class LauncherHome : Fragment() {
} }
/* get time format string */ /* get time format string */
private val timeFormat: String? get() { private val timeFormat: String
get() {
// when (settingsPrefs.getInt(KEY_TIME_FORMAT, 0)) { // when (settingsPrefs.getInt(KEY_TIME_FORMAT, 0)) {
// 0 -> return if (DateFormat.is24HourFormat(requireContext())) { // 0 -> return if (DateFormat.is24HourFormat(requireContext())) {
// "kk:mm" // "kk:mm"
@ -1156,7 +1230,8 @@ internal class LauncherHome : Fragment() {
} }
/* get date number suffix */ /* get date number suffix */
private val dateNumberSuffix: String get() { private val dateNumberSuffix: String
get() {
return when (Calendar.getInstance()[Calendar.DAY_OF_MONTH]) { return when (Calendar.getInstance()[Calendar.DAY_OF_MONTH]) {
1, 21, 31 -> "ˢᵗ" 1, 21, 31 -> "ˢᵗ"
2, 22 -> "ⁿᵈ" 2, 22 -> "ⁿᵈ"
@ -1166,7 +1241,8 @@ internal class LauncherHome : Fragment() {
} }
/* get date format string */ /* get date format string */
private val dateFormat: String get() { private val dateFormat: String
get() {
// settingsPrefs.getString(KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT).let { // settingsPrefs.getString(KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT).let {
// return if (it!!.contains("x")) { // return if (it!!.contains("x")) {
// it.replace("x", dateNumberSuffix) // it.replace("x", dateNumberSuffix)

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