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