....
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import android.app.Application
|
||||
import android.content.ComponentCallbacks2
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
|
||||
|
||||
internal class LunaticLauncher : Application() {
|
||||
companion object {
|
||||
var appContext : Context? = null
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
appContext = this
|
||||
PrefHelper.inject(getSharedPreferences(PrefHelper.D_PREFIX, Context.MODE_PRIVATE))
|
||||
}
|
||||
override fun onTrimMemory(level: Int) {
|
||||
super.onTrimMemory(level)
|
||||
// Picasso.
|
||||
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) SQLiteDatabase.releaseMemory()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
///*
|
||||
// * 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 rasel.lunar.launcher.apps
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.content.Context
|
||||
//import android.graphics.Canvas
|
||||
//import android.graphics.Paint
|
||||
//import android.util.AttributeSet
|
||||
//import android.util.TypedValue
|
||||
//import android.view.MotionEvent
|
||||
//import android.view.View
|
||||
//import androidx.core.content.ContextCompat
|
||||
//
|
||||
//
|
||||
//internal class AlphabetScrollbar : View {
|
||||
//
|
||||
// private var paint: Paint? = null
|
||||
// private var selectedIndex = -1
|
||||
//// private val alphabet get() = alphabetList.distinct()
|
||||
//
|
||||
// constructor(context: Context?) : super(context) {
|
||||
// init()
|
||||
// }
|
||||
//
|
||||
// constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
||||
// init()
|
||||
// }
|
||||
//
|
||||
// constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
|
||||
// init()
|
||||
// }
|
||||
//
|
||||
// @SuppressLint("ResourceType")
|
||||
// private fun init() {
|
||||
// paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
// color = defaultTextColor
|
||||
// textSize = 16f
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onDraw(canvas: Canvas) {
|
||||
// super.onDraw(canvas)
|
||||
// val width = width
|
||||
// val height = height
|
||||
// val letterHeight: Int = height / alphabet.count()
|
||||
// alphabet.indices.forEach { i: Int ->
|
||||
// val x = width / 2f - paint!!.measureText(alphabet[i]) / 2f
|
||||
// val y = i * letterHeight + letterHeight / 2f
|
||||
// when (i) {
|
||||
// selectedIndex -> paint!!.textSize = 20f
|
||||
// else -> paint!!.textSize = 16f
|
||||
// }
|
||||
// canvas.drawText(alphabet[i], x, y, paint!!)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @SuppressLint("ClickableViewAccessibility")
|
||||
// override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
//// when (event.action) {
|
||||
//// MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
|
||||
//// val y = event.y
|
||||
//// val index = (y / height * alphabet.count()).toInt()
|
||||
//// if (index != selectedIndex) {
|
||||
//// selectedIndex = index
|
||||
//// invalidate()
|
||||
//// }
|
||||
////
|
||||
//// if (!settingsPrefs!!.getBoolean(Constants.KEY_APPS_COUNT, true)) letterPreview?.visibility = VISIBLE
|
||||
//// try { letterPreview?.text = alphabet[selectedIndex] }
|
||||
//// catch (exception: Exception) { exception.printStackTrace() }
|
||||
//// }
|
||||
////
|
||||
//// MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
//// when {
|
||||
//// selectedIndex < 0 -> listenScroll(alphabet[0])
|
||||
//// selectedIndex > alphabet.count() - 1 -> listenScroll(alphabet[alphabet.count() - 1])
|
||||
//// else -> listenScroll(alphabet[selectedIndex])
|
||||
//// }
|
||||
////
|
||||
//// selectedIndex = -1
|
||||
//// invalidate()
|
||||
//// if (settingsPrefs!!.getBoolean(Constants.KEY_APPS_COUNT, true)) letterPreview?.text = appsSize.toString()
|
||||
//// else letterPreview?.visibility = GONE
|
||||
//// }
|
||||
//// }
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// private val defaultTextColor: Int get() {
|
||||
// val resolvedAttr = TypedValue()
|
||||
// context.theme.resolveAttribute(android.R.attr.textColorPrimary, resolvedAttr, true)
|
||||
// val colorRes = resolvedAttr.run { if (resourceId != 0) resourceId else data }
|
||||
// return ContextCompat.getColor(context, colorRes)
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.View.GONE
|
||||
import android.view.View.VISIBLE
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.CommadCallabck
|
||||
import bums.lunatic.launcher.LauncherActivity
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.databinding.AppDrawerBinding
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_APPS_COUNT
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_APPS_LAYOUT
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_APP_NAMES
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.helpers.PrefLong
|
||||
import bums.lunatic.launcher.helpers.letTrue
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.query.RealmResults
|
||||
import io.realm.kotlin.query.Sort
|
||||
import java.net.URLEncoder
|
||||
import java.text.Normalizer
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
|
||||
|
||||
internal class AppDrawer : Fragment() {
|
||||
|
||||
private lateinit var binding: AppDrawerBinding
|
||||
private var layoutType: Int = 0
|
||||
// private var isSearchShown: Boolean = false
|
||||
private var isKeyboardShowing: Boolean = false
|
||||
|
||||
companion object {
|
||||
private var packageManager: PackageManager? = null
|
||||
private var appsAdapter: AppsAdapter? = null
|
||||
private var contactAdapter : ContactAdapter? = null
|
||||
private var packageList = mutableListOf<AppInfo>()
|
||||
@JvmStatic var settingsPrefs: SharedPreferences? = null
|
||||
@JvmStatic var appNamesPrefs: SharedPreferences? = null
|
||||
|
||||
|
||||
fun appName(resolver: ResolveInfo): String {
|
||||
return resolver.loadLabel(packageManager).toString().apply {
|
||||
appNamesPrefs?.edit()?.putString(resolver.activityInfo.packageName, this)?.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getCategory(category : Int) : String {
|
||||
return when(category) {
|
||||
ApplicationInfo.CATEGORY_UNDEFINED -> "UNDEFINED"
|
||||
ApplicationInfo.CATEGORY_GAME -> "GAME"
|
||||
ApplicationInfo.CATEGORY_AUDIO -> "AUDIO"
|
||||
ApplicationInfo.CATEGORY_VIDEO -> "VIDEO"
|
||||
ApplicationInfo.CATEGORY_IMAGE -> "IMAGE"
|
||||
ApplicationInfo.CATEGORY_SOCIAL -> "SOCIAL"
|
||||
ApplicationInfo.CATEGORY_NEWS -> "NEWS"
|
||||
ApplicationInfo.CATEGORY_MAPS -> "MAPS"
|
||||
ApplicationInfo.CATEGORY_PRODUCTIVITY -> "PRODUCTIVITY"
|
||||
ApplicationInfo.CATEGORY_ACCESSIBILITY -> "ACCESSIBILITY"
|
||||
else -> {"UNKNOWN"}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
fun getInputText() = binding.searchInput.text.toString()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = AppDrawerBinding.inflate(inflater, container, false)
|
||||
|
||||
settingsPrefs = requireContext().getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
appNamesPrefs = requireContext().getSharedPreferences(PREFS_APP_NAMES, 0)
|
||||
layoutType = settingsPrefs!!.getInt(KEY_APPS_LAYOUT, 0)
|
||||
packageManager = lActivity?.packageManager
|
||||
appsAdapter = AppsAdapter(layoutType, packageManager!!, childFragmentManager, binding.appsCount)
|
||||
contactAdapter = ContactAdapter(packageManager!!, childFragmentManager)
|
||||
|
||||
binding.appsCount.visibility = if (settingsPrefs!!.getBoolean(KEY_APPS_COUNT, true)) VISIBLE else GONE
|
||||
binding.searchNmap.setOnClickListener {
|
||||
openSearchApps("nmap://search?query=${getInputText()}&appname=${BuildConfig.APPLICATION_ID}","com.nhn.android.nmap")
|
||||
}
|
||||
binding.searchYoutube.setOnClickListener {
|
||||
openSearchApps("https://www.youtube.com/results?search_query=${getInputText()}","com.google.android.youtube")
|
||||
}
|
||||
binding.searchGoogleMap.setOnClickListener {
|
||||
openSearchApps("geo:0,0?q=${getInputText()}","com.google.android.apps.maps")
|
||||
}
|
||||
binding.searchGoogle.setOnClickListener {
|
||||
openSearchApps("https://www.google.com/search?q=${getInputText()}","com.android.chrome")
|
||||
}
|
||||
binding.searchTmap.setOnClickListener {
|
||||
openSearchApps("tmap://search?name=${getInputText()}","com.skt.tmap.ku")
|
||||
|
||||
}
|
||||
binding.searchNaver.setOnClickListener {
|
||||
openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${getInputText()}", "com.nhn.android.search")
|
||||
}
|
||||
binding.searchDuckduckgo.setOnClickListener {
|
||||
openSearchApps("https://duckduckgo.com/?t=h_&q=${getInputText()}","com.duckduckgo.mobile.android")
|
||||
}
|
||||
binding.searchNamuwiki.setOnClickListener {
|
||||
openSearchApps("https://namu.wiki/Search?q=${getInputText()}")
|
||||
}
|
||||
|
||||
binding.searchTranslate.setOnClickListener {
|
||||
openSearchApps("https://translate.google.com/?hl=ko&sl=ko&tl=en&text=${getInputText()}&op=translate","com.android.chrome")
|
||||
}
|
||||
binding.searchStore.setOnClickListener {
|
||||
openSearchApps("market://search?q=${getInputText()}")
|
||||
}
|
||||
binding.runSend.setOnClickListener {
|
||||
sendMsg()
|
||||
}
|
||||
binding.runTelegram.setOnClickListener {
|
||||
sendMsg("tg://msg?text=${getInputText()}&to=","org.telegram.messenger")
|
||||
}
|
||||
binding.runKatalk.setOnClickListener {
|
||||
sendMsg(pkg = "com.kakao.talk")
|
||||
}
|
||||
|
||||
binding.runKatalkT.setOnClickListener{
|
||||
openSearchApps("kakaot://taxi?dest_lat=${URLEncoder.encode("37.467696")}&dest_lng=${URLEncoder.encode("127.101063")}","com.kakao.taxi")
|
||||
// openSearchApps("kakaot://taxi?${URLEncoder.encode("세곡동 557")}","com.kakao.taxi")
|
||||
// openSearchApps("kakaot://taxi?dest_addr=${URLEncoder.encode("세곡동 557")}","com.kakao.taxi")
|
||||
}
|
||||
setLayout()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
fun sendMsg(scheme : String? = null , pkg : String? = null) {
|
||||
var postIntent : Intent? = null
|
||||
if (scheme != null && scheme.length > 1) {
|
||||
postIntent = Intent(Intent.ACTION_VIEW,Uri.parse(scheme))
|
||||
} else {
|
||||
postIntent = Intent(Intent.ACTION_SEND)
|
||||
postIntent.type = "text/plain"
|
||||
postIntent.putExtra(Intent.EXTRA_TEXT, "${getInputText()}")
|
||||
}
|
||||
if (pkg != null && pkg.length > 1) {
|
||||
postIntent?.setPackage(pkg)
|
||||
startActivity(postIntent)
|
||||
} else {
|
||||
val chooserTitle = "바로 보냄"
|
||||
startActivity(Intent.createChooser(postIntent, chooserTitle))
|
||||
}
|
||||
}
|
||||
|
||||
val appNames = hashSetOf<AppInfo>()
|
||||
val contactList = arrayListOf<SimpleContact>()
|
||||
|
||||
fun openSearchApps(schemeString : String, pakage : String? = null) {
|
||||
val gmmIntentUri = Uri.parse(schemeString)
|
||||
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
|
||||
pakage?.let {
|
||||
mapIntent.setPackage(pakage)
|
||||
WorkersDb.updateAppUse(pakage)
|
||||
}
|
||||
startActivity(mapIntent)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.searchInput.setOnKeyListener { v, keyCode, event ->
|
||||
//contactList.size < 1 && packageList.size < 1 &&
|
||||
if(PrefBoolean.useQuickLaunch.get(false) && keyCode == 66 && event.action == KeyEvent.ACTION_UP) {
|
||||
checkResult(binding.searchInput.text.toString())
|
||||
true
|
||||
}else {
|
||||
false
|
||||
}
|
||||
}
|
||||
binding.searchInput.setOnLongClickListener {
|
||||
WorkersDb.getRealm().apply {
|
||||
var newQ = query<AppInfo>()
|
||||
appQuery = newQ.sort(Pair("clickCount", Sort.DESCENDING),Pair("lastUseDate",Sort.DESCENDING)).find()
|
||||
appQuery?.let {
|
||||
if(it.size > 0) {
|
||||
WorkersDb.getRealm().apply {
|
||||
packageList.clear()
|
||||
packageList.addAll(copyFromRealm(it))
|
||||
binding.appsList.post { if (packageList.size > 0) {
|
||||
appsAdapter?.updateData(packageList)
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WorkersDb.getRealm().apply {
|
||||
var newQ = query<SimpleContact>()
|
||||
contactQuery = newQ.sort(Pair("touchCount", Sort.DESCENDING),Pair("lastedTouchDateTime",Sort.DESCENDING)).find()
|
||||
contactQuery?.let {
|
||||
if (it.size > 0) {
|
||||
contactList.clear()
|
||||
contactList.addAll(copyFromRealm(it).toList())
|
||||
binding.contactList.post {
|
||||
if (contactList.size > 0) {
|
||||
contactAdapter?.updateData(contactList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
true
|
||||
}
|
||||
binding.searchInput.doOnTextChanged{ inputText, _, _, _ ->
|
||||
binding.searchInput.text?.let { binding.searchInput.setSelection(it.length) }
|
||||
filterAppsList(inputText.toString())
|
||||
}
|
||||
}
|
||||
|
||||
fun checkResult(keyword: String) {
|
||||
lActivity?.openSearchMenus(keyword) {
|
||||
registCancelSearch()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun runonUi(invoke : () -> Unit) {
|
||||
Handler(Looper.getMainLooper()).run {
|
||||
try {
|
||||
invoke.invoke()
|
||||
}catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
BLog.LOGE("onResume")
|
||||
fetchApps()
|
||||
|
||||
binding.appsCount.visibility = if (PrefBoolean.showAppResultCount.get(false)) VISIBLE else GONE
|
||||
|
||||
PrefBoolean.openWithKayboard.get().letTrue { openSearch() }
|
||||
registCancelSearch()
|
||||
// BLog.LOGE("onResume after chechHandler.postDelayed(cancelSearch, 3000L)")
|
||||
}
|
||||
|
||||
val chechHandler = Handler(Looper.getMainLooper())
|
||||
|
||||
val cancelSearch = Runnable {
|
||||
// lActivity?.viewPager?.currentItem = 1
|
||||
}
|
||||
|
||||
fun registCancelSearch() {
|
||||
chechHandler.removeCallbacks(cancelSearch)
|
||||
chechHandler.postDelayed(cancelSearch, 15000L)
|
||||
}
|
||||
|
||||
fun clearCancelSearch() {
|
||||
chechHandler.removeCallbacks(cancelSearch)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
closeSearch()
|
||||
}
|
||||
|
||||
private fun setLayout() {
|
||||
binding.appsList.layoutManager = GridLayoutManager(requireContext(), 2, GridLayoutManager.HORIZONTAL,false)
|
||||
binding.contactList.layoutManager = GridLayoutManager(requireContext(), 2, GridLayoutManager.HORIZONTAL,false)
|
||||
/* initialize apps list adapter */
|
||||
binding.appsList.adapter = appsAdapter
|
||||
binding.contactList.adapter = contactAdapter
|
||||
}
|
||||
var appQuery : RealmResults<AppInfo>? = null
|
||||
fun fetchApps(keyword : String? = null) {
|
||||
WorkersDb.getRealm().apply {
|
||||
var newQ = query<AppInfo>()
|
||||
if (keyword != null && keyword.length > 0) {
|
||||
if (JamoUtils.CHOSUNG.contains(keyword.split("")[0])) {
|
||||
newQ = newQ.query("appName CONTAINS $0 OR appNameChosung CONTAINS $0 OR koreanName CONTAINS $0 OR alphaCho CONTAINS $0 OR category CONTAINS $0", keyword)
|
||||
} else if(Pattern.matches("^[가-힣]*\$", keyword)){
|
||||
newQ = newQ.query("appName CONTAINS $0 OR koreanName CONTAINS $0 OR category CONTAINS $0", keyword)
|
||||
}else {
|
||||
keyword.split("").forEach {
|
||||
if (it.length > 0) {
|
||||
newQ = newQ.query(
|
||||
"appName CONTAINS $0 OR category CONTAINS $0 OR pkgName CONTAINS $0 OR appName CONTAINS $1 OR category CONTAINS $1 OR pkgName CONTAINS $1",
|
||||
keyword.lowercase(),
|
||||
keyword.uppercase()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
appQuery = newQ.sort(Pair("clickCount", Sort.DESCENDING),Pair("lastUseDate",Sort.DESCENDING))
|
||||
.limit(PrefLong.maxQueryCount.get(18L).toInt()).find()
|
||||
appQuery?.let {
|
||||
if(it.size > 0) {
|
||||
WorkersDb.getRealm().apply {
|
||||
packageList.clear()
|
||||
packageList.addAll(copyFromRealm(it))
|
||||
binding.appsList.post { if (packageList.size > 0) {
|
||||
appsAdapter?.updateData(packageList)
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fetcContact(keyword)
|
||||
}
|
||||
|
||||
|
||||
var contactQuery : RealmResults<SimpleContact>? = null
|
||||
fun fetcContact(keyword : String? = null) {
|
||||
WorkersDb.getRealm().apply {
|
||||
var newQ = query<SimpleContact>()
|
||||
if (keyword != null && keyword.length > 0) {
|
||||
if(Pattern.matches("^[0-9]*\$", keyword)){
|
||||
keyword.split("").forEach { if (it.length > 0) newQ = newQ.query("phoneNumber CONTAINS $0", keyword) }
|
||||
} else {
|
||||
newQ = newQ.query("name CONTAINS $0 OR chosung CONTAINS $0", keyword)
|
||||
}
|
||||
}
|
||||
contactQuery = newQ.sort(Pair("touchCount", Sort.DESCENDING),Pair("lastedTouchDateTime",Sort.DESCENDING))
|
||||
.limit(PrefLong.maxQueryCount.get(18L).toInt()).find()
|
||||
contactQuery?.let {
|
||||
if (it.size > 0)
|
||||
WorkersDb.getRealm().apply {
|
||||
contactList.clear()
|
||||
contactList.addAll(copyFromRealm(it).toList())
|
||||
binding.contactList.post { if (contactList.size > 0) {
|
||||
contactAdapter?.updateData(contactList)
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getHangule() {
|
||||
BLog.LOGE("on getHangule")
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
if (appNames.size > 0) {
|
||||
val info = appNames.first()
|
||||
appNames.remove(info)
|
||||
if (info.koreanName?.length ?: 0 > 0 || info.appNameChosung?.length ?: 0 > 0) {
|
||||
getHangule()
|
||||
} else {
|
||||
BLog.LOGE("on getHangule ${info.appName}")
|
||||
if (Pattern.matches("^[a-zA-Z]*$", info.appName)) {
|
||||
// Jsoup.connect("https://translate.google.com/?hl=ko&sl=en&tl=ko&text=${info.appName}&op=translate").get().let { trans ->
|
||||
// BLog.LOGE("on getHangule ${trans.title()}")
|
||||
// trans.getElementsByTag("span").forEach {
|
||||
// BLog.LOGE("on getHangule ${it.text()}")
|
||||
// if(it.hasAttr("jsaction") &&
|
||||
// it.attr("jsaction").contains("mouseout") &&
|
||||
// it.attr("jsaction").contains("contextmenu") &&
|
||||
// it.attr("jsaction").contains("mouseover")
|
||||
// ) {
|
||||
// BLog.LOGE("on getHangule $it")
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }.apply {
|
||||
// getHangule()
|
||||
// }
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
LauncherActivity.Companion.lActivity?.doWebParseStart(
|
||||
"https://translate.google.com/?hl=ko&sl=en&tl=ko&text=${info.appName}&op=translate",
|
||||
object : CommadCallabck {
|
||||
override fun onConsoleLog(log: String) {
|
||||
if (log.contains("result::")) {
|
||||
val appHangulName = log.split("result::")[1]
|
||||
if(appHangulName?.length ?: 0 > 0) {
|
||||
info.appNameChosung = JamoUtils.split(appHangulName).joinToString("")
|
||||
info.koreanName = appHangulName
|
||||
// BLog.LOGE("appHangulName >>> $appHangulName")
|
||||
// BLog.LOGE("appHangulName >>> ${info.appNameChosung}")
|
||||
WorkersDb.update(info)
|
||||
getHangule()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun collectComplete() {
|
||||
getHangule()
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
info.appNameChosung = JamoUtils.split(info.appName).joinToString("")
|
||||
info.koreanName = info.appName
|
||||
WorkersDb.update(info)
|
||||
getHangule()
|
||||
BLog.LOGE("on getHangule to next")
|
||||
}
|
||||
}
|
||||
}
|
||||
},5,TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
private fun getAlphabetItems() {
|
||||
// settingsPrefs!!.getInt(KEY_SCROLLBAR_HEIGHT, DEFAULT_SCROLLBAR_HEIGHT).let { height: Int ->
|
||||
// if (height == 0) { binding.alphabets.visibility = GONE }
|
||||
// else {
|
||||
// binding.alphabets.apply {
|
||||
// if (visibility == GONE) visibility = VISIBLE
|
||||
// updateLayoutParams { this.height = height }
|
||||
// }
|
||||
// alphabetList.clear()
|
||||
// for (mPackage in packageList) {
|
||||
// mPackage.appName.first().uppercase().let { firstLetter: String ->
|
||||
// when {
|
||||
// numberPattern.matcher(firstLetter).matches() -> alphabetList.add(0, "#")
|
||||
// alphabetPattern.matcher(firstLetter).matches() -> alphabetList.add(firstLetter)
|
||||
// !numberPattern.matcher(firstLetter).matches() &&
|
||||
// !alphabetPattern.matcher(firstLetter).matches() -> alphabetList.add(alphabetList.size,"⠶")
|
||||
// else -> {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// binding.alphabets.invalidate()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
private fun filterAppsList(searchString: String) {
|
||||
fetchApps(searchString)
|
||||
registCancelSearch()
|
||||
}
|
||||
|
||||
private fun openSearch() {
|
||||
binding.searchInput.apply {
|
||||
visibility = VISIBLE
|
||||
requestFocus()
|
||||
let {
|
||||
(lActivity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* clear search string, hide keyboard and search box */
|
||||
private fun closeSearch() {
|
||||
binding.searchInput.apply {
|
||||
let {
|
||||
text?.clear()
|
||||
(lActivity?.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager)?.hideSoftInputFromWindow(it.windowToken, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun normalize(str: String): String {
|
||||
val normalizedString =
|
||||
Normalizer.normalize(str.replace("\\W".toRegex(), ""), Normalizer.Form.NFC)
|
||||
val pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+")
|
||||
return pattern.matcher(normalizedString).replaceAll("").toLowerCase()
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityOptions
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Rect
|
||||
import android.icu.text.SimpleDateFormat
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.apps.AppDrawer.Companion.appNamesPrefs
|
||||
import bums.lunatic.launcher.databinding.ActivityBrowserDialogBinding
|
||||
import bums.lunatic.launcher.databinding.AppInfoDialogBinding
|
||||
import bums.lunatic.launcher.databinding.AppMenuBinding
|
||||
import bums.lunatic.launcher.helpers.UniUtils.Companion.copyToClipboard
|
||||
import bums.lunatic.launcher.helpers.UniUtils.Companion.screenHeight
|
||||
import bums.lunatic.launcher.helpers.UniUtils.Companion.screenWidth
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.realm.kotlin.ext.query
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class AppMenu : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: AppMenuBinding
|
||||
private lateinit var packageName: String
|
||||
private lateinit var packageManager: PackageManager
|
||||
private lateinit var defAppName: String
|
||||
var appInfo: ApplicationInfo? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = AppMenuBinding.inflate(inflater, container, false)
|
||||
|
||||
/* get package name from fragment's tag */
|
||||
packageName = tag.toString()
|
||||
packageManager = requireContext().packageManager
|
||||
|
||||
/* get application info */
|
||||
appInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
try {
|
||||
packageManager.getApplicationInfo(packageName,
|
||||
PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong()))
|
||||
}catch (e :Exception) {
|
||||
null
|
||||
}
|
||||
|
||||
} else {
|
||||
try {
|
||||
packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA)
|
||||
}catch (e :Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
if(appInfo == null){
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
defAppName = ""
|
||||
var result = query<AppInfo>("pkgName == $0", packageName).find()
|
||||
if (result.size > 0) {
|
||||
val app = result.first()
|
||||
delete(app)
|
||||
}
|
||||
}
|
||||
dismiss()
|
||||
}
|
||||
} else {
|
||||
|
||||
/* get default app name */
|
||||
defAppName = packageManager.resolveActivity(
|
||||
Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setPackage(packageName), 0
|
||||
)?.loadLabel(packageManager).toString()
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
var result = query<AppInfo>("pkgName == $0", packageName).find()
|
||||
if (result.size > 0) {
|
||||
val app = result.first()
|
||||
binding.totalTouch.text = "총 실행 횟수 : ".plus(app.clickCount.toString())
|
||||
binding.lastTouchDate.text =
|
||||
"최종 실행 일시 : ".plus(SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date(app.lastUseDate)))
|
||||
binding.alterName.setText(app.koreanName)
|
||||
// app.clickCount = app.clickCount + 15
|
||||
|
||||
// app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
|
||||
// app.clickCount = app.clickCount + 15
|
||||
// app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun update() {
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
var result = query<AppInfo>("pkgName == $0",packageName).find()
|
||||
if(result.size > 0) {
|
||||
val app = result.first()
|
||||
app.clickCount = app.clickCount + 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.totalTouch.setOnClickListener { update() }
|
||||
binding.lastTouchDate.setOnClickListener { update() }
|
||||
|
||||
binding.alterName.doOnTextChanged { text, start, before, count ->
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
var result = query<AppInfo>("pkgName == $0",packageName).find()
|
||||
if(result.size > 0) {
|
||||
val app = result.first()
|
||||
app.koreanName = text.toString()
|
||||
app.appNameChosung = JamoUtils.split(app.koreanName).joinToString("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* set application name and package name */
|
||||
binding.appName.apply {
|
||||
setText(appNamesPrefs?.getString(packageName, defAppName))
|
||||
hint = defAppName
|
||||
|
||||
}
|
||||
binding.appPackage.text = packageName
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
/* copy package name */
|
||||
binding.appPackage.setOnClickListener {
|
||||
copyToClipboard(requireContext(), packageName)
|
||||
}
|
||||
|
||||
appName()
|
||||
binding.detailedInfo.setOnClickListener { detailedInfo() }
|
||||
binding.activityBrowser.setOnClickListener { activityBrowser() }
|
||||
binding.appStore.setOnClickListener { appStore() }
|
||||
binding.appFreeform.setOnClickListener { freeform() }
|
||||
binding.appInfo.setOnClickListener { appInfo() }
|
||||
binding.appShare.setOnClickListener { share() }
|
||||
binding.appUninstall.setOnClickListener { uninstall() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun appName() {
|
||||
binding.appName.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) binding.appName.minWidth = resources.getDimensionPixelOffset(R.dimen.twoSeventySix)
|
||||
else {
|
||||
(requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager)
|
||||
.hideSoftInputFromWindow(binding.appName.windowToken, 0)
|
||||
|
||||
binding.appName.apply {
|
||||
minWidth = resources.getDimensionPixelOffset(R.dimen.zero)
|
||||
|
||||
if (text!!.isBlank()) setText(defAppName)
|
||||
else setText(text!!.trim())
|
||||
|
||||
if (text.toString() == defAppName) appNamesPrefs?.edit()!!.remove(packageName).apply()
|
||||
else appNamesPrefs?.edit()!!.putString(packageName, text.toString()).apply()
|
||||
|
||||
(requireParentFragment() as AppDrawer).fetchApps()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.appName.setOnKeyListener { _, keyCode, event ->
|
||||
if (event.action == KeyEvent.ACTION_DOWN) {
|
||||
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
binding.appName.clearFocus()
|
||||
return@setOnKeyListener true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/* detailed info dialog */
|
||||
@SuppressLint("SetTextI18n")
|
||||
private fun detailedInfo() {
|
||||
val dialogBinding = AppInfoDialogBinding.inflate(lActivity!!.layoutInflater)
|
||||
MaterialAlertDialogBuilder(lActivity!!)
|
||||
.setView(dialogBinding.root)
|
||||
.setPositiveButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
|
||||
appInfo?.let { appInfo ->
|
||||
dialogBinding.appName.text = packageManager.getApplicationLabel(appInfo)
|
||||
|
||||
/* get package info */
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
|
||||
} else {
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
}
|
||||
|
||||
/* show infos */
|
||||
dialogBinding.mixed.text =
|
||||
"${resources.getString(R.string.version)}: ${packageInfo.versionName} (${PackageInfoCompat.getLongVersionCode(packageInfo).toInt()})\n" +
|
||||
"${resources.getString(R.string.sdk)}: ${appInfo.minSdkVersion} ~ ${appInfo.targetSdkVersion}\n" +
|
||||
"${resources.getString(R.string.uid)}: ${appInfo.uid}\n" +
|
||||
"${resources.getString(R.string.first_install)}: ${dateTimeFormat(packageInfo.firstInstallTime)}\n" +
|
||||
"${resources.getString(R.string.last_update)}: ${dateTimeFormat(packageInfo.lastUpdateTime)}"
|
||||
|
||||
/* show permissions */
|
||||
dialogBinding.permissions.text = permissionsList
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* activity browser dialog */
|
||||
private fun activityBrowser() {
|
||||
val dialogBinding = ActivityBrowserDialogBinding.inflate(lActivity!!.layoutInflater)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(lActivity!!)
|
||||
.setView(dialogBinding.root)
|
||||
.setPositiveButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
|
||||
appInfo?.let { appInfo ->
|
||||
|
||||
|
||||
/* show app name */
|
||||
dialogBinding.appName.text = packageManager.getApplicationLabel(appInfo)
|
||||
|
||||
/* get activity info */
|
||||
val activityInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(
|
||||
packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_ACTIVITIES.toLong())
|
||||
)
|
||||
} else {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
|
||||
}
|
||||
// BLog.LOGE("activity. >>>>> ${Gson().toJson(activityInfo)}")
|
||||
/* show activity list */
|
||||
val activityAdapter: ArrayAdapter<String> =
|
||||
ArrayAdapter(requireContext(), R.layout.list_item, R.id.itemText, ArrayList())
|
||||
if (activityInfo.activities.isNotEmpty()) {
|
||||
for (activity in activityInfo.activities) {
|
||||
if (packageName.contains("com.kakao") == true) {
|
||||
// BLog.LOGE("activity. >>>>> ${Gson().toJson(activity)}")
|
||||
}
|
||||
activityAdapter.add(
|
||||
activity.toString().split(" ").toTypedArray()[1].replace("}", "")
|
||||
)
|
||||
}
|
||||
dialogBinding.activityList.adapter = activityAdapter
|
||||
}
|
||||
|
||||
/* listen item clicks */
|
||||
dialogBinding.activityList.onItemClickListener =
|
||||
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, i: Int, _: Long ->
|
||||
try {
|
||||
/* open activity */
|
||||
val intent = Intent()
|
||||
intent.component = ComponentName(packageName, activityAdapter.getItem(i).toString())
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
requireContext().startActivity(intent)
|
||||
} catch (exception: Exception) {
|
||||
/* couldn't open activity */
|
||||
exception.printStackTrace()
|
||||
val exceptionShort = (exception.toString().split(": ").toTypedArray())[0]
|
||||
Toast.makeText(requireContext(),
|
||||
"${resources.getString(R.string.unable_to_launch)} -\n$exceptionShort", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
dialogBuilder.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* open app's page in app store/market */
|
||||
private fun appStore() {
|
||||
try {
|
||||
val storeIntent = Intent(Intent.ACTION_VIEW)
|
||||
storeIntent.data = Uri.parse("market://details?id=$packageName")
|
||||
storeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
requireContext().startActivity(storeIntent)
|
||||
} catch (activityNotFoundException: ActivityNotFoundException) {
|
||||
/* no app store found exception */
|
||||
Toast.makeText(requireContext(), requireContext().getString(R.string.null_app_store_message),
|
||||
Toast.LENGTH_SHORT).show()
|
||||
activityNotFoundException.printStackTrace()
|
||||
}
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/* launch app as a freeform window */
|
||||
private fun freeform() {
|
||||
val freeformIntent = requireContext().packageManager.getLaunchIntentForPackage(packageName)
|
||||
freeformIntent!!.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT or
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
|
||||
val rect = Rect(0, screenHeight / 2, screenWidth, screenHeight)
|
||||
var activityOptions = activityOptions
|
||||
activityOptions = activityOptions.setLaunchBounds(rect)
|
||||
requireContext().startActivity(freeformIntent, activityOptions.toBundle())
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/* open android's app info screen */
|
||||
private fun appInfo() {
|
||||
val infoIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
infoIntent.data = Uri.parse("package:$packageName")
|
||||
infoIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
requireContext().startActivity(infoIntent)
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
private fun share() {
|
||||
try {
|
||||
appInfo?.let { appInfo ->
|
||||
// Create a temporary file to copy the APK
|
||||
val apkLabel = packageManager.getApplicationLabel(appInfo).toString().lowercase()
|
||||
.replace(" ", "_")
|
||||
val tempApkFile = File(requireContext().externalCacheDir, "$apkLabel.apk")
|
||||
|
||||
// Copy the APK file
|
||||
FileInputStream(File(appInfo.sourceDir)).use { `in` ->
|
||||
FileOutputStream(tempApkFile).use { out ->
|
||||
val buffer = ByteArray(1024)
|
||||
var length: Int
|
||||
while (`in`.read(buffer).also { length = it } > 0) {
|
||||
out.write(buffer, 0, length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a content URI using FileProvider
|
||||
val contentUri =
|
||||
FileProvider.getUriForFile(
|
||||
requireContext(),
|
||||
"${requireContext().packageName}.fileprovider",
|
||||
tempApkFile
|
||||
)
|
||||
|
||||
//requireContext().grantUriPermission(receivers.package.name, contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
// Create a Share Intent
|
||||
Intent(Intent.ACTION_SEND).apply {
|
||||
type = "application/vnd.android.package-archive"
|
||||
putExtra(Intent.EXTRA_STREAM, contentUri)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}.let {
|
||||
// Start the chooser activity
|
||||
startActivity(Intent.createChooser(it, getString(R.string.share_apk_message)))
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() }
|
||||
catch (e: IOException) { e.printStackTrace() }
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/* uninstall the app */
|
||||
private fun uninstall() {
|
||||
val uninstallIntent = Intent(Intent.ACTION_DELETE)
|
||||
uninstallIntent.data = Uri.parse("package:$packageName")
|
||||
uninstallIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
requireContext().startActivity(uninstallIntent)
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/* long value to local date-time format */
|
||||
private fun dateTimeFormat(long: Long) : String = SimpleDateFormat.getDateTimeInstance().format(Date(long))
|
||||
|
||||
/* get and arrange all the permissions for an application */
|
||||
private val permissionsList : String get() {
|
||||
val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(PackageManager.GET_PERMISSIONS.toLong()))
|
||||
} else {
|
||||
packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
|
||||
}
|
||||
|
||||
return if (packageInfo.requestedPermissions.isNotEmpty()) {
|
||||
val stringBuilder = StringBuilder()
|
||||
packageInfo.requestedPermissions.indices.forEach { i: Int ->
|
||||
if (i != packageInfo.requestedPermissions.size - 1)
|
||||
stringBuilder.append("${packageInfo.requestedPermissions[i]}\n\n")
|
||||
/* don't add any new line after the last entry */
|
||||
else
|
||||
stringBuilder.append(packageInfo.requestedPermissions[i])
|
||||
}
|
||||
stringBuilder.toString()
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
/* get activity options for launching app in freeform mode */
|
||||
private val activityOptions: ActivityOptions get() {
|
||||
val activityOptions = ActivityOptions.makeBasic()
|
||||
try {
|
||||
val method =
|
||||
ActivityOptions::class.java.getMethod("setLaunchWindowingMode", Int::class.javaPrimitiveType)
|
||||
method.invoke(activityOptions, 5)
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
return activityOptions
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.apps.IconPackManager.Companion.getDrawableIconForPackage
|
||||
import bums.lunatic.launcher.databinding.AppsChildBinding
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import io.realm.kotlin.ext.query
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.async
|
||||
|
||||
|
||||
internal class AppsAdapter(
|
||||
private val layoutType: Int,
|
||||
private val packageManager: PackageManager,
|
||||
private val fragmentManager: FragmentManager,
|
||||
private val appsCount: TextView) : RecyclerView.Adapter<AppsAdapter.AppsViewHolder>() {
|
||||
|
||||
private var oldList = mutableListOf<AppInfo>()
|
||||
// private var appGravity: Int = Gravity.CENTER
|
||||
|
||||
companion object {
|
||||
@JvmStatic var appsSize: Int? = null
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): AppsViewHolder =
|
||||
AppsViewHolder(AppsChildBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false))
|
||||
|
||||
override fun onBindViewHolder(holder: AppsViewHolder, i: Int) {
|
||||
val item = oldList[i]
|
||||
|
||||
|
||||
|
||||
|
||||
holder.view.apply {
|
||||
childTextview.text = item.appName
|
||||
appIconTwo.visibility = View.VISIBLE
|
||||
|
||||
MainScope().async {
|
||||
getDrawableIconForPackage(item.pkgName, packageManager.getApplicationIcon(item.pkgName!!)) {
|
||||
appIconTwo.post { appIconTwo.setImageDrawable(it) }
|
||||
} }
|
||||
childTextview.apply {
|
||||
gravity = Gravity.CENTER
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twelve))
|
||||
}
|
||||
}
|
||||
|
||||
holder.view.root.apply {
|
||||
/* on click - open app */
|
||||
setOnClickListener {
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
var result = query<AppInfo>("pkgName == $0",item.pkgName).find()
|
||||
if(result.size > 0) {
|
||||
val app = result.first()
|
||||
app.clickCount = app.clickCount + 1
|
||||
app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
}
|
||||
context.startActivity(packageManager.getLaunchIntentForPackage(item.pkgName!!))
|
||||
}
|
||||
|
||||
/* on long click - open app menu */
|
||||
setOnLongClickListener {
|
||||
WorkersDb.getRealm().apply {
|
||||
writeBlocking {
|
||||
var result = query<AppInfo>("pkgName == $0",item.pkgName).find()
|
||||
if(result.size > 0) {
|
||||
val app = result.first()
|
||||
app.clickCount = app.clickCount + 15
|
||||
// app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
|
||||
}
|
||||
}
|
||||
}
|
||||
AppMenu().apply {
|
||||
}.show(fragmentManager, item.pkgName)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = oldList.size
|
||||
|
||||
inner class AppsViewHolder(var view: AppsChildBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
/* update app list */
|
||||
fun updateData(newList: List<AppInfo>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(AppsDiffUtil(oldList, newList))
|
||||
//
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
oldList.clear()
|
||||
oldList.addAll(newList)
|
||||
newList.size.let {
|
||||
appsCount.text = it.toString()
|
||||
appsSize = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class AppsDiffUtil(
|
||||
private val oldList: List<AppInfo>, private val newList: List<AppInfo>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].pkgName == newList[newItemPosition].pkgName
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.databinding.ContactItemBinding
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
|
||||
internal class ContactAdapter (
|
||||
private val packageManager: PackageManager,
|
||||
private val fragmentManager: FragmentManager) : RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() {
|
||||
|
||||
private var oldList = mutableListOf<SimpleContact>()
|
||||
private var appGravity: Int = Gravity.CENTER
|
||||
|
||||
companion object {
|
||||
@JvmStatic var appsSize: Int? = null
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): ContactViewHolder =
|
||||
ContactViewHolder(ContactItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false))
|
||||
|
||||
override fun onBindViewHolder(holder: ContactViewHolder, i: Int) {
|
||||
val item = oldList[i]
|
||||
// BLog.LOGE("name >>> ${item.name} :: ${item.touchCount} :: ${RecentCallGetter.dateFormat.format(
|
||||
// Date(item.lastedTouchDateTime)
|
||||
// )}")
|
||||
|
||||
holder.view.apply {
|
||||
name.text = item.name
|
||||
number.text= item.phoneNumber
|
||||
}
|
||||
holder.view.root.apply {
|
||||
/* on click - open app */
|
||||
setOnClickListener {
|
||||
ContactMenu().show(fragmentManager, item.id.toString())
|
||||
}
|
||||
|
||||
/* on long click - open app menu */
|
||||
setOnLongClickListener {
|
||||
// BLog.LOGE("item.id.toString() >> ${item.id.toString()}")
|
||||
ContactMenu().show(fragmentManager, item.id.toString())
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = oldList.size
|
||||
|
||||
inner class ContactViewHolder(var view: ContactItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
/* update app list */
|
||||
fun updateData(newList: List<SimpleContact>) {
|
||||
synchronized(oldList) {
|
||||
try {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(ContactDiffUtil(oldList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
oldList.clear()
|
||||
oldList.addAll(newList)
|
||||
newList.size.let {
|
||||
appsSize = it
|
||||
}
|
||||
}catch (e : IndexOutOfBoundsException) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* update text gravity (alignment) */
|
||||
@SuppressLint("RtlHardcoded", "NotifyDataSetChanged")
|
||||
fun updateGravity(gravity: Int){
|
||||
/* the first check is to avoid calling notifyDataSetChanged() everytime */
|
||||
if (gravity != appGravity &&
|
||||
(gravity == Gravity.LEFT || gravity == Gravity.CENTER || gravity == Gravity.RIGHT)) {
|
||||
appGravity = gravity
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun hideItem(idx: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
class SimpleContact : RealmObject {
|
||||
@PrimaryKey
|
||||
var id : String? = ""
|
||||
var name : String? = ""
|
||||
var chosung : String? = ""
|
||||
var phoneNumber : String? = ""
|
||||
var touchCount = 0
|
||||
var lastedTouchDateTime = 0L
|
||||
|
||||
constructor(id: String, name: String, phoneNumber: String) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.phoneNumber = phoneNumber
|
||||
chosung = JamoUtils.split(name).joinToString("")
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
}
|
||||
|
||||
internal class ContactDiffUtil(
|
||||
private val oldList: List<SimpleContact>, private val newList: List<SimpleContact>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].phoneNumber == newList[newItemPosition].phoneNumber
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.databinding.ContactMenuBinding
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import io.realm.kotlin.ext.query
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class ContactMenu : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: ContactMenuBinding
|
||||
private lateinit var contactId: String
|
||||
|
||||
var contactName : String = ""
|
||||
var contactPhoneNumber : String = ""
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = ContactMenuBinding.inflate(inflater, container, false)
|
||||
|
||||
/* get package name from fragment's tag */
|
||||
contactId = tag.toString()
|
||||
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
if (contactId != null && contactId.length ?: 0 > 0) {
|
||||
val result = query<SimpleContact>().query("id == $0", contactId).find()
|
||||
if(result.size > 0){
|
||||
var contact = result.first()
|
||||
binding.totalTouch.text = "총 연락 횟수 : ".plus(contact.touchCount.toString())
|
||||
binding.lastTouchDate.text = "마지막 연락 시간 : ".plus(SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date(contact.lastedTouchDateTime)))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun update() {
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
if (contactId != null && contactId.length ?: 0 > 0) {
|
||||
val result = query<SimpleContact>().query("id == $0", contactId).find()
|
||||
if(result.size > 0){
|
||||
var contact = result.first()
|
||||
contact.touchCount = contact.touchCount + 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
binding.totalTouch.setOnClickListener { update() }
|
||||
binding.lastTouchDate.setOnClickListener { update() }
|
||||
|
||||
val resolver = lActivity!!.contentResolver
|
||||
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||
ContactsContract.CommonDataKinds.Phone.NUMBER,
|
||||
)
|
||||
|
||||
BLog.LOGE("GetContact", "packageName ${contactId}")
|
||||
try {
|
||||
val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null , null)
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
val nameIndex = cursor.getColumnIndex(projection[0])
|
||||
val numberIndex = cursor.getColumnIndex(projection[1])
|
||||
contactName = cursor.getString(nameIndex)
|
||||
var number = cursor.getString(numberIndex)
|
||||
contactPhoneNumber = number.replace("-", "")
|
||||
BLog.LOGE("GetContact", "이름 : $contactName 번호 : $contactPhoneNumber ")
|
||||
}
|
||||
}
|
||||
// 데이터 계열은 반드시 닫아줘야 한다.
|
||||
cursor!!.close()
|
||||
} catch ( e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
/* get application info */
|
||||
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
/* copy package name */
|
||||
// binding.appPackage.setOnClickListener {
|
||||
// copyToClipboard(requireContext(), packageName)
|
||||
// }
|
||||
//
|
||||
appName()
|
||||
binding.detailedInfo.setOnClickListener { detailedInfo() }
|
||||
binding.call.setOnClickListener { callPhone() }
|
||||
binding.sms.setOnClickListener { sendSms() }
|
||||
// binding.activityBrowser.setOnClickListener { activityBrowser() }
|
||||
// binding.appStore.setOnClickListener { appStore() }
|
||||
// binding.appFreeform.setOnClickListener { freeform() }
|
||||
// binding.appInfo.setOnClickListener { appInfo() }
|
||||
// binding.appShare.setOnClickListener { share() }
|
||||
// binding.appUninstall.setOnClickListener { uninstall() }
|
||||
}
|
||||
|
||||
|
||||
|
||||
private fun appName() {
|
||||
binding.appName.text = contactName
|
||||
binding.phoneNumber.text = contactPhoneNumber
|
||||
}
|
||||
|
||||
|
||||
private fun detailedInfo() {
|
||||
var intent = Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(ContactsContract.Contacts.CONTENT_URI.toString() + "/" + contactId));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private fun sendSms() {
|
||||
var intent = Intent(Intent.ACTION_SEND);
|
||||
intent.setData(Uri.parse("smsto:" + contactPhoneNumber));
|
||||
startActivity(intent);
|
||||
}
|
||||
private fun callPhone() {
|
||||
var intent = Intent(Intent.ACTION_DIAL);
|
||||
intent.setData(Uri.parse("tel:" + contactPhoneNumber));
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.collection.LruCache
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.DEFAULT_ICON_PACK
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_ICON_PACK
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_PKGICS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.utils.ImageUtils
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.async
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
internal class IconPackManager {
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
companion object {
|
||||
|
||||
private val settingsPrefs = lActivity!!.getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
private val icsPrefs = lActivity!!.getSharedPreferences(PREFS_PKGICS,0)
|
||||
private val packageName = settingsPrefs.getString(KEY_ICON_PACK, DEFAULT_ICON_PACK)
|
||||
private var loaded = false
|
||||
private val packagesDrawables = HashMap<String?, String?>()
|
||||
private val packagesConponentNames = HashMap<String?, String?>()
|
||||
private val backImages: MutableList<Bitmap> = ArrayList()
|
||||
private var maskImage: Bitmap? = null
|
||||
private var frontImage: Bitmap? = null
|
||||
private var factor = 1.0f
|
||||
private var totalIcons = 0
|
||||
private var iconPackRes: Resources? = null
|
||||
private var appPackageIconDrawables : HashMap<String, Drawable> = hashMapOf()
|
||||
|
||||
private fun load() {
|
||||
/* load appfilter.xml from the icon pack package */
|
||||
try {
|
||||
var xpp: XmlPullParser? = null
|
||||
iconPackRes = lActivity!!.packageManager.getResourcesForApplication(packageName!!)
|
||||
val appFilterId = iconPackRes!!.getIdentifier("appfilter", "xml", packageName)
|
||||
if (appFilterId > 0) {
|
||||
xpp = iconPackRes!!.getXml(appFilterId)
|
||||
// BLog.LOGE("packageName >>> ${packageName}")
|
||||
} else {
|
||||
try {
|
||||
xpp = XmlPullParserFactory.newInstance().apply { isNamespaceAware = true }
|
||||
.newPullParser().apply {
|
||||
// BLog.LOGE("packageName >>> ${packageName}")
|
||||
setInput(iconPackRes!!.assets.open("appfilter.xml"), "utf-8")
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
BLog.w("", "Couldn't find the appfilter.xml file")
|
||||
}
|
||||
}
|
||||
if (xpp != null) {
|
||||
var eventType = xpp.eventType
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (xpp.name) {
|
||||
"iconback" -> {
|
||||
for (i in 0 until xpp.attributeCount) { if (xpp.getAttributeName(i).startsWith("img")) { loadBitmap(xpp.getAttributeValue(i))?.let { backImages.add(it) }}}
|
||||
}
|
||||
"iconmask" -> {
|
||||
if (xpp.attributeCount > 0 && xpp.getAttributeName(0) == "img1") { maskImage = loadBitmap(xpp.getAttributeValue(0)) }
|
||||
}
|
||||
"iconupon" -> {
|
||||
if (xpp.attributeCount > 0 && xpp.getAttributeName(0) == "img1") { frontImage = loadBitmap(xpp.getAttributeValue(0)) }
|
||||
}
|
||||
"scale" -> {
|
||||
if (xpp.attributeCount > 0 && xpp.getAttributeName(0) == "factor") { factor = java.lang.Float.valueOf(xpp.getAttributeValue(0)) }
|
||||
}
|
||||
"item" -> {
|
||||
var componentName: String? = null
|
||||
var drawableName: String? = null
|
||||
for (i in 0 until xpp.attributeCount) { when (xpp.getAttributeName(i)) {
|
||||
"component" -> componentName = xpp.getAttributeValue(i)
|
||||
"drawable" -> drawableName = xpp.getAttributeValue(i)
|
||||
} }
|
||||
if (!packagesDrawables.containsKey(componentName)) {
|
||||
packagesDrawables[componentName] = drawableName
|
||||
totalIcons += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
eventType = xpp.next()
|
||||
}
|
||||
}
|
||||
loaded = true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
BLog.w("", "Failed to load the icon pack")
|
||||
} catch (e: XmlPullParserException) {
|
||||
BLog.w("", "Failed to parse the appfilter.xml file")
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
|
||||
val cacheSize = maxMemory / 8
|
||||
val bitmapCache = object : LruCache<String, Bitmap>(cacheSize) {
|
||||
fun sizeOf(key: String?, value: Bitmap?): Int {
|
||||
return if(value?.byteCount?.toInt() ?: 0 > 1024) {
|
||||
value?.byteCount!!.div(1024)
|
||||
} else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadBitmap(drawableName: String): Bitmap? {
|
||||
if (packageName != null && packageName.length > 0) {
|
||||
var bm = bitmapCache.get(packageName)
|
||||
GlobalScope.async {
|
||||
bm?.let { ImageUtils.bitmapToBase64String(it)?.let {
|
||||
icsPrefs.contains(packageName)
|
||||
} }
|
||||
}
|
||||
if (bm != null) return bm
|
||||
}
|
||||
iconPackRes!!.getIdentifier(drawableName, "drawable", packageName).let { id ->
|
||||
if (id > 0) {
|
||||
ResourcesCompat.getDrawable(iconPackRes!!, id, null).let {
|
||||
if (it is BitmapDrawable) {
|
||||
if (packageName != null && packageName.length > 0) {
|
||||
bitmapCache.put(packageName, it.bitmap)
|
||||
}
|
||||
return it.bitmap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadDrawable(drawableName: String): Drawable? {
|
||||
iconPackRes!!.getIdentifier(drawableName, "drawable", packageName).let {
|
||||
return if (it > 0) ResourcesCompat.getDrawable(iconPackRes!!, it, null)
|
||||
else null
|
||||
}
|
||||
}
|
||||
|
||||
fun putAfterReturn(packages: String, drawable : Drawable?) : Drawable? {
|
||||
if (drawable != null) {
|
||||
appPackageIconDrawables.put(packages, drawable)
|
||||
(drawable as? BitmapDrawable)?.let {
|
||||
if(icsPrefs.contains(packageName)) {
|
||||
|
||||
} else {
|
||||
icsPrefs.edit().putString(packageName, ImageUtils.bitmapToBase64String(it.bitmap)).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
return drawable
|
||||
}
|
||||
|
||||
fun getDrawableIconForPackage(appPackageName: String?, defaultDrawable: Drawable?, onComplete : (Drawable?)->Unit) {
|
||||
var ddd = if (appPackageIconDrawables.containsKey(appPackageName)) appPackageIconDrawables.get(appPackageName) else null
|
||||
if (ddd != null) {
|
||||
onComplete(ddd)
|
||||
} else {
|
||||
when (packageName) {
|
||||
DEFAULT_ICON_PACK -> onComplete.invoke(defaultDrawable)
|
||||
else -> {
|
||||
if (!loaded) load()
|
||||
var componentName: String? = null
|
||||
componentName = packagesConponentNames.get(appPackageName!!)
|
||||
if (componentName == null || componentName.length ?: 0 <= 0 ) {
|
||||
BLog.LOGE("it's compo ${appPackageName}")
|
||||
var pkgIntent =
|
||||
lActivity!!.packageManager.getLaunchIntentForPackage(
|
||||
appPackageName!!
|
||||
)
|
||||
if (pkgIntent != null) {
|
||||
componentName = pkgIntent!!.component.toString()
|
||||
}
|
||||
}
|
||||
var drawable = packagesDrawables[componentName]
|
||||
if (!drawable.isNullOrEmpty()) onComplete.invoke(
|
||||
putAfterReturn(
|
||||
appPackageName,
|
||||
loadDrawable(drawable)
|
||||
)
|
||||
)
|
||||
else {
|
||||
if (!componentName.isNullOrEmpty()) {
|
||||
val start = componentName.indexOf("{") + 1
|
||||
val end = componentName.indexOf("}", start)
|
||||
if (end > start) {
|
||||
drawable = componentName.substring(start, end)
|
||||
.lowercase(Locale.getDefault()).replace(".", "_")
|
||||
.replace("/", "_")
|
||||
try {
|
||||
if (iconPackRes!!.getIdentifier(
|
||||
drawable,
|
||||
"drawable",
|
||||
packageName
|
||||
) > 0
|
||||
) onComplete.invoke(putAfterReturn(appPackageName, loadDrawable(drawable)))
|
||||
} catch (e: NullPointerException) {
|
||||
settingsPrefs.edit()
|
||||
.putString(KEY_ICON_PACK, DEFAULT_ICON_PACK).apply()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
onComplete.invoke(defaultDrawable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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.apps
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.databinding.SearchMenuBinding
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
||||
|
||||
internal class SearchMenu : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: SearchMenuBinding
|
||||
private lateinit var searchWord: String
|
||||
private lateinit var packageManager: PackageManager
|
||||
private lateinit var appInfo: ApplicationInfo
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SearchMenuBinding.inflate(inflater, container, false)
|
||||
|
||||
/* get package name from fragment's tag */
|
||||
searchWord = tag.toString()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
/* copy package name */
|
||||
// binding.appPackage.setOnClickListener {
|
||||
// copyToClipboard(requireContext(), packageName)
|
||||
// }
|
||||
//
|
||||
appName()
|
||||
// binding.detailedInfo.setOnClickListener { detailedInfo() }
|
||||
// binding.activityBrowser.setOnClickListener { activityBrowser() }
|
||||
// binding.appStore.setOnClickListener { appStore() }
|
||||
// binding.appFreeform.setOnClickListener { freeform() }
|
||||
// binding.appInfo.setOnClickListener { appInfo() }
|
||||
// binding.appShare.setOnClickListener { share() }
|
||||
// binding.appUninstall.setOnClickListener { uninstall() }
|
||||
binding.searchNmap.setOnClickListener {
|
||||
openSearchApps("nmap://search?query=${searchWord}&appname=${BuildConfig.APPLICATION_ID}","com.nhn.android.nmap")
|
||||
}
|
||||
binding.searchGoogleMap.setOnClickListener {
|
||||
openSearchApps("geo:0,0?q=${searchWord}","com.google.android.apps.maps")
|
||||
}
|
||||
binding.searchGoogle.setOnClickListener {
|
||||
openSearchApps("https://www.google.com/search?q=${searchWord}","com.android.chrome")
|
||||
}
|
||||
binding.searchTmap.setOnClickListener {
|
||||
openSearchApps("tmap://search?name=${searchWord}","com.skt.tmap.ku")
|
||||
|
||||
}
|
||||
binding.searchNaver.setOnClickListener {
|
||||
openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${searchWord}", "com.nhn.android.search")
|
||||
}
|
||||
binding.searchDuckduckgo.setOnClickListener {
|
||||
openSearchApps("https://duckduckgo.com/?t=h_&q=${searchWord}","com.duckduckgo.mobile.android")
|
||||
}
|
||||
binding.searchNamuwiki.setOnClickListener {
|
||||
openSearchApps("https://namu.wiki/Search?q=${searchWord}")
|
||||
}
|
||||
|
||||
binding.searchTranslate.setOnClickListener {
|
||||
openSearchApps("https://translate.google.com/?hl=ko&sl=ko&tl=en&text=${searchWord}&op=translate","com.android.chrome")
|
||||
}
|
||||
|
||||
binding.searchCoupang.setOnClickListener {
|
||||
openSearchApps("coupang://search?q=${searchWord}","com.coupang.mobile")
|
||||
}
|
||||
}
|
||||
|
||||
fun openSearchApps(schemeString : String, pakage : String? = null) {
|
||||
val gmmIntentUri = Uri.parse(schemeString)
|
||||
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
|
||||
pakage?.let {
|
||||
mapIntent.setPackage(pakage)
|
||||
}
|
||||
startActivity(mapIntent)
|
||||
try {
|
||||
dismiss()
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun appName() {
|
||||
binding.keyworkd.text = searchWord
|
||||
}
|
||||
|
||||
var mDismissCalback : DismissCalback? = null
|
||||
|
||||
fun show(manager: FragmentManager, tag: String? , dismissCalback : DismissCalback?) {
|
||||
this.mDismissCalback = dismissCalback
|
||||
this.show(manager, tag)
|
||||
}
|
||||
|
||||
override fun show(manager: FragmentManager, tag: String?) {
|
||||
super.show(manager, tag)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
BLog.LOGE("dismiss()")
|
||||
mDismissCalback?.invoke()
|
||||
super.dismiss()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
BLog.LOGE("onDismiss(dialog: DialogInterface)")
|
||||
mDismissCalback?.invoke()
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
}
|
||||
|
||||
typealias DismissCalback = ()->Unit
|
||||
@@ -0,0 +1,42 @@
|
||||
package bums.lunatic.launcher.apps
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.databinding.ContactMenuBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
|
||||
class SmmsMenu: BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: ContactMenuBinding
|
||||
private lateinit var msgId: String
|
||||
private lateinit var packageManager: PackageManager
|
||||
private lateinit var appInfo: ApplicationInfo
|
||||
private lateinit var defAppName: String
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = ContactMenuBinding.inflate(inflater, container, false)
|
||||
|
||||
/* get package name from fragment's tag */
|
||||
msgId = tag.toString()
|
||||
val resolver = lActivity!!.contentResolver
|
||||
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,755 @@
|
||||
/*
|
||||
* 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.feeds
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity.RESULT_CANCELED
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.speech.RecognitionListener
|
||||
import android.speech.RecognizerIntent
|
||||
import android.speech.SpeechRecognizer
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.view.ContextMenu
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.EditText
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import bums.lunatic.launcher.CommadCallabck
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.appWidgetHost
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.appWidgetManager
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.getCal
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.refreshDeviceData
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.refreshFeeds
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.FeedsBinding
|
||||
import bums.lunatic.launcher.feeds.rss.RssAdapter
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_WIDGET_HEIGHTS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_WIDGET_IDS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_WIDGETS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.SEPARATOR
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.requestCreateWidget
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.requestPickWidget
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
import bums.lunatic.launcher.home.LauncherHome.Companion.home
|
||||
import bums.lunatic.launcher.home.LauncherHome.Companion.listTags
|
||||
import bums.lunatic.launcher.model.CiliMagnet
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.jGuruTag
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.utils.FeedParseManager
|
||||
import bums.lunatic.launcher.utils.RssList.jGuruMain
|
||||
import bums.lunatic.launcher.utils.getJ
|
||||
import bums.lunatic.launcher.workers.RecentCallGetter
|
||||
import bums.lunatic.launcher.workers.RecentSmsGetter
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
import com.google.gson.Gson
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.query.Sort
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jsoup.Jsoup
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.Charset
|
||||
import java.util.Base64
|
||||
|
||||
|
||||
internal class Feeds : Fragment() , CommadCallabck {
|
||||
|
||||
private lateinit var binding: FeedsBinding
|
||||
private val requestCodeString = "requestCode"
|
||||
var mRssAdapter : RssAdapter<RssDataInterface>? = null
|
||||
var mRssAdapter2 : RssAdapter<jGuruTag>? = null
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FeedsBinding.inflate(inflater, container, false)
|
||||
mRssAdapter = RssAdapter(requireContext())
|
||||
mRssAdapter2 = RssAdapter(requireContext())
|
||||
binding.feedsRss.rss.adapter = mRssAdapter
|
||||
binding.feedsRss.rss2.adapter = mRssAdapter2
|
||||
binding.consoleLog.movementMethod = ScrollingMovementMethod();
|
||||
updateWidgets()
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
expandCollapse()
|
||||
systemInfo()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
registerForContextMenu(binding.widgetContainer)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
unregisterForContextMenu(binding.widgetContainer)
|
||||
if (binding.expandRss.isChecked)
|
||||
binding.expandRss.isChecked = false
|
||||
}
|
||||
|
||||
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo)
|
||||
menu.clearHeader()
|
||||
lActivity!!.menuInflater.inflate(R.menu.add_widget, menu)
|
||||
}
|
||||
|
||||
override fun onContextItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.add_widget) selectWidget()
|
||||
return super.onContextItemSelected(item)
|
||||
}
|
||||
|
||||
/* control view's expand-collapse actions */
|
||||
private fun expandCollapse() {
|
||||
binding.expandableButtons.addOnButtonCheckedListener { _: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
when (checkedId) {
|
||||
binding.expandRss.id -> {
|
||||
binding.feedsRss.rss.visibility = View.GONE
|
||||
binding.feedsRss.rss2.visibility = View.GONE
|
||||
binding.feedsSysInfos.expandableSystemInfo.collapse()
|
||||
binding.feedsRss.expandableRss.expand()
|
||||
startService()
|
||||
}
|
||||
binding.expandSystemInfo.id -> {
|
||||
binding.feedsRss.expandableRss.collapse()
|
||||
binding.feedsSysInfos.expandableSystemInfo.expand()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (checkedId) {
|
||||
binding.expandRss.id -> binding.feedsRss.expandableRss.collapse()
|
||||
binding.expandSystemInfo.id -> binding.feedsSysInfos.expandableSystemInfo.collapse()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun consoleLog(str : String) {
|
||||
mMainHandler.removeCallbacks(hideConsole)
|
||||
binding.consoleLog.post {
|
||||
binding.consoleLog.visibility = View.VISIBLE
|
||||
binding.consoleLog.text = binding.consoleLog.text.toString() + "\n" + str
|
||||
}
|
||||
mMainHandler.postDelayed(hideConsole,10000L)
|
||||
BLog.LOGE("consoleLog >>>> ${str}")
|
||||
}
|
||||
|
||||
fun openOpera(schemeString : String) {
|
||||
BLog.LOGE("openOpera ${schemeString}")
|
||||
val gmmIntentUri = Uri.parse(schemeString)
|
||||
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
|
||||
mapIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
mapIntent.setPackage("com.opera.browser")
|
||||
lActivity?.startActivity(mapIntent)
|
||||
}
|
||||
|
||||
override fun onConsoleLog(log: String) {
|
||||
consoleLog(log)
|
||||
}
|
||||
|
||||
|
||||
val mMainHandler = Handler(Looper.getMainLooper())
|
||||
val hideConsole = {
|
||||
binding.consoleLog.visibility = View.GONE
|
||||
binding.consoleLog.text = ""
|
||||
}
|
||||
override fun collectComplete() {
|
||||
|
||||
}
|
||||
var speechRecognizer : SpeechRecognizer? = null
|
||||
|
||||
/* start rss service if network is active and rss url is not empty */
|
||||
private fun startService() {
|
||||
try {
|
||||
System.gc()
|
||||
}catch (e : Exception){e.printStackTrace()}
|
||||
binding.feedsRss.rss.visibility = View.GONE
|
||||
binding.feedsRss.rss2.visibility = View.GONE
|
||||
binding.feedsRss.loading.visibility = View.VISIBLE
|
||||
binding.feedsRss.refresh.visibility = View.VISIBLE
|
||||
val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext())
|
||||
builder.setTitle("Command Line")
|
||||
val viewInflated: View = LayoutInflater.from(context)
|
||||
.inflate(R.layout.text_inpu_password, view as ViewGroup?, false)
|
||||
val input = viewInflated.findViewById<View>(R.id.input) as EditText
|
||||
builder.setView(viewInflated)
|
||||
builder.setPositiveButton(android.R.string.ok,
|
||||
DialogInterface.OnClickListener { dialog, which ->
|
||||
dialog.dismiss()
|
||||
consoleLog("input.text.toString() >>>> ${input.text.toString()}")
|
||||
if (input.text.toString().trim().contains(" ")) {
|
||||
val cmd = input.text.toString().trim().split(" ")
|
||||
when(cmd[0]) {
|
||||
"car" -> {
|
||||
if (cmd[1].trim().length > 2) {
|
||||
PrefHelper.carName = cmd[1].trim()
|
||||
}
|
||||
consoleLog(PrefHelper.carName)
|
||||
}
|
||||
"tt" -> {
|
||||
if (cmd[1].trim().length > 5) {
|
||||
PrefHelper.telegramSendTarget = cmd[1].trim()
|
||||
}
|
||||
consoleLog(PrefHelper.telegramSendTarget)
|
||||
}
|
||||
"so"-> {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
consoleLog("${cmd[0]} Start ${cmd[1]}")
|
||||
String.format(String(Base64.getMimeDecoder().decode("aHR0cHM6Ly9rciVzLnNvZ2lybC5zby8=".toByteArray())),cmd[1]).getJ().let { doc -> FeedParseManager.parse(doc){consoleLog(it)} }
|
||||
consoleLog("current j req() ${WorkersDb.getRealm().query<RssData>("category == $0", RssDataType.GURU.name).find().size}")
|
||||
consoleLog("${cmd[0]} END ${cmd[1]}")
|
||||
}
|
||||
}
|
||||
"s" -> {
|
||||
home?.queryInfos(keyword = cmd[1])
|
||||
}
|
||||
"jf" -> {
|
||||
consoleLog("on Cmd JF")
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
consoleLog("${cmd[0]} Start ${cmd[1]}")
|
||||
String.format(String(Base64.getMimeDecoder().decode("aHR0cHM6Ly9qYXZtb3N0LnRvL3NlYXJjaC9tb3ZpZS8lcw==".toByteArray())),cmd[1]).getJ().let { doc -> FeedParseManager.parse(doc){consoleLog(it)} }
|
||||
consoleLog("current j req() ${WorkersDb.getRealm().query<RssData>("category == $0", RssDataType.GURU.name).find().size}")
|
||||
consoleLog("${cmd[0]} END ${cmd[1]}")
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
consoleLog("on Cmd JF with SO")
|
||||
consoleLog("${cmd[0]} Start ${cmd[1]}")
|
||||
String.format(String(Base64.getMimeDecoder().decode("aHR0cHM6Ly9rcjcwLnNvZ2lybC5zby8/cz0lcw==".toByteArray())),cmd[1]).getJ().let { doc -> FeedParseManager.parse(doc){consoleLog(it)} }
|
||||
consoleLog("current j req() ${WorkersDb.getRealm().query<RssData>("category == $0", RssDataType.GURU.name).find().size}")
|
||||
consoleLog("${cmd[0]} END ${cmd[1]}")
|
||||
}
|
||||
}
|
||||
"mgn"-> {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var temp = arrayListOf<CiliMagnet>()
|
||||
consoleLog("this >>>> cili ${cmd[0]} -> ${cmd[1]}")
|
||||
Jsoup.connect("https://cili.site/search?q=${URLEncoder.encode(cmd[1], Charset.defaultCharset().name())}").get().let { cili ->
|
||||
consoleLog("this >>>> cili ${cili.title()}")
|
||||
cili.getElementsByTag("tr").forEach { cili_tr ->
|
||||
CiliMagnet().let { ciliMgn ->
|
||||
ciliMgn.link = if(cili_tr.getElementsByTag("a").size > 0)cili_tr.getElementsByTag("a").get(0).attr("href") else ""
|
||||
ciliMgn.title = if(cili_tr.getElementsByTag("p").size > 0)cili_tr.getElementsByTag("p").text() else ""
|
||||
ciliMgn.size = if(cili_tr.getElementsByClass("td-size").size > 0)cili_tr.getElementsByClass("td-size").text() else ""
|
||||
if(ciliMgn.isValid() && temp.size < 8 ) {
|
||||
Jsoup.connect(ciliMgn.getMagnetPageLink()).get().let { mgn_Page ->
|
||||
consoleLog("magnet_page >>> ${mgn_Page.title()}")
|
||||
if (mgn_Page.getElementsByClass("input-group magnet-box").size > 0) {
|
||||
mgn_Page.getElementsByClass("input-group magnet-box")
|
||||
.get(0)?.let { magnet_box ->
|
||||
magnet_box.getElementById(
|
||||
"input-magnet"
|
||||
)?.let { input_magnet ->
|
||||
// BLog.LOGE("input_magnet >>> ${input_magnet}")
|
||||
ciliMgn.magnetLink = input_magnet.attr("value").replace("&","&")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
temp.add(ciliMgn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}.apply {
|
||||
temp.forEach {
|
||||
consoleLog("ciliResult >>> ${Gson().toJson(it)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
binding.expandRss.isChecked = false
|
||||
} else {
|
||||
when (input.text.toString()) {
|
||||
"spe"->{
|
||||
speechRecognizer?.stopListening()
|
||||
speechRecognizer?.destroy()
|
||||
speechRecognizer = null
|
||||
}
|
||||
"sps"->{
|
||||
lActivity?.let { lActivity ->
|
||||
if (lActivity.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
|
||||
lActivity.requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 1)
|
||||
} else {
|
||||
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(lActivity)
|
||||
val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
|
||||
intent.putExtra(
|
||||
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
|
||||
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
|
||||
)
|
||||
intent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, true)
|
||||
|
||||
speechRecognizer?.setRecognitionListener(object : RecognitionListener {
|
||||
override fun onReadyForSpeech(params: Bundle) {
|
||||
consoleLog("onReadyForSpeech ")
|
||||
}
|
||||
|
||||
override fun onBeginningOfSpeech() {
|
||||
consoleLog("onBeginningOfSpeech ")
|
||||
}
|
||||
|
||||
override fun onRmsChanged(rmsdB: Float) {}
|
||||
|
||||
override fun onBufferReceived(buffer: ByteArray) {}
|
||||
|
||||
override fun onEndOfSpeech() {
|
||||
consoleLog("onEndOfSpeech ")
|
||||
}
|
||||
|
||||
override fun onError(error: Int) {
|
||||
consoleLog("onError ${error}")
|
||||
}
|
||||
|
||||
override fun onResults(results: Bundle) {
|
||||
val matches =
|
||||
results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
|
||||
if (matches != null) {
|
||||
val recognizedText = matches[0]
|
||||
consoleLog("recognizedText ${recognizedText}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPartialResults(partialResults: Bundle) {
|
||||
consoleLog("recognizedText ${partialResults}")
|
||||
}
|
||||
|
||||
override fun onEvent(eventType: Int, params: Bundle) {}
|
||||
})
|
||||
|
||||
speechRecognizer?.startListening(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
"citys" -> {
|
||||
val baseUrl = "https://www.worldcitydb.com/"
|
||||
var nations = arrayListOf<String>()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
"https://www.worldcitydb.com/search-by-country?lang=ko".getJ().let { doc ->
|
||||
BLog.LOGE("it.title() >> ${doc.title()}")
|
||||
doc.getElementsByTag("tr").forEach { table ->
|
||||
table.children().forEach {
|
||||
it.getElementsByTag("td").forEach { td ->
|
||||
td.children().forEach {
|
||||
if (it.tag().name.equals("a")) {
|
||||
BLog.LOGE("TD>>A ${it}")
|
||||
it.text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"loc_ck" -> {
|
||||
FeedsResult().show(parentFragmentManager, "")
|
||||
}
|
||||
"loc_on" -> {
|
||||
PrefHelper.location(!PrefHelper.isLocationOn())
|
||||
consoleLog("PrefHelper.isLocationOn() >>> ${PrefHelper.isLocationOn()}")
|
||||
lActivity?.updateLocationService()
|
||||
}
|
||||
"cal" ->{
|
||||
getCal()
|
||||
}
|
||||
"ojs" -> home?.queryInfos(arrayListOf<RssDataType>().apply {
|
||||
addAll(RssDataType.values())
|
||||
remove(RssDataType.GURU)
|
||||
remove(RssDataType.MOST)
|
||||
})
|
||||
"all" -> home?.queryInfos(arrayListOf<RssDataType>().apply {
|
||||
|
||||
})
|
||||
"onews" -> home?.queryInfos(arrayListOf<RssDataType>().apply {
|
||||
addAll(RssDataType.values())
|
||||
remove(RssDataType.NEWSFEED)
|
||||
})
|
||||
"ored" -> home?.queryInfos(arrayListOf<RssDataType>().apply {
|
||||
addAll(RssDataType.values())
|
||||
remove(RssDataType.REDDIT)
|
||||
})
|
||||
"req" -> {
|
||||
refreshFeeds()
|
||||
refreshDeviceData()
|
||||
consoleLog("excute refreshFeeds()")
|
||||
}
|
||||
"reqmax" -> {
|
||||
refreshFeeds()
|
||||
RecentCallGetter.dayRange = 30
|
||||
RecentSmsGetter.dayRange = 30
|
||||
refreshDeviceData()
|
||||
consoleLog("excute refreshFeeds()")
|
||||
}
|
||||
|
||||
"taxi" -> {
|
||||
consoleLog("before run State home?.binding?.alcholKatalkT?.isVisible >> ${home?.binding?.alcholKatalkT?.isVisible}")
|
||||
home?.showAl()
|
||||
consoleLog("after run State home?.binding?.alcholKatalkT?.isVisible >> ${home?.binding?.alcholKatalkT?.isVisible}")
|
||||
}
|
||||
|
||||
"tax" -> {
|
||||
consoleLog("before run State home?.binding?.alcholKatalkT?.isVisible >> ${home?.binding?.alcholKatalkT?.isVisible}")
|
||||
home?.hideAl()
|
||||
consoleLog("after run State home?.binding?.alcholKatalkT?.isVisible >> ${home?.binding?.alcholKatalkT?.isVisible}")
|
||||
}
|
||||
|
||||
"jshow" -> {
|
||||
binding.feedsRss.apply {
|
||||
rss.adapter = mRssAdapter
|
||||
loading.visibility = View.VISIBLE
|
||||
mRssAdapter?.updateData(WorkersDb.getRealm()
|
||||
.query<RssData>("category == $0 || category == $1", RssDataType.GURU.name, RssDataType.MOST.name)
|
||||
.sort("pubDate", Sort.DESCENDING).find())
|
||||
rss.visibility = View.VISIBLE
|
||||
loading.visibility = View.GONE
|
||||
refresh.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
"jjp" -> {
|
||||
// lActivity?.doWebParseStart("https://projectjav.com") {}
|
||||
}
|
||||
"jmnew" -> {
|
||||
// lActivity?.doWebParseStart("https://missav.com/dm507/en/release") {}
|
||||
}
|
||||
"jmiss" -> {
|
||||
// lActivity?.doWebParseStart("https://missav.com/dm16/en") {}
|
||||
}
|
||||
"jreq" -> {
|
||||
consoleLog("current j req() ${WorkersDb.getRealm()
|
||||
.query<RssData>("category == $0", RssDataType.GURU.name).find().size}")
|
||||
lActivity?.doWebParseStart(jGuruMain,callBack = object : CommadCallabck {
|
||||
override fun onConsoleLog(log: String) {
|
||||
this@Feeds.consoleLog(log)
|
||||
}
|
||||
|
||||
override fun collectComplete() {
|
||||
consoleLog("excuted j req() ${WorkersDb.getRealm()
|
||||
.query<RssData>("category == $0", RssDataType.GURU.name).find().size}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
"jtag" -> {
|
||||
// lActivity?.doWebPare(TEST_PAG.plus("tags")) {
|
||||
binding.feedsRss.apply {
|
||||
rss2.adapter = mRssAdapter2
|
||||
|
||||
loading.visibility = View.VISIBLE
|
||||
if (listTags.size > 0) {
|
||||
mRssAdapter2?.updateData(listTags)
|
||||
rss2.visibility = View.VISIBLE
|
||||
loading.visibility = View.GONE
|
||||
refresh.visibility = View.GONE
|
||||
} else {
|
||||
refresh.visibility = View.VISIBLE
|
||||
rss2.visibility = View.GONE
|
||||
refresh.setOnClickListener {
|
||||
lActivity?.doWebParseStart(jGuruMain.plus("tags"), callBack = object : CommadCallabck {
|
||||
override fun onConsoleLog(log: String) {
|
||||
this@Feeds.consoleLog(log)
|
||||
}
|
||||
|
||||
override fun collectComplete() {
|
||||
if (listTags.size > 0) {
|
||||
rss2?.postDelayed({
|
||||
mRssAdapter2?.updateData(listTags)
|
||||
loading.visibility = View.GONE
|
||||
refresh.visibility = View.GONE
|
||||
rss2.visibility = View.VISIBLE
|
||||
}, 500L)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.expandRss.isChecked = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
builder.setNegativeButton(android.R.string.cancel,
|
||||
DialogInterface.OnClickListener { dialog, which -> dialog.cancel() })
|
||||
|
||||
builder.show()
|
||||
// binding.feedsRss.apply {
|
||||
// if(rss.adapter != null) {
|
||||
// (rss.adapter as RssAdapter).items.clear()
|
||||
// }
|
||||
// }
|
||||
// val rssUrl = lActivity!!.getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
// .getString(KEY_RSS_URL, "")
|
||||
// when {
|
||||
// isNetworkAvailable && !rssUrl.isNullOrEmpty() -> {
|
||||
//// Intent(lActivity!!, RssService::class.java)
|
||||
//// .putExtra(RSS_RECEIVER, resultReceiver).let {
|
||||
//// enqueueWork(lActivity!!, RssService::class.java, rssJobId, it)
|
||||
//// }
|
||||
// }
|
||||
// else -> resumeService()
|
||||
// }
|
||||
// val rssUrl2 = lActivity!!.getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
// .getString(KEY_RSS_URL2, "")
|
||||
// when {
|
||||
// isNetworkAvailable && !rssUrl2.isNullOrEmpty() -> {
|
||||
// Intent(lActivity!!, RssService::class.java)
|
||||
// .putExtra(RSS_RECEIVER, resultReceiver).let {
|
||||
// enqueueWork(lActivity!!, RssService::class.java, rssJobId, it)
|
||||
// }
|
||||
// }
|
||||
// else -> resumeService()
|
||||
// }
|
||||
}
|
||||
|
||||
/* retry to start rss service */
|
||||
private fun resumeService() {
|
||||
binding.feedsRss.apply {
|
||||
rss.visibility = View.GONE
|
||||
loading.visibility = View.GONE
|
||||
refresh.visibility = View.VISIBLE
|
||||
refresh.setOnClickListener {
|
||||
mRssAdapter?.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun systemInfo() {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
SystemStats().apply {
|
||||
intStorage(binding.feedsSysInfos.intParent)
|
||||
extStorage(binding.feedsSysInfos.extParent)
|
||||
while (isActive) {
|
||||
ram(binding.feedsSysInfos.ramParent)
|
||||
cpu(binding.feedsSysInfos.cpuParent)
|
||||
misc(binding.feedsSysInfos.misc)
|
||||
delay(1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectWidget() {
|
||||
Intent(AppWidgetManager.ACTION_APPWIDGET_PICK).apply {
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetHost?.allocateAppWidgetId())
|
||||
putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_INFO, ArrayList())
|
||||
putParcelableArrayListExtra(AppWidgetManager.EXTRA_CUSTOM_EXTRAS, ArrayList())
|
||||
putExtra(requestCodeString, requestPickWidget)
|
||||
}.let { widgetPicker.launch(it) }
|
||||
}
|
||||
|
||||
private val widgetPicker =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val data = result.data
|
||||
val appWidgetId = data?.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
when (data?.getIntExtra(requestCodeString, requestPickWidget)) {
|
||||
requestPickWidget -> configureWidget(appWidgetId!!)
|
||||
requestCreateWidget -> createWidget(appWidgetId!!, null)
|
||||
}
|
||||
} else if (result.resultCode == RESULT_CANCELED && data != null) {
|
||||
if (appWidgetId != -1) appWidgetHost?.deleteAppWidgetId(appWidgetId!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun configureWidget(appWidgetId: Int) {
|
||||
when (val appWidgetConfig = appWidgetManager!!.getAppWidgetInfo(appWidgetId).configure) {
|
||||
null -> createWidget(appWidgetId, null)
|
||||
else -> {
|
||||
Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE).apply {
|
||||
component = appWidgetConfig
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
putExtra(requestCodeString, requestCreateWidget)
|
||||
}.let {
|
||||
try { widgetPicker.launch(it) }
|
||||
catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createWidget(appWidgetId: Int, height: Int?) {
|
||||
if (appWidgetId == -1) return
|
||||
|
||||
val appWidgetInfo = appWidgetManager!!.getAppWidgetInfo(appWidgetId)
|
||||
val params: ConstraintLayout.LayoutParams?
|
||||
|
||||
when (height) {
|
||||
null -> {
|
||||
params = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, appWidgetInfo.minHeight)
|
||||
val updatedIds = splitWidgetIds.plus("$appWidgetId")
|
||||
val updatedHeights = splitWidgetHeights.plus("${appWidgetInfo.minHeight}")
|
||||
saveWidgetData(updatedIds, updatedHeights)
|
||||
}
|
||||
else -> params = ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, height)
|
||||
}
|
||||
|
||||
(appWidgetHost?.createView(lActivity!!.applicationContext, appWidgetId, appWidgetInfo) as WidgetHostView)
|
||||
.apply {
|
||||
setAppWidget(appWidgetId, appWidgetInfo)
|
||||
}.let {
|
||||
binding.widgetContainer.addView(it, params)
|
||||
widgetMenu(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateWidgets() {
|
||||
if (splitWidgetIds.size > 0) {
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
binding.widgetContainer.removeAllViews()
|
||||
splitWidgetIds.indices.forEach { i: Int ->
|
||||
createWidget(splitWidgetIds[i]!!.int(), splitWidgetHeights[i]!!.int())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun widgetMenu(hostView: WidgetHostView) {
|
||||
val appWidgetId = hostView.appWidgetId
|
||||
hostView.setOnLongClickListener {
|
||||
PopupMenu(requireContext(), it, Gravity.END).apply {
|
||||
menuInflater.inflate(R.menu.widget_menu, this.menu)
|
||||
show()
|
||||
setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.move_up -> moveWidget(appWidgetId, true)
|
||||
R.id.move_down -> moveWidget(appWidgetId, false)
|
||||
R.id.increase_height -> resizeWidget(appWidgetId, true)
|
||||
R.id.decrease_height -> resizeWidget(appWidgetId, false)
|
||||
R.id.delete_widget -> removeWidget(it as WidgetHostView)
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveWidget(widgetId: Int, moveUp: Boolean) {
|
||||
val tempIds = splitWidgetIds
|
||||
val tempHeights = splitWidgetHeights
|
||||
|
||||
splitWidgetIds.indexOf(widgetId.toString()).let { i ->
|
||||
when {
|
||||
moveUp && i > 0 -> {
|
||||
tempIds.swap(i-1, i)
|
||||
tempHeights.swap(i-1, i)
|
||||
}
|
||||
!moveUp && i < splitWidgetIds.size - 1 -> {
|
||||
tempIds.swap(i, i+1)
|
||||
tempHeights.swap(i, i+1)
|
||||
}
|
||||
else -> return
|
||||
}
|
||||
}
|
||||
|
||||
saveWidgetData(tempIds, tempHeights)
|
||||
updateWidgets()
|
||||
}
|
||||
|
||||
private fun resizeWidget(widgetId: Int, shouldAdd: Boolean) {
|
||||
val tempList = splitWidgetHeights
|
||||
|
||||
splitWidgetIds.indexOf(widgetId.toString()).let { i ->
|
||||
tempList[i] = when (shouldAdd) {
|
||||
true -> (splitWidgetHeights[i]!!.int().plus(50)).toString()
|
||||
false -> (splitWidgetHeights[i]!!.int().minus(50)).toString()
|
||||
}
|
||||
}
|
||||
|
||||
widgetPref.edit().putString(KEY_WIDGET_HEIGHTS, tempList.joinToString(separator = SEPARATOR)).apply()
|
||||
updateWidgets()
|
||||
}
|
||||
|
||||
private fun removeWidget(hostView: WidgetHostView) {
|
||||
hostView.let { v ->
|
||||
appWidgetHost?.deleteAppWidgetId(v.appWidgetId)
|
||||
binding.widgetContainer.removeView(v)
|
||||
|
||||
splitWidgetIds.indexOf(v.appWidgetId.toString()).let { i ->
|
||||
saveWidgetData(splitWidgetIds.minus(splitWidgetIds[i]), splitWidgetHeights.minus(splitWidgetHeights[i]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveWidgetData(idList: List<String?>, heightList: List<String?>) {
|
||||
widgetPref.edit()
|
||||
.putString(KEY_WIDGET_IDS, idList.joinToString(separator = SEPARATOR))
|
||||
.putString(KEY_WIDGET_HEIGHTS, heightList.joinToString(separator = SEPARATOR))
|
||||
.apply()
|
||||
}
|
||||
|
||||
private val widgetPref: SharedPreferences get() = lActivity!!.getSharedPreferences(PREFS_WIDGETS, 0)
|
||||
private val widgetIds: String? get() = widgetPref.getString(KEY_WIDGET_IDS, "")
|
||||
private val widgetHeights: String? get() = widgetPref.getString(KEY_WIDGET_HEIGHTS, "")
|
||||
private val splitWidgetIds: MutableList<String?> get() = widgetIds!!.split(SEPARATOR).toMutableList()
|
||||
private val splitWidgetHeights: MutableList<String?> get() = widgetHeights!!.split(SEPARATOR).toMutableList()
|
||||
|
||||
private fun <T> MutableList<T>.swap(index1: Int, index2: Int){
|
||||
val temp = this[index1]
|
||||
this[index1] = this[index2]
|
||||
this[index2] = temp
|
||||
}
|
||||
|
||||
private fun String.int() : Int {
|
||||
return try {
|
||||
this.toInt()
|
||||
} catch (e: Exception) {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package bums.lunatic.launcher.feeds
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import bums.lunatic.launcher.apps.DismissCalback
|
||||
import bums.lunatic.launcher.databinding.FeedsResultMenuBinding
|
||||
import bums.lunatic.launcher.model.LocationLog
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.query.Sort
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
internal class FeedsResult : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: FeedsResultMenuBinding
|
||||
private lateinit var searchWord: String
|
||||
private lateinit var packageManager: PackageManager
|
||||
private lateinit var appInfo: ApplicationInfo
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = FeedsResultMenuBinding.inflate(inflater, container, false)
|
||||
|
||||
/* get package name from fragment's tag */
|
||||
searchWord = tag.toString()
|
||||
WorkersDb.getRealm().query<LocationLog>().sort("time", Sort.DESCENDING).find()?.let {
|
||||
if (it.size > 0) {
|
||||
binding.logs.text = it.map {
|
||||
BLog.LOGE("LocLog >> ${it.toString()}")
|
||||
SimpleDateFormat("yyyy/MM/dd-HH:mm:ss").format(Date(it.time)).plus("\n").plus(it.mAddressLines.joinToString(" ,\n"))
|
||||
}.joinToString( ",\n")
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
var mDismissCalback : DismissCalback? = null
|
||||
|
||||
fun show(manager: FragmentManager, tag: String?, dismissCalback : DismissCalback?) {
|
||||
this.mDismissCalback = dismissCalback
|
||||
this.show(manager, tag)
|
||||
}
|
||||
|
||||
override fun show(manager: FragmentManager, tag: String?) {
|
||||
super.show(manager, tag)
|
||||
}
|
||||
|
||||
override fun dismiss() {
|
||||
BLog.LOGE("dismiss()")
|
||||
mDismissCalback?.invoke()
|
||||
super.dismiss()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
BLog.LOGE("onDismiss(dialog: DialogInterface)")
|
||||
mDismissCalback?.invoke()
|
||||
super.onDismiss(dialog)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,300 @@
|
||||
/*
|
||||
* 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.feeds
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.os.BatteryManager
|
||||
import android.os.Environment
|
||||
import android.os.StatFs
|
||||
import android.os.SystemClock
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.ChildSysInfoBinding
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_TEMP_UNIT
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||
import bums.lunatic.launcher.helpers.UniUtils.Companion.isNetworkAvailable
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.InputStreamReader
|
||||
import java.io.RandomAccessFile
|
||||
import java.net.NetworkInterface
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
internal class SystemStats {
|
||||
|
||||
private val toGb = 1.07374182E9f
|
||||
private fun string(id: Int) : String { return lActivity!!.getString(id) }
|
||||
private val inflater : LayoutInflater get() { return lActivity!!.layoutInflater }
|
||||
|
||||
/* ram info */
|
||||
fun ram(ramParent: LinearLayoutCompat) {
|
||||
val parent = ramParent.findViewById<View>(R.id.childSysInfo)
|
||||
val indicator = parent.findViewById<LinearProgressIndicator>(R.id.indicator)
|
||||
val textView = parent.findViewById<MaterialTextView>(R.id.textView)
|
||||
|
||||
val totalMem = memoryInfo.totalMem / toGb
|
||||
val availMem = memoryInfo.availMem / toGb
|
||||
val usedMem = totalMem - availMem
|
||||
|
||||
indicator.progress = (usedMem * 100 / totalMem).toInt()
|
||||
textView.text = Html.fromHtml(
|
||||
"<b>${string(R.string.ram)}</b><br>" +
|
||||
"${string(R.string.total)}: ${String.format("%.03f", totalMem)} GB | " +
|
||||
"${string(R.string.used)}: ${String.format("%.03f", usedMem)} GB | " +
|
||||
"${string(R.string.free)}: ${String.format("%.03f", availMem)} GB",
|
||||
Html.FROM_HTML_MODE_COMPACT)
|
||||
}
|
||||
|
||||
|
||||
/* cpu and battery info */
|
||||
fun cpu(cpuParent: LinearLayoutCompat) {
|
||||
val parent = cpuParent.findViewById<View>(R.id.childSysInfo)
|
||||
val indicator = parent.findViewById<LinearProgressIndicator>(R.id.indicator)
|
||||
val textView = parent.findViewById<MaterialTextView>(R.id.textView)
|
||||
|
||||
var cpuTemp = 0.0f
|
||||
try {
|
||||
val cpuTempProcess = Runtime.getRuntime().exec("cat sys/class/thermal/thermal_zone0/temp")
|
||||
cpuTempProcess.waitFor()
|
||||
val cpuTempReader = BufferedReader(InputStreamReader(cpuTempProcess.inputStream))
|
||||
cpuTemp = cpuTempReader.readLine().toFloat() / 1000.0f
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
|
||||
val finalCpuTemp = when (tempUnit) {
|
||||
0 -> "$cpuTemp ºC"
|
||||
1 -> "${String.format("%.01f", cpuTemp * 1.8 + 32)} ºF"
|
||||
else -> "$cpuTemp ºC"
|
||||
}
|
||||
|
||||
val cpuFreq = "${String.format("%.02f", minCpuFrequency.toFloat() / 1000)} - " +
|
||||
"${String.format("%.02f", maxCpuFrequency.toFloat() / 1000)} GHz"
|
||||
|
||||
indicator.progress = when (maxCpuFrequency) {
|
||||
0 -> 30
|
||||
else -> frequencyOfCore * 100 / maxCpuFrequency
|
||||
}
|
||||
textView.text = Html.fromHtml(
|
||||
"<b>${string(R.string.cpu)}</b><br>" +
|
||||
"${string(R.string.temperature)}: $finalCpuTemp | " +
|
||||
"${string(R.string.frequency)}: $cpuFreq",
|
||||
Html.FROM_HTML_MODE_COMPACT)
|
||||
}
|
||||
|
||||
|
||||
/* internal storage */
|
||||
fun intStorage(intParent: LinearLayoutCompat) {
|
||||
val parent = intParent.findViewById<View>(R.id.childSysInfo)
|
||||
val indicator = parent.findViewById<LinearProgressIndicator>(R.id.indicator)
|
||||
val textView = parent.findViewById<MaterialTextView>(R.id.textView)
|
||||
|
||||
val intPath = Environment.getExternalStorageDirectory().absolutePath
|
||||
val statFs = StatFs(intPath)
|
||||
val totalStorage = statFs.blockCountLong * statFs.blockSizeLong / toGb
|
||||
val availStorage = statFs.availableBlocksLong * statFs.blockSizeLong / toGb
|
||||
val usedStorage = totalStorage - availStorage
|
||||
|
||||
indicator.progress = (usedStorage * 100 / totalStorage).toInt()
|
||||
textView.text = Html.fromHtml(
|
||||
"<b>${intPath + File.separator}</b><br>" +
|
||||
"${string(R.string.total)}: ${String.format("%.03f", totalStorage)} GB | " +
|
||||
"${string(R.string.used)}: ${String.format("%.03f", usedStorage)} GB | " +
|
||||
"${string(R.string.free)}: ${String.format("%.03f", availStorage)} GB",
|
||||
Html.FROM_HTML_MODE_COMPACT)
|
||||
}
|
||||
|
||||
|
||||
/* external storage */
|
||||
fun extStorage(extParent: LinearLayoutCompat) {
|
||||
val extStorages = ContextCompat.getExternalFilesDirs(lActivity!!, null)
|
||||
/* sd card is available */
|
||||
if (extStorages.size > 1) {
|
||||
extParent.removeAllViews()
|
||||
for (extStorage in extStorages) {
|
||||
if (extStorage != null) {
|
||||
val binding = ChildSysInfoBinding.inflate(inflater)
|
||||
extParent.addView(binding.root)
|
||||
|
||||
val statFs = StatFs(extStorage.path)
|
||||
val blockSize = statFs.blockSizeLong
|
||||
val totalStorage = statFs.blockCountLong * blockSize / toGb
|
||||
val availStorage = statFs.availableBlocksLong * blockSize / toGb
|
||||
val usedStorage = totalStorage - availStorage
|
||||
|
||||
val sdcardPaths = extStorage.path.split(File.separator).toTypedArray()
|
||||
val sdPath = File.separator + sdcardPaths[1] + File.separator + sdcardPaths[2] + File.separator
|
||||
|
||||
binding.indicator.progress = (usedStorage * 100 / totalStorage).toInt()
|
||||
binding.textView.text = Html.fromHtml(
|
||||
"<b>$sdPath</b><br>" +
|
||||
"${string(R.string.total)}: ${String.format("%.03f", totalStorage)} GB | " +
|
||||
"${string(R.string.used)}: ${String.format("%.03f", usedStorage)} GB | " +
|
||||
"${string(R.string.free)}: ${String.format("%.03f", availStorage)} GB",
|
||||
Html.FROM_HTML_MODE_COMPACT)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
extParent.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun misc(misc: MaterialTextView) {
|
||||
val totalRootStorage = StatFs(Environment.getRootDirectory().path).blockCountLong *
|
||||
StatFs(Environment.getRootDirectory().path).blockSizeLong / toGb
|
||||
|
||||
val batteryIntent = lActivity!!.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
|
||||
val batteryTemp = batteryIntent!!.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0).toFloat() / 10
|
||||
val voltage = batteryIntent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0).toFloat() / 1000
|
||||
|
||||
val finalBatteryTemp = when (tempUnit) {
|
||||
0 -> "$batteryTemp ºC"
|
||||
1 -> "${String.format("%.01f", batteryTemp * 1.8 + 32)} ºF"
|
||||
else -> "$batteryTemp ºC"
|
||||
}
|
||||
|
||||
misc.text =
|
||||
"${longToString(SystemClock.elapsedRealtime())}\n" +
|
||||
"${longToString(SystemClock.uptimeMillis())}\n" +
|
||||
"${String.format("%.02f", memoryInfo.threshold / 1048576f)} MB\n" +
|
||||
"$finalBatteryTemp\n" +
|
||||
"$voltage V\n" +
|
||||
"${String.format("%.03f", totalRootStorage)} GB\n" +
|
||||
"${getIpAddress(true)}\n" +
|
||||
getIpAddress(false)
|
||||
}
|
||||
|
||||
|
||||
private val memoryInfo: ActivityManager.MemoryInfo get() {
|
||||
val memoryInfo = ActivityManager.MemoryInfo()
|
||||
val activityManager = lActivity!!.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
activityManager.getMemoryInfo(memoryInfo)
|
||||
return memoryInfo
|
||||
}
|
||||
|
||||
private val tempUnit: Int get() =
|
||||
lActivity!!.getSharedPreferences(PREFS_SETTINGS, 0).getInt(KEY_TEMP_UNIT, 0)
|
||||
|
||||
private fun longToString(long: Long) : String {
|
||||
var seconds = (long.toDouble() / 1000).roundToInt()
|
||||
val hours = TimeUnit.SECONDS.toHours(seconds.toLong())
|
||||
if (hours > 0) seconds -= TimeUnit.HOURS.toSeconds(hours).toInt()
|
||||
val minutes = if (seconds > 0) TimeUnit.SECONDS.toMinutes(seconds.toLong()) else 0
|
||||
if (minutes > 0) seconds -= TimeUnit.MINUTES.toSeconds(minutes).toInt()
|
||||
return if (hours > 0) String.format("%02d:%02d:%02d", hours, minutes, seconds)
|
||||
else String.format("%02d:%02d", minutes, seconds)
|
||||
}
|
||||
|
||||
/* frequency of core */
|
||||
private val frequencyOfCore: Int get() {
|
||||
var currentFReq = 0
|
||||
try {
|
||||
val currentFreq: Double
|
||||
val readerCurFreq =
|
||||
RandomAccessFile("/sys/devices/system/cpu/cpu" + 0 + "/cpufreq/scaling_cur_freq", "r")
|
||||
val curFreq = readerCurFreq.readLine()
|
||||
currentFreq = curFreq.toDouble() / 1000
|
||||
readerCurFreq.close()
|
||||
currentFReq = currentFreq.toInt()
|
||||
// println("$currentFReq----------------------------------------------------")
|
||||
} catch (ex: java.lang.Exception) {
|
||||
ex.printStackTrace()
|
||||
}
|
||||
return currentFReq
|
||||
}
|
||||
|
||||
/* minimum cpu frequency */
|
||||
private val minCpuFrequency: Int get() {
|
||||
var minFreq = -1
|
||||
try {
|
||||
val randomAccessFile =
|
||||
RandomAccessFile("/sys/devices/system/cpu/cpu" + 0 + "/cpufreq/cpuinfo_min_freq", "r")
|
||||
while (true) {
|
||||
val line = randomAccessFile.readLine() ?: break
|
||||
val timeInState = line.toInt()
|
||||
if (timeInState > 0) {
|
||||
val freq = timeInState / 1000
|
||||
if (freq > minFreq) {
|
||||
minFreq = freq
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception: java.lang.Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
return minFreq
|
||||
}
|
||||
|
||||
/* maximum cpu frequency */
|
||||
private val maxCpuFrequency: Int get() {
|
||||
var currentFReq = 0
|
||||
try {
|
||||
val currentFreq: Double
|
||||
val readerCurFreq =
|
||||
RandomAccessFile("/sys/devices/system/cpu/cpu" + 0 + "/cpufreq/cpuinfo_max_freq", "r")
|
||||
val curFreq = readerCurFreq.readLine()
|
||||
currentFreq = curFreq.toDouble() / 1000
|
||||
readerCurFreq.close()
|
||||
currentFReq = currentFreq.toInt()
|
||||
} catch (exception: java.lang.Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
return currentFReq
|
||||
}
|
||||
|
||||
private fun getIpAddress(getIPv4: Boolean): String {
|
||||
try {
|
||||
for (interFace in Collections.list(NetworkInterface.getNetworkInterfaces())) {
|
||||
for (address in Collections.list(interFace.inetAddresses)) {
|
||||
if (!address.isLoopbackAddress) {
|
||||
val addressStr = address.hostAddress
|
||||
val isIPv4 = addressStr!!.indexOf(':') < 0
|
||||
if (getIPv4) {
|
||||
if (isIPv4) return addressStr
|
||||
} else {
|
||||
if (!isIPv4 && isNetworkAvailable) {
|
||||
val endIndex = addressStr.indexOf('%')
|
||||
return if (endIndex < 0) addressStr
|
||||
else addressStr.substring(0, endIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: java.lang.Exception) { e.printStackTrace() }
|
||||
return string(R.string.na)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package bums.lunatic.launcher.feeds
|
||||
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetHostView
|
||||
import android.appwidget.AppWidgetProviderInfo
|
||||
import android.content.Context
|
||||
|
||||
|
||||
internal class WidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
|
||||
|
||||
override fun onCreateView(context: Context?, appWidgetId: Int, appWidget: AppWidgetProviderInfo?
|
||||
): AppWidgetHostView = WidgetHostView(context!!)
|
||||
|
||||
override fun stopListening() {
|
||||
super.stopListening()
|
||||
clearViews()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package bums.lunatic.launcher.feeds
|
||||
|
||||
import android.appwidget.AppWidgetHostView
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import android.view.ViewConfiguration
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
internal class WidgetHostView(context: Context) : AppWidgetHostView(context) {
|
||||
|
||||
private var hasPerformedLongPress = false
|
||||
private var pendingCheckForLongPress: CheckForLongPress? = null
|
||||
private var xPos = 0f
|
||||
private var yPos = 0f
|
||||
|
||||
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
|
||||
// Consume any touch events for ourselves after longpress is triggered
|
||||
if (hasPerformedLongPress) {
|
||||
hasPerformedLongPress = false
|
||||
return true
|
||||
}
|
||||
// Watch for long press events at this level to make sure
|
||||
// users can always pick up this widget
|
||||
when (ev.action) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
postCheckForLongClick()
|
||||
xPos = ev.x
|
||||
yPos = ev.y
|
||||
}
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
if (abs(ev.x - xPos) > 5 || abs(ev.y - yPos) > 5) {
|
||||
hasPerformedLongPress = false
|
||||
if (pendingCheckForLongPress != null) removeCallbacks(pendingCheckForLongPress)
|
||||
}
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||
hasPerformedLongPress = false
|
||||
if (pendingCheckForLongPress != null) removeCallbacks(pendingCheckForLongPress)
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise continue letting touch events fall through to children
|
||||
return false
|
||||
}
|
||||
|
||||
internal inner class CheckForLongPress : Runnable {
|
||||
private var originalWindowAttachCount = 0
|
||||
override fun run() {
|
||||
if (parent != null && hasWindowFocus()
|
||||
&& originalWindowAttachCount == windowAttachCount && !hasPerformedLongPress
|
||||
) {
|
||||
if (performLongClick()) hasPerformedLongPress = true
|
||||
}
|
||||
}
|
||||
|
||||
fun rememberWindowAttachCount() { originalWindowAttachCount = windowAttachCount }
|
||||
}
|
||||
|
||||
private fun postCheckForLongClick() {
|
||||
hasPerformedLongPress = false
|
||||
if (pendingCheckForLongPress == null) pendingCheckForLongPress = CheckForLongPress()
|
||||
pendingCheckForLongPress!!.rememberWindowAttachCount()
|
||||
postDelayed(pendingCheckForLongPress, ViewConfiguration.getLongPressTimeout().toLong())
|
||||
}
|
||||
|
||||
override fun cancelLongPress() {
|
||||
super.cancelLongPress()
|
||||
hasPerformedLongPress = false
|
||||
if (pendingCheckForLongPress != null) removeCallbacks(pendingCheckForLongPress)
|
||||
}
|
||||
|
||||
override fun getDescendantFocusability(): Int = FOCUS_BLOCK_DESCENDANTS
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.feeds.rss
|
||||
|
||||
|
||||
internal class Rss(
|
||||
val title: String,
|
||||
val link: String
|
||||
)
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.feeds.rss
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.databinding.ListItemWithBinding
|
||||
import bums.lunatic.launcher.home.adapters.RssItemDiffUtil
|
||||
import bums.lunatic.launcher.model.JGuru
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.net.URLEncoder
|
||||
import java.nio.charset.Charset
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class RssAdapter<T : RssDataInterface>(private val context: Context) :
|
||||
RecyclerView.Adapter<RssViewHolder>() {
|
||||
var items: ArrayList<RssDataInterface> = arrayListOf<RssDataInterface>()
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RssViewHolder {
|
||||
val binding = ListItemWithBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return RssViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
val dateFormat = SimpleDateFormat("hh:mm / yy - MM - dd")
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RssViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
holder.view.circlePreview.visibility = View.GONE
|
||||
if (item.category() == RssDataType.TAGS) {
|
||||
var txts = item.title().split("(")
|
||||
holder.view.title.text = "\n".plus(txts[0])
|
||||
holder.view.title.gravity = Gravity.CENTER
|
||||
if (txts.size > 1) {
|
||||
holder.view.desc.text = "(".plus(txts[1])
|
||||
holder.view.desc.gravity = Gravity.RIGHT
|
||||
holder.view.desc.visibility = View.VISIBLE
|
||||
|
||||
} else {
|
||||
holder.view.desc.text = ""
|
||||
}
|
||||
holder.view.date.visibility = View.GONE
|
||||
holder.view.date.text = ""
|
||||
} else {
|
||||
holder.view.title.gravity = Gravity.CENTER_VERTICAL.plus(Gravity.RIGHT)
|
||||
holder.view.desc.gravity = Gravity.CENTER_VERTICAL.plus(Gravity.RIGHT)
|
||||
holder.view.date.gravity = Gravity.CENTER_VERTICAL.plus(Gravity.RIGHT)
|
||||
holder.view.title.text = item.title()
|
||||
if (item.pubDate() > 1000L) {
|
||||
holder.view.date.text = dateFormat.format(Date(item.pubDate()))
|
||||
}
|
||||
holder.view.desc.text = item.description()
|
||||
holder.view.desc.visibility = View.VISIBLE
|
||||
holder.view.date.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
|
||||
if (item.thumbnailUrl().length ?: 0 > 6) {
|
||||
Picasso.get().load(item.thumbnailUrl()).into(holder.view.circlePreview)
|
||||
}
|
||||
|
||||
holder.view.root.setOnClickListener {
|
||||
holder.view.circlePreview.visibility = View.VISIBLE
|
||||
holder.view.circlePreview.postDelayed({
|
||||
holder.view.circlePreview.visibility = View.GONE
|
||||
},500L)
|
||||
}
|
||||
|
||||
holder.view.root.setOnLongClickListener {
|
||||
if(item is JGuru) {
|
||||
openOpera(
|
||||
"https://cili.site/search?q=${
|
||||
URLEncoder.encode(
|
||||
item.model,
|
||||
Charset.defaultCharset().name()
|
||||
)
|
||||
}"
|
||||
)
|
||||
}
|
||||
openOpera(item.originPage())
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fun openOpera(schemeString : String) {
|
||||
val gmmIntentUri = Uri.parse(schemeString)
|
||||
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
|
||||
mapIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
mapIntent.setPackage("com.opera.browser")
|
||||
lActivity?.startActivity(mapIntent)
|
||||
}
|
||||
|
||||
fun updateData(newList: List<RssDataInterface>) {
|
||||
DiffUtil.calculateDiff(RssItemDiffUtil(items, newList)).dispatchUpdatesTo(this)
|
||||
items.clear()
|
||||
items.addAll(newList)
|
||||
}
|
||||
}
|
||||
|
||||
class RssViewHolder(var view: ListItemWithBinding) : RecyclerView.ViewHolder(view.root) {
|
||||
|
||||
}
|
||||
|
||||
internal class RssItemDiffUtil(
|
||||
private val oldList: List<RssDataInterface>, private val newList: List<RssDataInterface>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].originPage() == newList[newItemPosition].originPage()
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].originPage() == newList[newItemPosition].originPage()
|
||||
}
|
||||
|
||||
//fun View.setOnVeryLongClickListener(listener: () -> Unit) {
|
||||
// setOnTouchListener(object : View.OnTouchListener {
|
||||
//
|
||||
// private val longClickDuration = 2000L
|
||||
// private val handler = Handler(Looper.getMainLooper())
|
||||
//
|
||||
// override fun onTouch(v: View?, event: MotionEvent?): Boolean {
|
||||
// if (event?.action == MotionEvent.ACTION_DOWN) {
|
||||
// handler.postDelayed({ listener.invoke() }, longClickDuration)
|
||||
// } else if (event?.action == MotionEvent.ACTION_UP) {
|
||||
// handler.removeCallbacksAndMessages(null)
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
// })
|
||||
//}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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.feeds.rss
|
||||
|
||||
import android.util.Xml
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
internal class RssParser {
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
fun parse(inputStream: InputStream): List<Rss> {
|
||||
return inputStream.use { stream ->
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(stream, null)
|
||||
parser.nextTag()
|
||||
readFeed(parser)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readFeed(parser: XmlPullParser): List<Rss> {
|
||||
parser.require(XmlPullParser.START_TAG, null, "rss")
|
||||
var title: String? = null
|
||||
var link: String? = null
|
||||
val items: MutableList<Rss> = ArrayList()
|
||||
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
val name = parser.name
|
||||
if (name == "title") {
|
||||
title = readTitle(parser)
|
||||
} else if (name == "link") {
|
||||
link = readLink(parser)
|
||||
}
|
||||
|
||||
if (title != null && link != null) {
|
||||
val item = Rss(title, link)
|
||||
items.add(item)
|
||||
title = null
|
||||
link = null
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readLink(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "link")
|
||||
val link = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "link")
|
||||
return link
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readTitle(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "title")
|
||||
val title = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "title")
|
||||
return title
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readText(parser: XmlPullParser): String {
|
||||
var result = ""
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
result = parser.text
|
||||
parser.nextTag()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.feeds.rss
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ResultReceiver
|
||||
import androidx.core.app.JobIntentService
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_RSS_URL
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.RSS_ITEMS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.RSS_RECEIVER
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.Serializable
|
||||
import java.net.URL
|
||||
|
||||
|
||||
internal class RssService : JobIntentService() {
|
||||
|
||||
override fun onHandleWork(intent: Intent) {
|
||||
val settingsPrefs = getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
val rssUrl = settingsPrefs.getString(KEY_RSS_URL, "")
|
||||
var rssItems: List<Rss?>? = null
|
||||
|
||||
try {
|
||||
val parser = RssParser()
|
||||
rssItems = getInputStream(rssUrl)?.let { parser.parse(it) }
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putSerializable(RSS_ITEMS, rssItems as? Serializable)
|
||||
|
||||
val receiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(RSS_RECEIVER, ResultReceiver::class.java)
|
||||
} else {
|
||||
@Suppress("DEPRECATION") intent.getParcelableExtra(RSS_RECEIVER)
|
||||
}
|
||||
|
||||
receiver?.send(0, bundle)
|
||||
}
|
||||
|
||||
private fun getInputStream(link: String?): InputStream? {
|
||||
return try {
|
||||
val url = URL(link)
|
||||
url.openConnection().getInputStream()
|
||||
} catch (ioException: IOException) {
|
||||
ioException.printStackTrace()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import android.app.admin.DeviceAdminReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
|
||||
|
||||
internal class AdminReceiver : DeviceAdminReceiver() {
|
||||
|
||||
override fun onDisabled(context: Context, intent: Intent) {
|
||||
super.onDisabled(context, intent)
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(
|
||||
Intent("device_admin_action_disabled")
|
||||
)
|
||||
}
|
||||
|
||||
override fun onEnabled(context: Context, intent: Intent) {
|
||||
super.onEnabled(context, intent)
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(
|
||||
Intent("device_admin_action_enabled")
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package bums.lunatic.launcher.helpers
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothAdapter
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.app.ActivityCompat
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class BluetoothManager {
|
||||
|
||||
enum class BLUETOOTH_STATE(val statestr: String) {
|
||||
ENABLED("enabledBlutooth"),
|
||||
DISABLED("disableBlutooth"),
|
||||
NOT_SUPPORT("notSupport")
|
||||
}
|
||||
|
||||
lateinit var context: Context
|
||||
var blueToothAdapter:BluetoothAdapter? = null
|
||||
|
||||
constructor(context: Context) {
|
||||
this.context = context
|
||||
}
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
// init {
|
||||
// this.context = context
|
||||
// }
|
||||
|
||||
fun initBluetoothAdapter(){
|
||||
if ( blueToothAdapter == null ){
|
||||
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as android.bluetooth.BluetoothManager
|
||||
blueToothAdapter = bluetoothManager.getAdapter()
|
||||
}
|
||||
}
|
||||
|
||||
fun register(){
|
||||
if (context == null) return
|
||||
context.registerReceiver(bluetoothreceiver, addFilterAction())
|
||||
}
|
||||
|
||||
fun unregister(){
|
||||
if (context == null) return
|
||||
context.unregisterReceiver(bluetoothreceiver)
|
||||
}
|
||||
|
||||
//페어링된 디바이스 정보 가져오기
|
||||
|
||||
fun getPairedDevices() {
|
||||
val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as android.bluetooth.BluetoothManager
|
||||
blueToothAdapter = bluetoothManager.adapter
|
||||
if (ActivityCompat.checkSelfPermission(context,Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) return
|
||||
var pairedDevices = blueToothAdapter?.bondedDevices
|
||||
if (pairedDevices?.size ?: 0 > 0) {
|
||||
pairedDevices?.forEach { i ->
|
||||
//bondState : 12 (페어링 등록된 상태)
|
||||
//bondState : 10 (페어링 등록 안됨)
|
||||
BLog.LOGE("getPairedDevices() / name : ${i.name}")
|
||||
BLog.LOGE("getPairedDevices() / bondState : ${i.bondState}")
|
||||
val isConnected = isConnected(i)
|
||||
if(PrefHelper.carName.length > 2 && i.name.equals(PrefHelper.carName) && isConnected != PrefHelper.isConnectedCar) {
|
||||
PrefHelper.isConnectedCar = isConnected
|
||||
sendToI(PrefHelper.isConnectedCar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun sendToI(boolean: Boolean) {
|
||||
if (PrefHelper.telegramSendTarget.length > 5) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val url =
|
||||
"https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/sendMessage?chat_id=${PrefHelper.telegramSendTarget}&text=${if(boolean) {
|
||||
"돼지가 ${PrefHelper.carName}에 탔다요."
|
||||
}else {
|
||||
"${PrefHelper.carName}의 시동이 꺼졌다요."
|
||||
}}"
|
||||
//7068729507
|
||||
// OkHttp 클라이언트 객체 생성
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectionPool(ConnectionPool(5, 60, TimeUnit.SECONDS))
|
||||
.build()
|
||||
|
||||
// GET 요청 객체 생성
|
||||
val builder: Request.Builder = Request.Builder().url(url)
|
||||
.addHeader("Content-Type", "application/json").get()
|
||||
|
||||
val request: Request = builder.build()
|
||||
|
||||
BLog.LOGE("sendToI telegram before request ")
|
||||
// OkHttp 클라이언트로 GET 요청 객체 전송
|
||||
val response: Response = client.newCall(request).execute()
|
||||
if (response.isSuccessful()) {
|
||||
// 응답 받아서 처리
|
||||
val body: ResponseBody? = response.body()
|
||||
if (body != null) {
|
||||
|
||||
}
|
||||
BLog.LOGE("sendToI telegram response isSuccessful ${body}")
|
||||
} else BLog.LOGE("sendToI telegram Error Occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
@SuppressLint("MissingPermission")
|
||||
fun isConnected(device: BluetoothDevice): Boolean {
|
||||
try {
|
||||
val m = device.javaClass.getMethod("isConnected")
|
||||
val connected = m.invoke(device) as Boolean
|
||||
BLog.LOGE("D >> " + device.name + " || isConnected >>> " + (if (connected) "TRUE" else "FALSE"))
|
||||
return connected
|
||||
} catch (e: Exception) {
|
||||
throw IllegalStateException(e)
|
||||
}
|
||||
}
|
||||
|
||||
//블루투스 상태(켜짐 / 꺼짐 / 지원 불가 기기)
|
||||
fun blueToothState(): String {
|
||||
if (blueToothAdapter != null) {
|
||||
if (blueToothAdapter!!.isEnabled) {
|
||||
return BLUETOOTH_STATE.ENABLED.statestr
|
||||
} else {
|
||||
return BLUETOOTH_STATE.DISABLED.statestr
|
||||
}
|
||||
}
|
||||
return BLUETOOTH_STATE.NOT_SUPPORT.statestr
|
||||
}
|
||||
|
||||
//add Receive action
|
||||
private fun addFilterAction(): IntentFilter {
|
||||
val stateFilter = IntentFilter()
|
||||
stateFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED) //BluetoothAdapter.ACTION_STATE_CHANGED : 블루투스 상태변화 액션
|
||||
stateFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
|
||||
stateFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED) //연결 확인
|
||||
stateFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) //연결 끊김 확인
|
||||
/*
|
||||
stateFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
|
||||
stateFilter.addAction(BluetoothDevice.ACTION_FOUND) //기기 검색됨
|
||||
stateFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED) //기기 검색 시작
|
||||
stateFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED) //기기 검색 종료
|
||||
stateFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
|
||||
*/
|
||||
return stateFilter
|
||||
}
|
||||
|
||||
//BlueTooth Receiver
|
||||
private var bluetoothreceiver = object : BroadcastReceiver(){
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
var state = intent?.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
|
||||
val action = intent!!.action
|
||||
if (context == null) return
|
||||
if (ActivityCompat.checkSelfPermission(context!!, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) return
|
||||
val device: BluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)!!
|
||||
getPairedDevices()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.textfield.TextInputEditText
|
||||
|
||||
|
||||
internal class ColorPicker(private val initialColor: String,
|
||||
private val editText: TextInputEditText, private val sliderA: Slider, private val sliderR: Slider,
|
||||
private val sliderG: Slider, private val sliderB: Slider, private val colorPreview: ConstraintLayout) {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun pickColor() {
|
||||
editText.setText(initialColor)
|
||||
stringToSlider(initialColor)
|
||||
colorPreview.setBackgroundColor(Color.parseColor("#$initialColor"))
|
||||
|
||||
editText.addTextChangedListener(object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
if (s.length == 6){
|
||||
sliderA.value = 255F
|
||||
sliderR.value = Integer.parseInt(s.substring(0..1), 16).toFloat()
|
||||
sliderG.value = Integer.parseInt(s.substring(2..3), 16).toFloat()
|
||||
sliderB.value = Integer.parseInt(s.substring(4..5), 16).toFloat()
|
||||
} else if (s.length == 8){
|
||||
stringToSlider(s.toString())
|
||||
} else if (s.isEmpty()) {
|
||||
sliderA.value = 0F
|
||||
sliderR.value = 0F
|
||||
sliderG.value = 0F
|
||||
sliderB.value = 0F
|
||||
}
|
||||
}
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||
})
|
||||
|
||||
sliderA.addOnChangeListener(Slider.OnChangeListener { _: Slider?, _: Float, _: Boolean ->
|
||||
editText.setText(colorString.uppercase())
|
||||
colorPreview.setBackgroundColor(Color.parseColor("#$colorString"))
|
||||
})
|
||||
|
||||
sliderR.addOnChangeListener(Slider.OnChangeListener { _: Slider?, _: Float, _: Boolean ->
|
||||
editText.setText(colorString.uppercase())
|
||||
colorPreview.setBackgroundColor(Color.parseColor("#$colorString"))
|
||||
})
|
||||
|
||||
sliderG.addOnChangeListener(Slider.OnChangeListener { _: Slider?, _: Float, _: Boolean ->
|
||||
editText.setText(colorString.uppercase())
|
||||
colorPreview.setBackgroundColor(Color.parseColor("#$colorString"))
|
||||
})
|
||||
|
||||
sliderB.addOnChangeListener(Slider.OnChangeListener { _: Slider?, _: Float, _: Boolean ->
|
||||
editText.setText(colorString.uppercase())
|
||||
colorPreview.setBackgroundColor(Color.parseColor("#$colorString"))
|
||||
})
|
||||
}
|
||||
|
||||
private fun stringToSlider(s: String) {
|
||||
sliderA.value = Integer.parseInt(s.substring(0..1), 16).toFloat()
|
||||
sliderR.value = Integer.parseInt(s.substring(2..3), 16).toFloat()
|
||||
sliderG.value = Integer.parseInt(s.substring(4..5), 16).toFloat()
|
||||
sliderB.value = Integer.parseInt(s.substring(6..7), 16).toFloat()
|
||||
}
|
||||
|
||||
private val colorString: String get() {
|
||||
var a = Integer.toHexString((((255*sliderA.value)/sliderA.valueTo).toInt()))
|
||||
if(a.length==1) a = "0$a"
|
||||
var r = Integer.toHexString((((255*sliderR.value)/sliderR.valueTo).toInt()))
|
||||
if(r.length==1) r = "0$r"
|
||||
var g = Integer.toHexString((((255*sliderG.value)/sliderG.valueTo).toInt()))
|
||||
if(g.length==1) g = "0$g"
|
||||
var b = Integer.toHexString((((255*sliderB.value)/sliderB.valueTo).toInt()))
|
||||
if(b.length==1) b = "0$b"
|
||||
return "$a$r$g$b"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
|
||||
|
||||
internal class Constants {
|
||||
|
||||
companion object {
|
||||
|
||||
/* first launch */
|
||||
const val PREFS_FIRST_LAUNCH = "rasel.lunar.launcher.FIRST_LAUNCH"
|
||||
const val KEY_FIRST_LAUNCH = "first_launch"
|
||||
|
||||
/* widgets */
|
||||
const val PREFS_WIDGETS = "rasel.lunar.launcher.WIDGETS"
|
||||
const val KEY_WIDGET_IDS = "widget_ids"
|
||||
const val KEY_WIDGET_HEIGHTS = "widget_heights"
|
||||
|
||||
const val PREFS_PKGICS = "rasel.lunar.launcher.Icons"
|
||||
|
||||
|
||||
/* settings */
|
||||
const val PREFS_SETTINGS = "rasel.lunar.launcher.SETTINGS"
|
||||
const val KEY_TIME_FORMAT = "time_format"
|
||||
const val KEY_DATE_FORMAT = "date_format"
|
||||
const val KEY_CITY_NAME = "city_name"
|
||||
const val KEY_OWM_API = "owm_api"
|
||||
const val KEY_TEMP_UNIT = "temp_unit"
|
||||
const val KEY_SHOW_CITY = "show_city"
|
||||
const val KEY_TODO_COUNTS = "todo_count"
|
||||
const val KEY_TODO_LOCK = "todo_lock"
|
||||
const val KEY_KEYBOARD_SEARCH = "keyboard_search"
|
||||
const val KEY_QUICK_LAUNCH = "quick_launch"
|
||||
const val KEY_APPS_LAYOUT = "apps_layout"
|
||||
const val KEY_APPS_COUNT = "apps_count"
|
||||
const val KEY_DRAW_ALIGN = "drawer_alignment"
|
||||
const val KEY_ICON_PACK = "icon_pack"
|
||||
const val KEY_GRID_COLUMNS = "grid_columns"
|
||||
const val KEY_SCROLLBAR_HEIGHT = "scrollbar_height"
|
||||
const val KEY_WINDOW_BACKGROUND = "window_background"
|
||||
const val KEY_APPLICATION_THEME = "application_theme"
|
||||
const val KEY_STATUS_BAR = "status_bar"
|
||||
const val KEY_BACK_HOME = "back_home"
|
||||
const val KEY_SHORTCUT_COUNT = "shortcut_count"
|
||||
const val KEY_ICON_SIZE = "icon_size"
|
||||
const val KEY_RSS_URL = "rss_url"
|
||||
const val KEY_RSS_URL2 = "rss_url2"
|
||||
const val KEY_LOCK_METHOD = "lock_method"
|
||||
|
||||
/* --- */
|
||||
const val DEFAULT_DATE_FORMAT = "EEE, dd, MM, yyyy"
|
||||
const val DEFAULT_ICON_SIZE = 44
|
||||
const val DEFAULT_ICON_PACK = "default_icon_pack"
|
||||
const val DEFAULT_GRID_COLUMNS = 4
|
||||
const val DEFAULT_SCROLLBAR_HEIGHT = 400
|
||||
const val MAX_SHORTCUTS = 6
|
||||
const val MAX_FAVORITE_APPS = 6
|
||||
|
||||
const val BOTTOM_SHEET_TAG = "rasel.lunar.launcher.TAG"
|
||||
const val SEPARATOR = "||"
|
||||
const val ACCESSIBILITY_SERVICE_LOCK_SCREEN = "rasel.lunar.launcher.LOCK_SCREEN_SERVICE"
|
||||
const val AUTHENTICATOR_TYPE = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||
|
||||
const val rssJobId = 101
|
||||
const val widgetHostId = 102
|
||||
const val requestPickWidget = 103
|
||||
const val requestCreateWidget = 104
|
||||
|
||||
/* app names */
|
||||
const val PREFS_APP_NAMES = "rasel.lunar.launcher.APP_NAMES"
|
||||
|
||||
/* favorite apps */
|
||||
const val PREFS_FAVORITE_APPS = "rasel.lunar.launcher.FAVORITE_APPS"
|
||||
const val KEY_APP_NO_ = "app_no_"
|
||||
|
||||
/* phone and url shortcuts */
|
||||
const val PREFS_SHORTCUTS = "rasel.lunar.launcher.SHORTCUTS"
|
||||
const val KEY_SHORTCUT_NO_ = "shortcut_no_"
|
||||
const val SHORTCUT_TYPE_URL = "shortcut_type_url"
|
||||
const val SHORTCUT_TYPE_PHONE = "shortcut_type_phone"
|
||||
|
||||
/* to-do database */
|
||||
const val TODO_DATABASE_NAME = "rasel.lunar.launcher.TODOS"
|
||||
const val TODO_DATABASE_VERSION = 1
|
||||
const val TODO_TABLE_NAME = "todo_table"
|
||||
const val TODO_COLUMN_ID = "todo_column_id"
|
||||
const val TODO_COLUMN_NAME = "todo_column_name"
|
||||
const val TODO_COLUMN_CREATED = "todo_column_created"
|
||||
|
||||
/* rss feed */
|
||||
const val RSS_ITEMS = "rss_items"
|
||||
const val RSS_RECEIVER = "rss_receiver"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import android.accessibilityservice.AccessibilityService
|
||||
import android.accessibilityservice.AccessibilityServiceInfo
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.view.accessibility.AccessibilityEvent
|
||||
import android.view.accessibility.AccessibilityManager
|
||||
|
||||
|
||||
internal class LockService : AccessibilityService() {
|
||||
|
||||
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
|
||||
override fun onInterrupt() {}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN)
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
/* check whether accessibility service is enabled */
|
||||
fun isAccessibilityServiceEnabled(context: Context): Boolean {
|
||||
val accessibilityManager =
|
||||
context.getSystemService(ACCESSIBILITY_SERVICE) as AccessibilityManager
|
||||
val accessibilityServiceInfoList: List<AccessibilityServiceInfo> =
|
||||
accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK)
|
||||
|
||||
for (enabledService in accessibilityServiceInfoList) {
|
||||
val enabledServiceInfo = enabledService.resolveInfo.serviceInfo
|
||||
if (enabledServiceInfo.packageName == context.packageName && enabledServiceInfo.name == LockService::class.java.name) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package bums.lunatic.launcher.helpers
|
||||
|
||||
import android.content.SharedPreferences
|
||||
|
||||
enum class PrefString {
|
||||
weatherApiKey,
|
||||
locationApi,
|
||||
telegramBotApi,
|
||||
telegramMyId,
|
||||
telegramSendTarget,
|
||||
carName;
|
||||
fun set(value : String) = PrefHelper.putString(this.name, value)
|
||||
fun get(def : String? = null) : String = PrefHelper.getString(this.name, def as? String ?: "") ?: ""
|
||||
}
|
||||
|
||||
enum class PrefLong {
|
||||
locationTimePeriod,
|
||||
locationDistance,
|
||||
shortTimePeriod,
|
||||
longTimePeriod,
|
||||
midTimePeriod,
|
||||
maxQueryCount;
|
||||
fun set(value : Long) = PrefHelper.putLong(this.name, value)
|
||||
fun get(def : Long? = null) : Long = PrefHelper.getLong(this.name, def as? Long ?: 0L) ?: 0L
|
||||
}
|
||||
|
||||
enum class PrefBoolean {
|
||||
location,
|
||||
rootPermisssion,
|
||||
isConnectedCar,
|
||||
useQuickLaunch,
|
||||
openWithKayboard,
|
||||
showAppResultCount,
|
||||
weatherDress,
|
||||
weatherState,
|
||||
showCallHistory,
|
||||
showSMSHistory,
|
||||
showNotificationHistory,
|
||||
showNewsHistory,
|
||||
showNowPlaying,
|
||||
;
|
||||
fun set(value : Boolean) = PrefHelper.putBoolean(this.name, value)
|
||||
fun get(def : Boolean? = null) : Boolean = PrefHelper.getBoolean(this.name, def as? Boolean ?: false) ?: false
|
||||
}
|
||||
enum class PrefKey {
|
||||
TYPE_STRING,
|
||||
locationApi,
|
||||
telegramBotApi,
|
||||
telegramSendTarget,
|
||||
carName,
|
||||
TYPE_LONG,
|
||||
shortTimePeriod,
|
||||
longTimePeriod,
|
||||
midTimePeriod,
|
||||
maxQueryCount,
|
||||
TYPE_BOOL,
|
||||
location,
|
||||
isConnectedCar,
|
||||
useQuickLaunch,
|
||||
openWithKayboard,
|
||||
showAppResultCount,
|
||||
TYPE_END;
|
||||
|
||||
fun set(value : Any): Unit {
|
||||
when(this.ordinal) {
|
||||
in (TYPE_STRING.ordinal..TYPE_LONG.ordinal) -> {
|
||||
(value as? String)?.let { PrefHelper.putString(this.name, it) }
|
||||
}
|
||||
in (TYPE_LONG.ordinal..TYPE_BOOL.ordinal) -> {
|
||||
(value as? Long)?.let { PrefHelper.putLong(this.name, it) }
|
||||
}
|
||||
in (TYPE_BOOL.ordinal..TYPE_END.ordinal) -> {
|
||||
(value as? Boolean)?.let { PrefHelper.putBoolean(this.name, it) }
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
fun get(def : Any? = null) : Any? {
|
||||
when(this.ordinal) {
|
||||
in (TYPE_STRING.ordinal..TYPE_LONG.ordinal) -> {
|
||||
return PrefHelper.getString(this.name, def as? String ?: "")
|
||||
}
|
||||
in (TYPE_LONG.ordinal..TYPE_BOOL.ordinal) -> {
|
||||
return PrefHelper.getLong(this.name, def as? Long ?: 0L)
|
||||
}
|
||||
in (TYPE_BOOL.ordinal..TYPE_END.ordinal) -> {
|
||||
return PrefHelper.getBoolean(this.name, def as? Boolean ?: false)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
object PrefHelper {
|
||||
val D_PREFIX = "rasel.lunar.launcher.helpers"
|
||||
val BOOL_PRE = D_PREFIX.plus(".BOOL.")
|
||||
val STRING_PRE = D_PREFIX.plus(".STRING.")
|
||||
val LONG_PRE = D_PREFIX.plus(".LONG.")
|
||||
fun inject(SharedPreferences : SharedPreferences) {
|
||||
this.sharedPreferences = SharedPreferences
|
||||
}
|
||||
var sharedPreferences : SharedPreferences? = null
|
||||
fun getBoolean(key : String, def :Boolean) = this.sharedPreferences?.getBoolean(BOOL_PRE.plus(key),def) ?: def
|
||||
fun putBoolean(key : String, value :Boolean) = this.sharedPreferences?.edit()?.putBoolean(BOOL_PRE.plus(key),value)?.apply()
|
||||
fun getLong(key : String, def :Long) = this.sharedPreferences?.getLong(LONG_PRE.plus(key),def) ?: def
|
||||
fun putLong(key : String, value :Long) = this.sharedPreferences?.edit()?.putLong(LONG_PRE.plus(key),value)?.apply()
|
||||
fun getString(key : String, def :String) = this.sharedPreferences?.getString(STRING_PRE.plus(key),def) ?: def
|
||||
fun putString(key : String, value :String) = this.sharedPreferences?.edit()?.putString(STRING_PRE.plus(key),value)?.apply()
|
||||
|
||||
|
||||
fun location(boolean: Boolean) = PrefKey.location.set(boolean)
|
||||
fun isLocationOn() = (PrefKey.location.get() as? Boolean) ?: false
|
||||
|
||||
var locationApi : String
|
||||
get() = PrefKey.locationApi.get() as? String ?: ""
|
||||
set(value) = PrefKey.locationApi.set(value)
|
||||
|
||||
var telegramBotApi : String
|
||||
get() = PrefKey.telegramBotApi.get() as? String ?: ""
|
||||
set(value) = PrefKey.telegramBotApi.set(value)
|
||||
|
||||
var telegramSendTarget : String
|
||||
get() = PrefKey.telegramSendTarget.get() as? String ?: ""
|
||||
set(value) = PrefKey.telegramSendTarget.set(value)
|
||||
|
||||
var carName : String
|
||||
get() = PrefKey.carName.get() as? String ?: ""
|
||||
set(value) = PrefKey.carName.set(value)
|
||||
|
||||
|
||||
var shortTimePeriod : Long
|
||||
get() = PrefKey.shortTimePeriod.get(20L) as? Long ?: 20L
|
||||
set(value) = PrefKey.shortTimePeriod.set(value)
|
||||
|
||||
var longTimePeriod : Long
|
||||
get() = PrefKey.longTimePeriod.get(60L) as? Long ?: 60L
|
||||
set(value) = PrefKey.longTimePeriod.set(value)
|
||||
|
||||
var midTimePeriod : Long
|
||||
get() = PrefKey.midTimePeriod.get(30L) as? Long ?: 30L
|
||||
set(value) = PrefKey.midTimePeriod.set(value)
|
||||
|
||||
|
||||
var isConnectedCar : Boolean
|
||||
get() = PrefKey.isConnectedCar.get() as? Boolean ?: false
|
||||
set(value) = PrefKey.isConnectedCar.set(value)
|
||||
|
||||
var useQuickLaunch : Boolean
|
||||
get() = PrefKey.useQuickLaunch.get() as? Boolean ?: false
|
||||
set(value) = PrefKey.useQuickLaunch.set(value)
|
||||
|
||||
var openWithKayboard : Boolean
|
||||
get() = PrefKey.openWithKayboard.get() as? Boolean ?: false
|
||||
set(value) = PrefKey.openWithKayboard.set(value)
|
||||
|
||||
var showAppResultCount : Boolean
|
||||
get() = PrefKey.showAppResultCount.get() as? Boolean ?: false
|
||||
set(value) = PrefKey.showAppResultCount.set(value)
|
||||
|
||||
|
||||
|
||||
}
|
||||
typealias BLOCK = ()->Unit
|
||||
inline fun Boolean.letTrue(block: BLOCK) {
|
||||
if (this) {
|
||||
block.invoke()
|
||||
} else {
|
||||
// elseblock.invoke()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.GestureDetector
|
||||
import android.view.GestureDetector.SimpleOnGestureListener
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import android.view.ViewParent
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
internal open class SwipeTouchListener(c: Context?) : OnTouchListener {
|
||||
|
||||
private val gestureDetector: GestureDetector = GestureDetector(c, GestureListener())
|
||||
private lateinit var viewParent: ViewParent
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
|
||||
viewParent = view.parent
|
||||
return gestureDetector.onTouchEvent(motionEvent)
|
||||
}
|
||||
|
||||
private inner class GestureListener : SimpleOnGestureListener() {
|
||||
|
||||
override fun onDown(e: MotionEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSingleTapUp(e: MotionEvent): Boolean {
|
||||
onClick()
|
||||
return super.onSingleTapUp(e)
|
||||
}
|
||||
|
||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||
onDoubleClick()
|
||||
return super.onDoubleTap(e)
|
||||
}
|
||||
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
onLongClick()
|
||||
viewParent.requestDisallowInterceptTouchEvent(true)
|
||||
super.onLongPress(e)
|
||||
}
|
||||
|
||||
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
|
||||
try {
|
||||
val diffY = e2.y - e1!!.y
|
||||
val diffX = e2.x - e1.x
|
||||
val swipeThreshold = 15
|
||||
val swipeVelocityThreshold = 90
|
||||
if (abs(diffX) > abs(diffY)) {
|
||||
if (abs(diffX) > swipeThreshold && abs(velocityX) > swipeVelocityThreshold) {
|
||||
when {
|
||||
diffX > 0 -> onSwipeRight()
|
||||
else -> onSwipeLeft()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (abs(diffY) > swipeThreshold && abs(velocityY) > swipeVelocityThreshold) {
|
||||
when {
|
||||
diffY > 0 -> onSwipeDown()
|
||||
else -> onSwipeUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun onSwipeRight() {}
|
||||
fun onSwipeLeft() {}
|
||||
open fun onSwipeUp() {}
|
||||
open fun onSwipeDown() {}
|
||||
open fun onClick() {}
|
||||
open fun onDoubleClick() {}
|
||||
open fun onLongClick() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.admin.DevicePolicyManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.util.DisplayMetrics
|
||||
import android.util.TypedValue
|
||||
import android.view.WindowInsets
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.ACCESSIBILITY_SERVICE_LOCK_SCREEN
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.AUTHENTICATOR_TYPE
|
||||
import java.io.DataOutputStream
|
||||
|
||||
|
||||
internal class UniUtils {
|
||||
|
||||
companion object {
|
||||
|
||||
/* get display width */
|
||||
val screenWidth: Int get() {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val windowMetrics = lActivity!!.windowManager.currentWindowMetrics
|
||||
val insets = windowMetrics.windowInsets
|
||||
.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
|
||||
windowMetrics.bounds.width() - insets.left - insets.right
|
||||
} else {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
@Suppress("DEPRECATION") lActivity!!.windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
displayMetrics.widthPixels
|
||||
}
|
||||
}
|
||||
|
||||
/* get display height */
|
||||
val screenHeight: Int get() {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val windowMetrics = lActivity!!.windowManager.currentWindowMetrics
|
||||
val insets = windowMetrics.windowInsets
|
||||
.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars())
|
||||
windowMetrics.bounds.height() - insets.top - insets.bottom
|
||||
} else {
|
||||
val displayMetrics = DisplayMetrics()
|
||||
@Suppress("DEPRECATION") lActivity!!.windowManager.defaultDisplay.getMetrics(displayMetrics)
|
||||
displayMetrics.heightPixels
|
||||
}
|
||||
}
|
||||
|
||||
/* copy texts to clipboard */
|
||||
fun copyToClipboard(context: Context, copiedString: String?) {
|
||||
val clipBoard =
|
||||
lActivity!!.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipBoard.setPrimaryClip(ClipData.newPlainText("", copiedString))
|
||||
Toast.makeText(context, context.getString(R.string.copied_message), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
/* expand notification panel */
|
||||
@SuppressLint("WrongConstant")
|
||||
fun expandNotificationPanel(context: Context) {
|
||||
try {
|
||||
Class.forName("android.app.StatusBarManager")
|
||||
.getMethod("expandNotificationsPanel")
|
||||
.invoke(context.getSystemService("statusbar"))
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/* lock using preferred method */
|
||||
fun lockMethod(lockMethodValue: Int, context: Context, linearLayoutCompat: LinearLayoutCompat) {
|
||||
when (lockMethodValue) {
|
||||
0 -> populateFavApps(context, linearLayoutCompat)
|
||||
1 -> lockAccessibility()
|
||||
2 -> lockDeviceAdmin(context)
|
||||
3 -> lockRoot()
|
||||
}
|
||||
}
|
||||
|
||||
/* check if the device is rooted */
|
||||
val isRooted: Boolean get() {
|
||||
var process: Process? = null
|
||||
return try {
|
||||
process = Runtime.getRuntime().exec("su")
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
false
|
||||
} finally {
|
||||
if (process != null) {
|
||||
try {
|
||||
process.destroy()
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check if the device is connected to the internet */
|
||||
val isNetworkAvailable: Boolean get() {
|
||||
val connectivityManager =
|
||||
lActivity!!.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
@Suppress("DEPRECATION") val activeNetworkInfo = connectivityManager.activeNetworkInfo
|
||||
@Suppress("DEPRECATION") return activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting
|
||||
}
|
||||
|
||||
/* check if authenticator available */
|
||||
fun canAuthenticate(context: Context): Boolean {
|
||||
val biometricManager = BiometricManager.from(context)
|
||||
return biometricManager.canAuthenticate(AUTHENTICATOR_TYPE) == BIOMETRIC_SUCCESS
|
||||
}
|
||||
|
||||
/* show device authenticator */
|
||||
fun biometricPromptInfo(title: String): BiometricPrompt.PromptInfo {
|
||||
return BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(title)
|
||||
.setSubtitle(lActivity!!.getString(R.string.authentication_subtitle))
|
||||
.setConfirmationRequired(true)
|
||||
.setAllowedAuthenticators(AUTHENTICATOR_TYPE)
|
||||
.build()
|
||||
}
|
||||
|
||||
/* get color red id from attribute */
|
||||
fun getColorResId(context: Context, colorAttr: Int) : Int {
|
||||
val typedValue = TypedValue()
|
||||
context.theme.resolveAttribute(colorAttr, typedValue, true)
|
||||
return typedValue.resourceId
|
||||
}
|
||||
|
||||
/* convert dp value to px */
|
||||
fun dpToPx(context: Context, id: Int) : Int = (context.resources.getDimension(id) * context.resources.displayMetrics.density).toInt()
|
||||
|
||||
/* lock screen using device admin */
|
||||
private fun lockDeviceAdmin(context: Context) {
|
||||
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
if (powerManager.isInteractive) {
|
||||
val policy =
|
||||
context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
|
||||
try {
|
||||
policy.lockNow()
|
||||
} catch (exception: SecurityException) {
|
||||
/* open device admin manager screen */
|
||||
lActivity!!.startActivity(
|
||||
Intent().setComponent(
|
||||
ComponentName(
|
||||
"com.android.settings",
|
||||
"com.android.settings.DeviceAdminSettings"
|
||||
)
|
||||
)
|
||||
)
|
||||
exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* favorite apps */
|
||||
private fun populateFavApps(context: Context, linearLayoutCompat: LinearLayoutCompat) {
|
||||
// val prefsFavApps = context.getSharedPreferences(PREFS_FAVORITE_APPS, 0)
|
||||
// if (linearLayoutCompat.isVisible || prefsFavApps.all.toString().length < 3) {
|
||||
// linearLayoutCompat.visibility = View.GONE
|
||||
// } else {
|
||||
// linearLayoutCompat.removeAllViews()
|
||||
// linearLayoutCompat.visibility = View.VISIBLE
|
||||
// val iconSize = context.getSharedPreferences(PREFS_SETTINGS, 0).getInt(KEY_ICON_SIZE, DEFAULT_ICON_SIZE)
|
||||
//
|
||||
// for (position in 1..MAX_FAVORITE_APPS) {
|
||||
// val packageName = prefsFavApps.getString(KEY_APP_NO_ + position.toString(), "").toString()
|
||||
// /* package name is not empty for a specific position */
|
||||
// if (packageName.isNotEmpty()) {
|
||||
// try {
|
||||
// ShapeableImageView(context).apply {
|
||||
// layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
// (iconSize * resources.displayMetrics.density).toInt(),
|
||||
// (iconSize * resources.displayMetrics.density).toInt(), 1F)
|
||||
// }.let { sImageView ->
|
||||
// context.packageManager.getApplicationIcon(packageName).let { defaultIcon ->
|
||||
// if (context.getSharedPreferences(PREFS_SETTINGS, 0).getInt(KEY_APPS_LAYOUT, 0) != 0)
|
||||
// getDrawableIconForPackage(packageName, defaultIcon) {
|
||||
// sImageView.setImageDrawable(it ?: defaultIcon)
|
||||
// }
|
||||
// else sImageView.setImageDrawable(defaultIcon)
|
||||
// }
|
||||
// sImageView.setOnClickListener {
|
||||
// context.startActivity(context.packageManager.getLaunchIntentForPackage(packageName))
|
||||
// }
|
||||
// linearLayoutCompat.addView(sImageView)
|
||||
// }
|
||||
// } catch (nameNotFoundException: PackageManager.NameNotFoundException) {
|
||||
// context.getSharedPreferences(PREFS_FAVORITE_APPS, 0)
|
||||
// .edit().remove(KEY_APP_NO_ + position).apply()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/* lock screen using accessibility service */
|
||||
private fun lockAccessibility() {
|
||||
if (LockService().isAccessibilityServiceEnabled(lActivity!!.applicationContext)) {
|
||||
try {
|
||||
lActivity!!.startService(
|
||||
Intent(lActivity!!.applicationContext, LockService::class.java)
|
||||
.setAction(ACCESSIBILITY_SERVICE_LOCK_SCREEN)
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
} else {
|
||||
/* open accessibility service screen */
|
||||
lActivity!!.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
/* lock screen using root */
|
||||
private fun lockRoot() {
|
||||
try {
|
||||
val process = Runtime.getRuntime().exec("su")
|
||||
val dataOutputStream = DataOutputStream(process.outputStream)
|
||||
dataOutputStream.writeBytes("input keyevent \${KeyEvent.KEYCODE_POWER}\n")
|
||||
dataOutputStream.writeBytes("exit\n")
|
||||
dataOutputStream.flush()
|
||||
dataOutputStream.close()
|
||||
process.waitFor()
|
||||
process.destroy()
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.helpers
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
|
||||
|
||||
internal class ViewPagerAdapter(
|
||||
fragmentManager: FragmentManager, private val fragments: MutableList<Fragment>, lifecycle: Lifecycle) :
|
||||
FragmentStateAdapter(fragmentManager, lifecycle) {
|
||||
|
||||
override fun getItemCount(): Int = fragments.size
|
||||
override fun createFragment(position: Int): Fragment = fragments[position]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.BatteryManager
|
||||
import android.provider.Settings
|
||||
import android.widget.TextView
|
||||
|
||||
|
||||
internal class BatteryReceiver(private val progressBar: TextView) : BroadcastReceiver() {
|
||||
|
||||
/* get current battery percentage */
|
||||
private fun batteryPercentage(intent: Intent): Int {
|
||||
val percentage = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)) /
|
||||
(intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)).toFloat()
|
||||
return (percentage * 100).toInt()
|
||||
}
|
||||
|
||||
/* get current charging status */
|
||||
private fun chargingStatus(intent: Intent): Int = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
val animationDuration = try {
|
||||
Settings.Global.getFloat(context?.contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1.0f)
|
||||
} catch (e: Settings.SettingNotFoundException) {
|
||||
// e.printStackTrace()
|
||||
}
|
||||
|
||||
/* set battery percentage value to the circular progress bar */
|
||||
progressBar.text = "빠떼뤼 ~> ${batteryPercentage(intent!!)}%"
|
||||
|
||||
/* progress bar animation */
|
||||
// if (chargingStatus(intent) == BatteryManager.BATTERY_STATUS_CHARGING ||
|
||||
// chargingStatus(intent) == BatteryManager.BATTERY_STATUS_FULL) {
|
||||
// if (progressBar.animation == null && animationDuration != 0f) {
|
||||
// progressBar.startAnimation(
|
||||
// AnimationUtils.loadAnimation(context, R.anim.rotate_clockwise)
|
||||
// )
|
||||
// }
|
||||
// } else if (chargingStatus(intent) == BatteryManager.BATTERY_STATUS_DISCHARGING ||
|
||||
// chargingStatus(intent) == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
|
||||
// progressBar.clearAnimation()
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
package bums.lunatic.launcher.home.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
|
||||
class LinearLayoutManagerWrapper : LinearLayoutManager {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
|
||||
|
||||
override fun supportsPredictiveItemAnimations(): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.databinding.ListItemWithBinding
|
||||
import bums.lunatic.launcher.model.NotificationItem
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class NotificationItemAdapter (
|
||||
private val context: Context) : RecyclerView.Adapter<NotiHolder>() {
|
||||
private var notiItems: ArrayList<NotificationItem> = arrayListOf()
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): NotiHolder {
|
||||
val binding = ListItemWithBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return NotiHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return notiItems.size
|
||||
}
|
||||
|
||||
val dateFormat = SimpleDateFormat("hh:mm / yy - MM - dd")
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: NotiHolder, position: Int) {
|
||||
val appInfo = notiItems[position]
|
||||
try {
|
||||
holder.view.circlePreview.visibility = View.VISIBLE
|
||||
var param = holder.view.circlePreview.layoutParams
|
||||
holder.view.circlePreview.layoutParams = ConstraintLayout.LayoutParams(120,param.height)
|
||||
val d: Drawable = context.packageManager.getApplicationIcon(appInfo.pkgName!!)
|
||||
holder.view.circlePreview.setImageDrawable(d)
|
||||
holder.view.title.text = appInfo.tikerMsg ?: "${appInfo.selfDisplayName} ${appInfo.subtext}"
|
||||
holder.view.desc.text = "${appInfo.pkgName} ${appInfo.title}"
|
||||
holder.view.date.text = dateFormat.format(Date(appInfo.postTime))
|
||||
holder.view.circlePreview.setOnLongClickListener {
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
delete(query<NotificationItem>(NotificationItem::class).query("pkgName == $0",appInfo.pkgName).find())
|
||||
}
|
||||
lActivity?.packageManager?.apply {
|
||||
context.startActivity(getLaunchIntentForPackage(appInfo.pkgName!!))
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun updateData(newList: List<NotificationItem>) {
|
||||
try {
|
||||
DiffUtil.calculateDiff(NotiItemDiffUtil(notiItems, newList)).apply {
|
||||
|
||||
}.dispatchUpdatesTo(this).apply {
|
||||
// notifyItemRangeChanged(0,10)
|
||||
}
|
||||
notiItems.clear()
|
||||
notiItems.addAll(newList)
|
||||
}catch ( e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
internal class NotiHolder(var view: ListItemWithBinding) : RecyclerView.ViewHolder(view.root)
|
||||
internal class NotiItemDiffUtil(
|
||||
var oldList: List<NotificationItem>, var newList: List<NotificationItem>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].uniq_id == if (newList.size > newItemPosition) newList[newItemPosition].uniq_id else ""
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].uniq_id == if (newList.size > newItemPosition) newList[newItemPosition].uniq_id else ""
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* 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.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.CallLog
|
||||
import android.provider.ContactsContract
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.CalllogItemBinding
|
||||
import bums.lunatic.launcher.utils.getContactId
|
||||
import bums.lunatic.launcher.workers.RecentCall
|
||||
|
||||
|
||||
internal class RecentCallsAdapter(
|
||||
private val callList: ArrayList<RecentCall>,
|
||||
private val context: Context) : RecyclerView.Adapter<RecentCallsAdapter.RecentCallsHolder>() {
|
||||
|
||||
private val currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer)
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RecentCallsHolder {
|
||||
val binding = CalllogItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return RecentCallsHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
// BLog.LOGE("callList.size >>> ${callList.size}")
|
||||
return callList.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RecentCallsHolder, position: Int) {
|
||||
val todo = callList[position]
|
||||
holder.view.name.text = if(todo.name.equals("unknown")) todo.number else { todo.name}
|
||||
|
||||
when (todo.type) {
|
||||
// CallLog.Calls.INCOMING_TYPE -> { dir = "INCOMING_TYPE" }
|
||||
// CallLog.Calls.OUTGOING_TYPE -> { dir = "OUTGOING_TYPE" }
|
||||
CallLog.Calls.MISSED_TYPE -> { holder.view.root.isSelected = true }
|
||||
// CallLog.Calls.VOICEMAIL_TYPE -> { dir = "VOICEMAIL_TYPE" }
|
||||
// CallLog.Calls.REJECTED_TYPE -> { dir = "REJECTED_TYPE" }
|
||||
// CallLog.Calls.BLOCKED_TYPE -> { dir = "BLOCKED_TYPE" }
|
||||
// CallLog.Calls.ANSWERED_EXTERNALLY_TYPE -> { dir = "ANSWERED_EXTERNALLY_TYPE" }
|
||||
else -> { holder.view.root.isSelected = false }
|
||||
}
|
||||
// "\u25CF ${} , ${todo.typeString} : ${todo.count} : ${todo.date}"
|
||||
holder.view.type.text = todo.typeString
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
holder.view.date.text = todo.date
|
||||
/* launch edit or update dialog on item click */
|
||||
holder.view.root.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.root.setOnLongClickListener {
|
||||
|
||||
// copyToClipboard(context, todo.name)
|
||||
var cId = getContactId(lActivity!!.contentResolver, todo.number)
|
||||
if (cId != null && cId.length > 0) {
|
||||
var intent = Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(ContactsContract.Contacts.CONTENT_URI.toString() + "/" + cId));
|
||||
lActivity?.startActivity(intent);
|
||||
} else {
|
||||
lActivity?.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:${todo.number}")))
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class RecentCallsHolder(var view: CalllogItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
/* update dialog */
|
||||
private fun updateDialog(position: Int) {
|
||||
// val bottomSheetDialog = BottomSheetDialog(lActivity!!, R.style.BottomSheetDialog)
|
||||
// val dialogBinding = TodoDialogBinding.inflate(LayoutInflater.from(context))
|
||||
// bottomSheetDialog.setContentView(dialogBinding.root)
|
||||
// bottomSheetDialog.show()
|
||||
// bottomSheetDialog.dismissWithAnimation = true
|
||||
//
|
||||
// val databaseHandler = DatabaseHandler(context)
|
||||
// val todo = databaseHandler.todos[position]
|
||||
//
|
||||
// dialogBinding.apply {
|
||||
// deleteAllConfirmation.visibility = View.GONE
|
||||
// todoInput.setText(todo.name)
|
||||
// todoCancel.text = context.getString(R.string.delete)
|
||||
// todoCancel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
|
||||
// todoOk.text = context.getString(R.string.update)
|
||||
// }
|
||||
//
|
||||
// /* delete the item */
|
||||
// dialogBinding.todoCancel.setOnClickListener {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /* update the item */
|
||||
// dialogBinding.todoOk.setOnClickListener {
|
||||
// val updatedTodoString = Objects.requireNonNull(dialogBinding.todoInput.text).toString().trim { it <= ' ' }
|
||||
// if (updatedTodoString.isNotEmpty()) {
|
||||
// todo.name = updatedTodoString
|
||||
// databaseHandler.updateTodo(todo)
|
||||
// bottomSheetDialog.dismiss()
|
||||
// } else {
|
||||
// dialogBinding.todoInput.error = context.getString(R.string.empty_text_field)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
var layoutManager : GridLayoutManager? = null
|
||||
var recyclerView: RecyclerView? = null
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
layoutManager = recyclerView.layoutManager as? GridLayoutManager
|
||||
this.recyclerView = recyclerView
|
||||
}
|
||||
|
||||
fun updateData(newList: Collection<RecentCall>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(RecentCallDiffUtil(callList, newList.toList()))
|
||||
diffUtilResult.dispatchUpdatesTo(this).apply {
|
||||
val visibleItemCount = (layoutManager?.findLastVisibleItemPosition() ?: 0) - (layoutManager?.findFirstVisibleItemPosition() ?: 0)
|
||||
if (visibleItemCount > 0) {
|
||||
this@RecentCallsAdapter.notifyItemRangeChanged(0, visibleItemCount)
|
||||
recyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
callList.clear()
|
||||
callList.addAll(newList)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class RecentCallDiffUtil(
|
||||
private val oldList: List<RecentCall>, private val newList: List<RecentCall>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].date == newList[newItemPosition].date
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
package bums.lunatic.launcher.home.adapters
|
||||
|
||||
import android.util.Xml
|
||||
import bums.lunatic.launcher.model.NewsData
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.others.Reddit
|
||||
import bums.lunatic.launcher.utils.beforeDay
|
||||
import com.google.gson.Gson
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import java.net.URL
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
object RssFeedsParser {
|
||||
var parseDateFormat: SimpleDateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH)
|
||||
var limitDateTime = beforeDay(Date(),3)
|
||||
fun getFeeds(url : String) : List<RssDataInterface> {
|
||||
var returnList = mutableListOf<RssDataInterface>()
|
||||
try {
|
||||
returnList.addAll(parse(getInputStream(url)!!))
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return returnList
|
||||
}
|
||||
|
||||
fun getReddit(url : String, nsfw : Boolean): List<RssDataInterface> {
|
||||
var returnList = mutableListOf<RssDataInterface>()
|
||||
var dateTime = beforeDay(Date(),3)
|
||||
try {
|
||||
var mReddit = Gson().fromJson(InputStreamReader(getInputStream(url)!!), Reddit::class.java)
|
||||
mReddit.data?.children?.forEach {
|
||||
it.data?.nsfw = nsfw
|
||||
if(((it.data?.created_utc ?: 0).toLong() * 1000L > dateTime)) {
|
||||
(it.data as? RssDataInterface)?.let { rss ->
|
||||
if (rss.title().contains("request") == false) {
|
||||
returnList.add(rss)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return returnList
|
||||
}
|
||||
|
||||
private fun getInputStream(link: String?): InputStream? {
|
||||
return try {
|
||||
val url = URL(link)
|
||||
url.openConnection().getInputStream()
|
||||
|
||||
} catch (ioException: IOException) {
|
||||
ioException.printStackTrace()
|
||||
null
|
||||
}.apply {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun parse(inputStream: InputStream): List<RssDataInterface> {
|
||||
return inputStream.use { stream ->
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
|
||||
parser.setInput(stream, null)
|
||||
parser.nextTag()
|
||||
readFeed(parser)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readFeed(parser: XmlPullParser): List<NewsData> {
|
||||
|
||||
parser.require(XmlPullParser.START_TAG, null, "rss")
|
||||
var title: String? = null
|
||||
var link: String? = null
|
||||
var date = 0L
|
||||
var desc : String? = null
|
||||
var source : String? = null
|
||||
val items: MutableList<NewsData> = ArrayList()
|
||||
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (parser.eventType != XmlPullParser.START_TAG) {
|
||||
continue
|
||||
}
|
||||
|
||||
val name = parser.name
|
||||
if (name == "title") {
|
||||
title = readTitle(parser)
|
||||
} else if (name == "link") {
|
||||
link = readLink(parser)
|
||||
} else if (name == "pubDate") {
|
||||
try {
|
||||
date = parseDateFormat.parse(readDate(parser))?.time ?: 0L
|
||||
}catch (e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
} else if (name == "description") {
|
||||
desc = readDesc(parser)
|
||||
} else if (name == "source") {
|
||||
source = readThumbnail(parser)
|
||||
}
|
||||
|
||||
if (date > limitDateTime && title != null && link != null) {
|
||||
val item = NewsData(title, link)
|
||||
item.pubDate = date
|
||||
item.source = source
|
||||
item.description = desc
|
||||
items.add(item)
|
||||
title = null
|
||||
link = null
|
||||
source = null
|
||||
desc = null
|
||||
date = 0
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readLink(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "link")
|
||||
val link = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "link")
|
||||
return link
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readTitle(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "title")
|
||||
val title = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "title")
|
||||
return title
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readDate(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "pubDate")
|
||||
val date = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "pubDate")
|
||||
return date
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readDesc(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "description")
|
||||
val link = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "description")
|
||||
return link
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
private fun readThumbnail(parser: XmlPullParser): String {
|
||||
parser.require(XmlPullParser.START_TAG, null, "source")
|
||||
val link = readText(parser)
|
||||
parser.require(XmlPullParser.END_TAG, null, "source")
|
||||
return link
|
||||
}
|
||||
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readText(parser: XmlPullParser): String {
|
||||
var result = ""
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
result = parser.text
|
||||
parser.nextTag()
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* 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.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.net.toUri
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.ListItemWithBinding
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.openDotax
|
||||
import bums.lunatic.launcher.openNews
|
||||
import bums.lunatic.launcher.openOpera
|
||||
import bums.lunatic.launcher.openReddit
|
||||
import bums.lunatic.launcher.openYouTube
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import com.squareup.picasso.Picasso
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
internal class RssItemAdapter (
|
||||
|
||||
private val context: Context) : RecyclerView.Adapter<RssTag>() {
|
||||
companion object {
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
val dateFormat = SimpleDateFormat("a HH:mm / yy - MM - dd")
|
||||
val emptyDate = " - "
|
||||
|
||||
val dateViewClick = View.OnClickListener { v ->
|
||||
(v?.tag as? RssData)?.let { rss ->
|
||||
when(rss.category()) {
|
||||
RssDataType.GURU,RssDataType.MOST,RssDataType.REDDIT_NSFW -> {
|
||||
v.findViewById<ShapeableImageView>(R.id.circle_preview)?.let {
|
||||
if (it.visibility == View.GONE) {
|
||||
it.visibility = View.VISIBLE
|
||||
it.postDelayed({
|
||||
it.visibility = View.GONE
|
||||
}, 2000L)
|
||||
} else {
|
||||
openOpera(rss.originPage())
|
||||
}
|
||||
}
|
||||
}
|
||||
RssDataType.REDDIT -> { openReddit(rss.originPage()) }
|
||||
RssDataType.DOTAX -> { openDotax(rss.originPage()) }
|
||||
RssDataType.YOUTUBE -> { openYouTube(rss.originPage()) }
|
||||
else -> { openNews(rss.originPage()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
private var rssDataItemLis: ArrayList<RssDataInterface> = arrayListOf()
|
||||
// val mLongClickListener = View.OnLongClickListener { v ->
|
||||
// (v?.tag as? Int)?.let { idx ->
|
||||
// val rss = rssDataItemLis[idx]
|
||||
// }
|
||||
// true
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RssTag {
|
||||
val binding = ListItemWithBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return RssTag(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return rssDataItemLis.size
|
||||
}
|
||||
|
||||
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RssTag, position: Int) {
|
||||
val rssData = rssDataItemLis[position]
|
||||
if (rssData.pubDate() > 1000L) {
|
||||
holder.view.date.text = dateFormat.format(Date(rssData.pubDate()))
|
||||
} else {
|
||||
holder.view.date.text = emptyDate
|
||||
}
|
||||
|
||||
holder.view.title.text = rssData.title()
|
||||
holder.view.desc.text = rssData.description()
|
||||
|
||||
var param = holder.view.circlePreview.layoutParams
|
||||
holder.view.circlePreview.layoutParams = ConstraintLayout.LayoutParams(rssData.category().defaultImgSize(), param.height)
|
||||
holder.view.circlePreview.visibility = rssData.category().getDefaultVisibiliy()
|
||||
Picasso.get().cancelRequest(holder.view.circlePreview)
|
||||
|
||||
if(rssData.thumbnailUrl()?.length ?: 0 > 6) {
|
||||
Picasso.get().load(rssData.thumbnailUrl().replace("&","&").toUri()).into(holder.view.circlePreview)
|
||||
} else if (rssData.category().getResId() > 0 ) {
|
||||
holder.view.circlePreview.setImageResource(rssData.category().getResId())
|
||||
} else {
|
||||
holder.view.circlePreview.setImageDrawable(null)
|
||||
}
|
||||
|
||||
holder.view.root.tag = rssData
|
||||
holder.view.root.setOnClickListener(dateViewClick)
|
||||
// holder.view.root.setOnLongClickListener(mLongClickListener)
|
||||
}
|
||||
|
||||
var layoutManager : LinearLayoutManager? = null
|
||||
var recyclerView: RecyclerView? = null
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
layoutManager = recyclerView.layoutManager as? LinearLayoutManager
|
||||
this.recyclerView = recyclerView
|
||||
}
|
||||
|
||||
fun updateData(newList: List<RssDataInterface>) {
|
||||
try {
|
||||
// BLog.LOGE("newList >> ${newList}")
|
||||
DiffUtil.calculateDiff(RssItemDiffUtil(rssDataItemLis, newList)).apply {
|
||||
|
||||
}.dispatchUpdatesTo(this).apply {
|
||||
val visibleItemCount = (layoutManager?.findLastVisibleItemPosition() ?: 0) - (layoutManager?.findFirstVisibleItemPosition() ?: 0)
|
||||
val first = layoutManager?.findLastVisibleItemPosition() ?: 0
|
||||
if (visibleItemCount > 0) {
|
||||
this@RssItemAdapter.notifyItemRangeChanged(first, visibleItemCount)
|
||||
// recyclerView?.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
rssDataItemLis.clear()
|
||||
rssDataItemLis.addAll(newList)
|
||||
// BLog.LOGE("rssDataItemLis >> ${rssDataItemLis}")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class RssTag(var view: ListItemWithBinding) : RecyclerView.ViewHolder(view.root) {}
|
||||
internal class RssItemDiffUtil(
|
||||
var oldList: List<RssDataInterface>, var newList: List<RssDataInterface>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].originPage() == if (newList.size > newItemPosition) newList[newItemPosition].originPage() else ""
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].originPage() == if (newList.size > newItemPosition) newList[newItemPosition].originPage() else ""
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.ListItemBinding
|
||||
import bums.lunatic.launcher.model.jGuruTag
|
||||
import com.google.gson.Gson
|
||||
|
||||
internal class RssTagAdapter(
|
||||
private val smsList: ArrayList<jGuruTag>,
|
||||
private val context: Context) : RecyclerView.Adapter<RssTagAdapter.RssTag>() {
|
||||
|
||||
private val currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer)
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): RssTag {
|
||||
val binding = ListItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return RssTag(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return smsList.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: RssTag, position: Int) {
|
||||
val todo = smsList[position]
|
||||
|
||||
holder.view.itemText.text = "\u25CF ${Gson().toJson(todo)}"
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
// holder.view.itemText.isSingleLine = false
|
||||
/* launch edit or update dialog on item click */
|
||||
// holder.view.itemText.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.itemText.setOnLongClickListener {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class RssTag(var view: ListItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
|
||||
fun updateData(newList: List<jGuruTag>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(RssTagDiffUtil(smsList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
// smsList.clear()
|
||||
// smsList.addAll(newList)
|
||||
}
|
||||
|
||||
/* update dialog */
|
||||
private fun updateDialog(position: Int) {
|
||||
// val bottomSheetDialog = BottomSheetDialog(lActivity!!, R.style.BottomSheetDialog)
|
||||
// val dialogBinding = TodoDialogBinding.inflate(LayoutInflater.from(context))
|
||||
// bottomSheetDialog.setContentView(dialogBinding.root)
|
||||
// bottomSheetDialog.show()
|
||||
// bottomSheetDialog.dismissWithAnimation = true
|
||||
//
|
||||
// val databaseHandler = DatabaseHandler(context)
|
||||
// val todo = databaseHandler.todos[position]
|
||||
//
|
||||
// dialogBinding.apply {
|
||||
// deleteAllConfirmation.visibility = View.GONE
|
||||
// todoInput.setText(todo.name)
|
||||
// todoCancel.text = context.getString(R.string.delete)
|
||||
// todoCancel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
|
||||
// todoOk.text = context.getString(R.string.update)
|
||||
// }
|
||||
//
|
||||
// /* delete the item */
|
||||
// dialogBinding.todoCancel.setOnClickListener {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /* update the item */
|
||||
// dialogBinding.todoOk.setOnClickListener {
|
||||
// val updatedTodoString = Objects.requireNonNull(dialogBinding.todoInput.text).toString().trim { it <= ' ' }
|
||||
// if (updatedTodoString.isNotEmpty()) {
|
||||
// todo.name = updatedTodoString
|
||||
// databaseHandler.updateTodo(todo)
|
||||
// bottomSheetDialog.dismiss()
|
||||
// } else {
|
||||
// dialogBinding.todoInput.error = context.getString(R.string.empty_text_field)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal class RssTagDiffUtil(
|
||||
private val oldList: List<jGuruTag>, private val newList: List<jGuruTag>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].link == newList[newItemPosition].link
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].link == newList[newItemPosition].link
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.SmsItemBinding
|
||||
import bums.lunatic.launcher.utils.getContactName
|
||||
import bums.lunatic.launcher.workers.RecentSms
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
||||
internal class SmsLogsAdapter(
|
||||
private val smsList: ArrayList<RecentSms>,
|
||||
private val context: Context) : RecyclerView.Adapter<SmsLogHolder>() {
|
||||
|
||||
private val currentFragment = lActivity!!.supportFragmentManager.findFragmentById(R.id.mainFragmentsContainer)
|
||||
|
||||
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): SmsLogHolder {
|
||||
val binding = SmsItemBinding.inflate(LayoutInflater.from(viewGroup.context), viewGroup, false)
|
||||
return SmsLogHolder(binding)
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return smsList.size
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: SmsLogHolder, position: Int) {
|
||||
val todo = smsList[position]
|
||||
var name = getContactName(lActivity!!.contentResolver,todo.person)
|
||||
if (name == null) {
|
||||
getContactName(lActivity!!.contentResolver,todo.addr)
|
||||
}
|
||||
if(todo.isMms) {
|
||||
var body = todo.texts?.joinToString("\n")?.replace("\n"," ")
|
||||
body = if (body?.length ?: 0 > 60) body?.substring(0,60).plus("...") else body
|
||||
holder.view.itemText.text = "\u25CF ${if(name != null && name.length > 0) name else if(todo.person != null && todo.person.length > 0){todo.person} else todo.addr} : ${
|
||||
SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(
|
||||
Date(
|
||||
Math.max(
|
||||
todo.pstDate.toLong(),
|
||||
todo.rcvDate.toLong()
|
||||
)
|
||||
)
|
||||
)
|
||||
} : ${todo.type}"
|
||||
holder.view.contents.text = "${body}"
|
||||
} else {
|
||||
holder.view.itemText.text = "\u25CF ${if(name != null && name.length > 0) name else if(todo.person != null && todo.person.length > 0){todo.person} else todo.addr} : ${
|
||||
SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(
|
||||
Date(
|
||||
Math.max(
|
||||
todo.pstDate.toLong(),
|
||||
todo.rcvDate.toLong()
|
||||
)
|
||||
)
|
||||
)
|
||||
} : ${todo.type}"
|
||||
|
||||
|
||||
|
||||
|
||||
holder.view.contents.text = "${todo.body}"
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* multiline texts are enabled for TodoManager */
|
||||
// holder.view.itemText.isSingleLine = false
|
||||
/* launch edit or update dialog on item click */
|
||||
// holder.view.itemText.setOnClickListener { updateDialog(position) }
|
||||
/* copy texts on long click */
|
||||
holder.view.itemText.setOnLongClickListener {
|
||||
var intent = Intent(Intent.ACTION_SENDTO);
|
||||
intent.setData(Uri.parse("smsto:" + Uri.encode(todo.addr)));
|
||||
lActivity?.startActivity(intent);
|
||||
true
|
||||
}
|
||||
|
||||
holder.view.root.isActivated = if(holder.view.itemText.text.contains("#CMAS#")) true else false
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun updateData(newList: List<RecentSms>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(SmsDiffUtil(smsList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
smsList.clear()
|
||||
smsList.addAll(newList)
|
||||
}
|
||||
|
||||
/* update dialog */
|
||||
private fun updateDialog(position: Int) {
|
||||
// val bottomSheetDialog = BottomSheetDialog(lActivity!!, R.style.BottomSheetDialog)
|
||||
// val dialogBinding = TodoDialogBinding.inflate(LayoutInflater.from(context))
|
||||
// bottomSheetDialog.setContentView(dialogBinding.root)
|
||||
// bottomSheetDialog.show()
|
||||
// bottomSheetDialog.dismissWithAnimation = true
|
||||
//
|
||||
// val databaseHandler = DatabaseHandler(context)
|
||||
// val todo = databaseHandler.todos[position]
|
||||
//
|
||||
// dialogBinding.apply {
|
||||
// deleteAllConfirmation.visibility = View.GONE
|
||||
// todoInput.setText(todo.name)
|
||||
// todoCancel.text = context.getString(R.string.delete)
|
||||
// todoCancel.setTextColor(ContextCompat.getColor(context, android.R.color.holo_red_light))
|
||||
// todoOk.text = context.getString(R.string.update)
|
||||
// }
|
||||
//
|
||||
// /* delete the item */
|
||||
// dialogBinding.todoCancel.setOnClickListener {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// /* update the item */
|
||||
// dialogBinding.todoOk.setOnClickListener {
|
||||
// val updatedTodoString = Objects.requireNonNull(dialogBinding.todoInput.text).toString().trim { it <= ' ' }
|
||||
// if (updatedTodoString.isNotEmpty()) {
|
||||
// todo.name = updatedTodoString
|
||||
// databaseHandler.updateTodo(todo)
|
||||
// bottomSheetDialog.dismiss()
|
||||
// } else {
|
||||
// dialogBinding.todoInput.error = context.getString(R.string.empty_text_field)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
class SmsLogHolder(var view: SmsItemBinding) : RecyclerView.ViewHolder(view.root)
|
||||
internal class SmsDiffUtil(
|
||||
private val oldList: List<RecentSms>, private val newList: List<RecentSms>
|
||||
) : DiffUtil.Callback() {
|
||||
|
||||
override fun getOldListSize(): Int = oldList.size
|
||||
override fun getNewListSize(): Int = newList.size
|
||||
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].rcvDate == newList[newItemPosition].rcvDate
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
|
||||
oldList[oldItemPosition].rcvDate == newList[newItemPosition].rcvDate
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package bums.lunatic.launcher.home.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.databinding.WeatherBookBinding
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
|
||||
class WeatherAdapter(
|
||||
private val pages: List<Int>,
|
||||
private val adatpers: List<RecyclerView.Adapter<out RecyclerView.ViewHolder>?>,
|
||||
private val weatherBook: WeatherBookBinding? = null)
|
||||
: RecyclerView.Adapter<WeatherAdapter.PageViewHolder>() {
|
||||
|
||||
class PageViewHolder(val view: View): RecyclerView.ViewHolder(view)
|
||||
|
||||
|
||||
var childs : ArrayList<RecyclerView> = arrayListOf<RecyclerView>()
|
||||
|
||||
|
||||
@SuppressLint("ResourceType")
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageViewHolder {
|
||||
|
||||
|
||||
val layoutInflater = LayoutInflater.from(parent.context).inflate(pages[viewType], parent, false)
|
||||
// var first = layoutInflater.findViewById<RecyclerView>(R.id.recycler_hourly_weather)
|
||||
// var second = layoutInflater.findViewById<RecyclerView>(R.id.weather_dress_recycller)
|
||||
|
||||
return PageViewHolder(layoutInflater)
|
||||
}
|
||||
|
||||
|
||||
fun syncScroll(newPosition: Int) {
|
||||
childs[newPosition].let {recyclerView ->
|
||||
BLog.LOGE("recyclerView >>> ${recyclerView} 1 ")
|
||||
if (!isSyncingScroll) {
|
||||
isSyncingScroll = true
|
||||
BLog.LOGE("recyclerView >>> ${recyclerView} 2 ")
|
||||
childs.forEach { c ->
|
||||
// val visibleItemCount = (layoutManager?.findLastVisibleItemPosition() ?: 0) - (layoutManager?.findFirstVisibleItemPosition() ?: 0)
|
||||
if (c != recyclerView) {
|
||||
(c.layoutManager as LinearLayoutManager)?.let {
|
||||
it.findFirstVisibleItemPosition()?.let {
|
||||
(recyclerView?.layoutManager as? LinearLayoutManager)?.let { target ->
|
||||
target.scrollToPositionWithOffset(it, 0)
|
||||
}
|
||||
// .scrollToPosition(it)
|
||||
}
|
||||
}
|
||||
BLog.LOGE("recyclerView >>> ${recyclerView} 3 ")
|
||||
}
|
||||
}
|
||||
isSyncingScroll = false
|
||||
BLog.LOGE("recyclerView >>> ${recyclerView} 4 ")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
val mOnScrollListener = object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
// recyclerView.postDelayed({syncScroll(recyclerView)},10L)
|
||||
// recyclerView.post{syncScroll(recyclerView)}
|
||||
}
|
||||
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
super.onScrolled(recyclerView, dx, dy)
|
||||
}
|
||||
}
|
||||
var isSyncingScroll = false
|
||||
override fun onBindViewHolder(holder: PageViewHolder, position: Int) {
|
||||
if (adatpers[position] != null) {
|
||||
(holder.view as RecyclerView).apply {
|
||||
this.adapter = adatpers[position]
|
||||
this.layoutManager = LinearLayoutManager(this.context, LinearLayoutManager.HORIZONTAL, false)
|
||||
if (!childs.contains(this)) {
|
||||
childs.add(this)
|
||||
this.addOnScrollListener(mOnScrollListener)
|
||||
LinearSnapHelper().attachToRecyclerView(this)
|
||||
}
|
||||
}
|
||||
// when (position) {
|
||||
// 0 -> holder.view.findViewById<RecyclerView>(R.id.recycler_hourly_weather).apply {
|
||||
// this.adapter = adatpers[position]
|
||||
// this.layoutManager =
|
||||
//
|
||||
// }
|
||||
//
|
||||
// else -> holder.view.findViewById<RecyclerView>(R.id.weather_dress_recycller).apply {
|
||||
// this.adapter = adatpers[position]
|
||||
// this.layoutManager =
|
||||
// LinearLayoutManager(this.context, LinearLayoutManager.HORIZONTAL, false)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = pages.size
|
||||
|
||||
override fun getItemViewType(position: Int): Int = position
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package bums.lunatic.launcher.home.adapters
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.databinding.ItemRecHourlyDressBinding
|
||||
import bums.lunatic.launcher.model.Hour
|
||||
import bums.lunatic.launcher.model.WeatherInfoManager
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import java.util.Calendar
|
||||
|
||||
class WeatherDressAdatper (private val dataSet: ArrayList<Hour>) : RecyclerView.Adapter<WeatherDressAdatper.ViewHolder>(){
|
||||
var isChangedAmOrPm: Boolean = true
|
||||
|
||||
class ViewHolder(val viewItem: ItemRecHourlyDressBinding): RecyclerView.ViewHolder(viewItem.root)
|
||||
|
||||
// @SuppressLint("NotifyDataSetChanged")
|
||||
fun update(li: Collection<Hour>) {
|
||||
li.toList()
|
||||
this.dataSet.clear()
|
||||
this.dataSet.addAll(li)
|
||||
// notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WeatherDressAdatper.ViewHolder {
|
||||
// val view = LayoutInflater.from(parent.context)
|
||||
// .inflate(R.layout.item_rec_hourly_dress, parent, false)
|
||||
// val itemBinding: ItemRecHourlyDressBinding = DataBindingUtil.
|
||||
val binding = ItemRecHourlyDressBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return WeatherDressAdatper.ViewHolder(binding)
|
||||
}
|
||||
|
||||
fun getToday() = Calendar.getInstance().get(Calendar.DAY_OF_YEAR)
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onBindViewHolder(holder: WeatherDressAdatper.ViewHolder, position: Int) {
|
||||
val data = dataSet[position] as? Hour
|
||||
// BLog.LOGE("saved weatherForcast >>> asFlow ${dataSet.size}")
|
||||
// val today = Calendar.getInstance()
|
||||
// today.time = Date(data?.time_epoch?.toLong()?.times(1000L) ?: 0L)
|
||||
// val dayOfItem = today.get(Calendar.DAY_OF_YEAR)
|
||||
data?.let {
|
||||
// total.loc = WorkersDb.getRealm().query<Location>().also {
|
||||
// BLog.LOGE("re >>> ${it.description()}") // 쿼리 로그
|
||||
// }.find().first()
|
||||
WeatherInfoManager.getShowingInfo(it).apply {
|
||||
// total.setInfo(this)
|
||||
holder.viewItem.setInfo(this)
|
||||
}
|
||||
// BLog.LOGE("reeeeeeeeeee >>> ${holder.viewItem.hour.text}")
|
||||
holder.viewItem.amOrPm.visibility =
|
||||
if (arrayListOf(12, 0).contains(WeatherInfoManager.toZonedDateTime(it.time_epoch).hour) || position == 0) {
|
||||
View.VISIBLE
|
||||
} else View.INVISIBLE
|
||||
holder.viewItem.hour.apply {
|
||||
if (WeatherInfoManager.toZonedDateTime(it.time_epoch).hour == 0) {
|
||||
this@apply.setTextColor(Color.BLACK)
|
||||
this@apply.isSelected = true
|
||||
} else {
|
||||
this@apply.setTextColor(Color.WHITE)
|
||||
this@apply.isSelected = false
|
||||
}
|
||||
}
|
||||
holder.viewItem.imgDress.setImageLevel(it.temp_c.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = dataSet.size
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package bums.lunatic.launcher.home.adapters
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.databinding.ItemHourlyWeatherBinding
|
||||
import bums.lunatic.launcher.model.Hour
|
||||
import bums.lunatic.launcher.model.WeatherInfoManager
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import com.squareup.picasso.Picasso
|
||||
|
||||
class WeatherHourlyAdapter(private val dataSet: ArrayList<Hour>): RecyclerView.Adapter<WeatherHourlyAdapter.ViewHolder>() {
|
||||
class ViewHolder(val viewItem: ItemHourlyWeatherBinding): RecyclerView.ViewHolder(viewItem.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, type: Int): WeatherHourlyAdapter.ViewHolder {
|
||||
val binding = ItemHourlyWeatherBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return WeatherHourlyAdapter.ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val data = dataSet[position] as? Hour
|
||||
// BLog.LOGE("saved weatherForcast >>> asFlow ${dataSet.size}")
|
||||
data?.let {
|
||||
WeatherInfoManager.getShowingInfo(it).apply {
|
||||
holder.viewItem.setInfo(this)
|
||||
Picasso.get()
|
||||
.load(this.urlImgWeather)
|
||||
.into(holder.viewItem.imgWeather)
|
||||
}
|
||||
// BLog.LOGE("reeeeeeeeeee >>> ${holder.viewItem.hour.text}")
|
||||
holder.viewItem.amOrPm.visibility =
|
||||
if (arrayListOf(12, 0).contains(WeatherInfoManager.toZonedDateTime(it.time_epoch).hour) || position == 0) {
|
||||
View.VISIBLE
|
||||
} else View.INVISIBLE
|
||||
holder.viewItem.hour.apply {
|
||||
if (WeatherInfoManager.toZonedDateTime(it.time_epoch).hour == 0) {
|
||||
this@apply.setTextColor(Color.BLACK)
|
||||
this@apply.isSelected = true
|
||||
} else {
|
||||
this@apply.setTextColor(Color.WHITE)
|
||||
this@apply.isSelected = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = dataSet.size
|
||||
|
||||
fun update(li: Collection<Hour>) {
|
||||
BLog.LOGE("${this.javaClass.simpleName} update")
|
||||
li.toList()
|
||||
this.dataSet.clear()
|
||||
this.dataSet.addAll(li)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.weather
|
||||
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
|
||||
internal class JsonParser {
|
||||
|
||||
fun getMyWeather(jsonStr: String): Weather {
|
||||
val weather = Weather()
|
||||
|
||||
try {
|
||||
val jsonObject = JSONObject(jsonStr)
|
||||
|
||||
/* Get weather condition */
|
||||
val weatherArray = jsonObject.getJSONArray("weather")
|
||||
val weatherObject = weatherArray.getJSONObject(0)
|
||||
weather.weatherCondition = weatherObject.getString("main")
|
||||
weather.weatherDescription = weatherObject.getString("description")
|
||||
weather.weatherIconId = weatherObject.getString("icon")
|
||||
|
||||
/* Get temperature */
|
||||
val mainObject = jsonObject.getJSONObject("main")
|
||||
weather.temperature = mainObject.getDouble("temp").toFloat()
|
||||
|
||||
weather.cityName = jsonObject.getString("name")
|
||||
} catch (jsonException: JSONException) {
|
||||
jsonException.printStackTrace()
|
||||
}
|
||||
|
||||
return weather
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.weather
|
||||
|
||||
|
||||
internal class Weather {
|
||||
var weatherCondition: String? = null
|
||||
var weatherDescription: String? = null
|
||||
var weatherIconId: String? = null
|
||||
var temperature = 0f
|
||||
var cityName: String? = null
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.weather
|
||||
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
|
||||
internal class WeatherClient {
|
||||
|
||||
fun fetchWeather(wUrl: String?): String? {
|
||||
var httpURLConnection: HttpURLConnection? = null
|
||||
var bufferedReader: BufferedReader? = null
|
||||
|
||||
try {
|
||||
httpURLConnection = URL(wUrl).openConnection() as HttpURLConnection
|
||||
httpURLConnection.connect()
|
||||
bufferedReader = BufferedReader(InputStreamReader(httpURLConnection.inputStream))
|
||||
|
||||
val stringBuilder = StringBuilder()
|
||||
var line: String?
|
||||
while (bufferedReader.readLine().also { line = it } != null) {
|
||||
stringBuilder.append("$line\n")
|
||||
}
|
||||
|
||||
if (stringBuilder.isNotEmpty()) return stringBuilder.toString()
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
} finally {
|
||||
httpURLConnection?.disconnect()
|
||||
if (bufferedReader != null) {
|
||||
try {
|
||||
bufferedReader.close()
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.weather
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.View
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_CITY_NAME
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_OWM_API
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_SHOW_CITY
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_TEMP_UNIT
|
||||
import bums.lunatic.launcher.helpers.UniUtils.Companion.isNetworkAvailable
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
|
||||
internal class WeatherExecutor(sharedPreferences: SharedPreferences) {
|
||||
|
||||
companion object {
|
||||
var lastedCheckTime = 0L
|
||||
var weather: Weather? = null
|
||||
}
|
||||
|
||||
private val cityName: String
|
||||
private val owmApi: String
|
||||
private val weatherUrl: String
|
||||
private val tempUnit: Int
|
||||
private val showCity: Boolean
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun generateWeatherString(materialTextView: MaterialTextView) {
|
||||
materialTextView.visibility = View.GONE
|
||||
/* run the executor if network is available,
|
||||
and city name and owm api values are not empty */
|
||||
if (System.currentTimeMillis() - lastedCheckTime > (1000 * 60 * 15) && isNetworkAvailable && cityName.isNotEmpty() && owmApi.isNotEmpty()) {
|
||||
try {
|
||||
Executors.newSingleThreadExecutor().execute {
|
||||
WeatherClient().fetchWeather(weatherUrl).let {
|
||||
if (!it.isNullOrEmpty()) weather = JsonParser().getMyWeather(it)
|
||||
}
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (weather != null) {
|
||||
materialTextView.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = weather!!.temperature.toString().substringBefore(".") +
|
||||
(if (tempUnit == 0) "ºC" else "ºF") +
|
||||
(if (showCity) " at ${weather!!.cityName}" else "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
} else {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (weather != null) {
|
||||
materialTextView.apply {
|
||||
visibility = View.VISIBLE
|
||||
text = weather!!.temperature.toString().substringBefore(".") +
|
||||
(if (tempUnit == 0) "ºC" else "ºF") +
|
||||
(if (showCity) " at ${weather!!.cityName}" else "")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lastedCheckTime = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
init {
|
||||
cityName = sharedPreferences.getString(KEY_CITY_NAME, "").toString()
|
||||
owmApi = sharedPreferences.getString(KEY_OWM_API, "").toString()
|
||||
tempUnit = sharedPreferences.getInt(KEY_TEMP_UNIT, 0)
|
||||
showCity = sharedPreferences.getBoolean(KEY_SHOW_CITY, false)
|
||||
weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=$cityName&APPID=$owmApi&units=" + if (tempUnit == 0) "metric" else "imperial"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import bums.lunatic.launcher.beforeDay
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import java.util.Date
|
||||
|
||||
class MissD : RssDataInterface {
|
||||
var link : String? = null
|
||||
|
||||
var title : String? = null
|
||||
|
||||
var thumb : String? = null
|
||||
|
||||
var desc : String? = null
|
||||
|
||||
override fun title(): String {
|
||||
return title ?: ""
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return thumb ?: ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return link ?: ""
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return desc ?: ""
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
return beforeDay(Date())
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.GURU
|
||||
}
|
||||
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
class MostItem : JGuru , RssDataInterface {
|
||||
|
||||
constructor() : super()
|
||||
|
||||
override fun title(): String {
|
||||
return title
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return image
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return pageLink
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return tags.plus(", ").plus(model).plus(", ").plus(category())
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
return date
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.MOST
|
||||
}
|
||||
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
open class JGuru : RssDataInterface {
|
||||
var model : String = ""
|
||||
var title : String = ""
|
||||
var pageLink : String = ""
|
||||
var image : String = ""
|
||||
var tags : String = ""
|
||||
var date : Long = 0L
|
||||
|
||||
constructor(
|
||||
model: String,
|
||||
title: String,
|
||||
pageLink: String,
|
||||
image: String,
|
||||
tags: String,
|
||||
date: Long
|
||||
) {
|
||||
this.model = model
|
||||
this.title = title
|
||||
this.pageLink = pageLink
|
||||
this.image = image
|
||||
this.tags = tags
|
||||
this.date = date
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
override fun title(): String {
|
||||
return title
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return image
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return pageLink
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return tags.plus(", ").plus(model).plus(", ").plus(category())
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
return date
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.GURU
|
||||
}
|
||||
|
||||
fun isValid() = (((pageLink.length ?:0) > 0) && ((title.length ?:0) > 0)&& ((image.length ?:0) > 0))
|
||||
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
class jGuruTag : RssDataInterface {
|
||||
var link : String = ""
|
||||
var tagTitle = ""
|
||||
var count = 0
|
||||
|
||||
override fun title(): String {
|
||||
return tagTitle
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return link
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return " "
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
return count.toLong()
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.TAGS
|
||||
}
|
||||
|
||||
constructor(link: String, tagTitle: String) {
|
||||
this.link = link
|
||||
this.tagTitle = tagTitle
|
||||
if (tagTitle.contains("(") && tagTitle.contains(")")) {
|
||||
try {
|
||||
count = tagTitle.split("(")[1].split(")")[0].toInt()
|
||||
}catch (e : Exception) {}
|
||||
}
|
||||
}
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(tagTitle!!).joinToString("")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
class AppInfo : RealmObject {
|
||||
|
||||
var appName : String? = null
|
||||
var appNameChosung : String? = null
|
||||
var koreanName : String? = null
|
||||
var alphaCho : String? = null
|
||||
|
||||
@PrimaryKey
|
||||
var pkgName : String? = null
|
||||
|
||||
var clickCount : Int = 0
|
||||
var lastUseDate : Long = 0L
|
||||
var category : String? = null
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
class CiliMagnet {
|
||||
var link : String? = null
|
||||
var title : String? = null
|
||||
var size : String? = null
|
||||
var magnetLink : String? = null
|
||||
|
||||
fun isValid() = (((link?.length ?: 0) > 0 && (title?.length ?: 0) > 0 && (size?.length
|
||||
?: 0) > 0))
|
||||
|
||||
fun getMagnetPageLink() = "https://cili.site".plus(link)
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import bums.lunatic.launcher.utils.afterDay
|
||||
import bums.lunatic.launcher.utils.beforeDay
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.Ignore
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
import org.jsoup.select.Elements
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.TimeZone
|
||||
|
||||
|
||||
fun Elements.getT() = if (size > 0) get(0).text() else ""
|
||||
fun Elements.getHref() = if (size > 0) get(0).attr("href") else ""
|
||||
val dateFormat = SimpleDateFormat("hh:mm / yy - MM - dd")
|
||||
class Clien : DcInside() {
|
||||
companion object{
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
}
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.CLIEN
|
||||
}
|
||||
|
||||
|
||||
override fun pubDate(): Long {
|
||||
if (dateTiemL < 1L) {
|
||||
if (dateTiem?.length ?: 0 < 1) return 0L
|
||||
return dateFormat.parse(dateTiem!!).time.apply {
|
||||
dateTiemL = this
|
||||
}
|
||||
} else {
|
||||
return dateTiemL
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class Arca : RssDataInterface {
|
||||
var link : String? = null
|
||||
var title : String? = null
|
||||
var thumbnail : String? = null
|
||||
var desc : String? = null
|
||||
var dateTiem : String? = null
|
||||
var updateDateTime : Long = 0L
|
||||
override fun title(): String {
|
||||
return title ?: ""
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return thumbnail ?: ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return link ?: ""
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return desc ?: ""
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
var date = Date()
|
||||
var dateTime = date.time
|
||||
var before = 0
|
||||
if (updateDateTime == 0L) {
|
||||
try {
|
||||
// BLog.LOGE("this.dateTiem >>> ${this.dateTiem}")
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
sdf.timeZone = TimeZone.getTimeZone("GMT")
|
||||
updateDateTime = sdf.parse(this.dateTiem!!).time
|
||||
// BLog.LOGE("updateDateTime >>>> ${dateFormat.format(Date(updateDateTime))}" )
|
||||
// var targetDate = this.dateTiem ?: ""
|
||||
// if (targetDate?.length ?: 0 > 1) {
|
||||
// var dateDesc = targetDate
|
||||
// var isBefore = dateDesc.contains("전")
|
||||
// val dayString = dateDesc.replace("[^0-9]".toRegex(), "")
|
||||
// before = dayString.toInt()
|
||||
// if (dateDesc.contains("년")) {
|
||||
// before = 365 * before
|
||||
// dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
// } else if (dateDesc.contains("월")) {
|
||||
// before = 30 * before
|
||||
// dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
// } else if (dateDesc.contains("주")) {
|
||||
// before = 7 * before
|
||||
// dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
// } else if (dateDesc.contains("일")) {
|
||||
// dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
// } else if (dateDesc.contains("시간")) {
|
||||
// dateTime =
|
||||
// if (isBefore) dateTime.minus(before.times(1000L * 60L * 60L)) else dateTime.plus(
|
||||
// before.times(1000L * 60L * 60L)
|
||||
// )
|
||||
// } else if (dateDesc.contains("분")) {
|
||||
// dateTime =
|
||||
// if (isBefore) dateTime.minus(before.times(1000L * 60L)) else dateTime.plus(
|
||||
// before.times(1000L * 60L)
|
||||
// )
|
||||
// }
|
||||
// else if (dateDesc.contains("초")) {
|
||||
// dateTime =
|
||||
// if (isBefore) dateTime.minus(before.times(1000L)) else dateTime.plus(
|
||||
// before.times(1000L)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
} finally {
|
||||
// updateDateTime = dateTime
|
||||
}
|
||||
} else {
|
||||
dateTime = updateDateTime
|
||||
}
|
||||
return dateTime
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.ARCA
|
||||
}
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
open class DcInside : RssDataInterface {
|
||||
var link : String? = null
|
||||
var title : String? = null
|
||||
var thumbnail : String? = null
|
||||
var desc : String? = null
|
||||
var dateTiem : String? = null
|
||||
|
||||
var dateTiemL : Long = 0L
|
||||
|
||||
val dateF = SimpleDateFormat("MM-dd")
|
||||
val timeF = SimpleDateFormat("HH:mm")
|
||||
override fun title(): String {
|
||||
return title ?:""
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return thumbnail ?: ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return link ?:""
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return desc ?: ""
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
if (dateTiemL < 1L) {
|
||||
if (dateTiem?.length ?: 0 < 1) return 0L
|
||||
return if (dateTiem?.contains(":") == true) {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(Date())
|
||||
cal.set(Calendar.HOUR_OF_DAY, dateTiem!!.split(":")[0].toInt())
|
||||
cal.set(Calendar.MINUTE, dateTiem!!.split(":")[1].toInt())
|
||||
// cal.set(Calendar.MINUTE, dateTiem!!.split(":")[1].toI nt())
|
||||
dateTiemL = cal.timeInMillis
|
||||
dateTiemL
|
||||
} else if (dateTiem?.contains("-") == true) {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(Date())
|
||||
cal.set(Calendar.MONTH, dateTiem!!.split("-")[0].toInt() - 1)
|
||||
cal.set(Calendar.DAY_OF_MONTH, dateTiem!!.split("-")[1].toInt())
|
||||
// cal.add(Calendar.DAY_OF_MONTH, -1)
|
||||
dateTiemL = cal.timeInMillis
|
||||
dateTiemL
|
||||
} else {
|
||||
0L
|
||||
}
|
||||
} else {
|
||||
return dateTiemL
|
||||
}
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.DCINSIDE
|
||||
}
|
||||
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RuliWeb : DcInside() {
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.RULIWEB
|
||||
}
|
||||
}
|
||||
class TheQoo : DcInside() {
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.THEQOO
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RssData : RealmObject, RssDataInterface {
|
||||
|
||||
@PrimaryKey
|
||||
var originPage : String? = null
|
||||
var title : String? = null
|
||||
var description : String? = null
|
||||
var thumbnail : String? = null
|
||||
var pubDate : Long = 0L
|
||||
var category : String? = null
|
||||
|
||||
var chosung : String? = null
|
||||
|
||||
@Ignore
|
||||
var mRssDataType : RssDataType? = null
|
||||
override fun title(): String {
|
||||
return when(category()){
|
||||
RssDataType.NEWSFEED -> {
|
||||
if(title?.length ?: 0 > 30) title?.substring(0,30).plus("...") else title ?: ""
|
||||
}
|
||||
else -> title ?: ""
|
||||
}.apply {
|
||||
chosung = JamoUtils.split(this).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return thumbnail ?: ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return originPage ?: ""
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
|
||||
return when(category()){
|
||||
RssDataType.YOUTUBE -> {
|
||||
if(description?.contains("게시자") == true) description!!.split("게시자")[0] else description ?: ""
|
||||
}
|
||||
RssDataType.NEWSFEED -> {
|
||||
category().name
|
||||
}
|
||||
else -> description.plus(" / ").plus(category().name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
return pubDate
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
if (mRssDataType == null)
|
||||
mRssDataType = RssDataType.valueOf(category!!.uppercase())
|
||||
return mRssDataType!!
|
||||
}
|
||||
|
||||
override fun getCho(): String? {
|
||||
return chosung
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open class Dotax(var pageLink : String,
|
||||
var desc : String,
|
||||
var dateTime : String,
|
||||
var title : String,
|
||||
var thumbnail : String) : RssDataInterface {
|
||||
var updateDateTime = 0L
|
||||
override fun title(): String {
|
||||
return title
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return thumbnail
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return "https://m.cafe.daum.net".plus(pageLink)
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return desc
|
||||
}
|
||||
override fun pubDate(): Long {
|
||||
var date = Date()
|
||||
var dateTime = date.time
|
||||
var before = 0
|
||||
if (updateDateTime == 0L) {
|
||||
try {
|
||||
var targetDate = this.dateTime ?: ""
|
||||
if (targetDate?.length ?: 0 > 1) {
|
||||
var dateDesc = targetDate
|
||||
var isBefore = dateDesc.contains("전")
|
||||
val dayString = dateDesc.replace("[^0-9]".toRegex(), "")
|
||||
before = dayString.toInt()
|
||||
if (dateDesc.contains("년")) {
|
||||
before = 365 * before
|
||||
dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
} else if (dateDesc.contains("월")) {
|
||||
before = 30 * before
|
||||
dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
} else if (dateDesc.contains("주")) {
|
||||
before = 7 * before
|
||||
dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
} else if (dateDesc.contains("일")) {
|
||||
dateTime = if (isBefore) beforeDay(date, before) else afterDay(date, before)
|
||||
} else if (dateDesc.contains("시간")) {
|
||||
dateTime =
|
||||
if (isBefore) dateTime.minus(before.times(1000L * 60L * 60L)) else dateTime.plus(
|
||||
before.times(1000L * 60L * 60L)
|
||||
)
|
||||
} else if (dateDesc.contains("분")) {
|
||||
dateTime =
|
||||
if (isBefore) dateTime.minus(before.times(1000L * 60L)) else dateTime.plus(
|
||||
before.times(1000L * 60L)
|
||||
)
|
||||
}
|
||||
else if (dateDesc.contains("초")) {
|
||||
dateTime =
|
||||
if (isBefore) dateTime.minus(before.times(1000L)) else dateTime.plus(
|
||||
before.times(1000L)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
} finally {
|
||||
updateDateTime = dateTime
|
||||
}
|
||||
} else {
|
||||
dateTime = updateDateTime
|
||||
}
|
||||
return dateTime
|
||||
}
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.DOTAX
|
||||
}
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
data class FmKorea(var apageLink : String,
|
||||
var adesc : String,
|
||||
var adateTime : String,
|
||||
var atitle : String,
|
||||
var athumbnail : String) : Dotax(apageLink, adesc, adateTime, atitle, athumbnail) {
|
||||
override fun originPage(): String {
|
||||
return pageLink
|
||||
}
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.FMKORAE
|
||||
}
|
||||
}
|
||||
|
||||
fun RssDataInterface.getRssData() : RssData {
|
||||
return RssData().apply {
|
||||
title = this@getRssData.title()
|
||||
description = this@getRssData.description()
|
||||
originPage = this@getRssData.originPage()
|
||||
thumbnail = this@getRssData.thumbnailUrl()
|
||||
pubDate = this@getRssData.pubDate()
|
||||
chosung = this@getRssData.getCho()
|
||||
category = this@getRssData.category().name
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
class CurrentPlayItem : RealmObject {
|
||||
@PrimaryKey
|
||||
var uniqId = "RPrimaryKey"
|
||||
var title : String? = ""
|
||||
var artists : String? = ""
|
||||
var albumArt : String? = ""
|
||||
var state : Int = 0
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import android.location.Address
|
||||
import io.realm.kotlin.ext.realmListOf
|
||||
import io.realm.kotlin.types.RealmList
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
class LocationLog : RealmObject {
|
||||
var mFeatureName: String? = null
|
||||
var mAddressLines: RealmList<String> = realmListOf()
|
||||
var mAdminArea: String? = null
|
||||
var mSubAdminArea: String? = null
|
||||
var mLocality: String? = null
|
||||
var mSubLocality: String? = null
|
||||
var mThoroughfare: String? = null
|
||||
var mSubThoroughfare: String? = null
|
||||
var mPremises: String? = null
|
||||
var mPostalCode: String? = null
|
||||
var mCountryCode: String? = null
|
||||
var mCountryName: String? = null
|
||||
var mLatitude = 0.0
|
||||
var mLongitude = 0.0
|
||||
|
||||
var mPhone: String? = null
|
||||
var mUrl: String? = null
|
||||
var time : Long = 0L
|
||||
var timeString : String? = null
|
||||
var userId : String? = null
|
||||
|
||||
fun fillData(address: Address) {
|
||||
time = System.currentTimeMillis()
|
||||
timeString = SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())
|
||||
mFeatureName = address.featureName
|
||||
mAddressLines.apply {
|
||||
for (i in 0..address.maxAddressLineIndex) {
|
||||
this.add(address.getAddressLine(i))
|
||||
}
|
||||
}
|
||||
mAdminArea = address.adminArea
|
||||
mSubAdminArea = address.subAdminArea
|
||||
mLocality = address.locality
|
||||
mSubLocality = address.subLocality
|
||||
mThoroughfare = address.thoroughfare
|
||||
mSubThoroughfare = address.subThoroughfare
|
||||
mPremises = address.premises
|
||||
mPostalCode = address.postalCode
|
||||
mCountryCode = address.countryCode
|
||||
mCountryName = address.countryName
|
||||
mLatitude = address.latitude
|
||||
mLongitude = address.longitude
|
||||
mPhone = address.phone
|
||||
mUrl = address.url
|
||||
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val buffer = StringBuffer()
|
||||
buffer.append(mFeatureName).append("|").append("\n")
|
||||
buffer.append(mAddressLines.joinToString(" , ")).append("|").append("\n")
|
||||
buffer.append(mAdminArea).append("|").append("\n")
|
||||
buffer.append(mSubAdminArea).append("|").append("\n")
|
||||
buffer.append(mLocality).append("|").append("\n")
|
||||
buffer.append(mSubLocality).append("|").append("\n")
|
||||
buffer.append(mThoroughfare).append("|").append("\n")
|
||||
buffer.append(mSubThoroughfare).append("|").append("\n")
|
||||
buffer.append(mPremises).append("|").append("\n")
|
||||
buffer.append(mPostalCode).append("|").append("\n")
|
||||
buffer.append(mCountryCode).append("|").append("\n")
|
||||
buffer.append(mCountryName).append("|").append("\n")
|
||||
buffer.append(mLatitude).append("|").append("\n")
|
||||
buffer.append(mLongitude).append("|").append("\n")
|
||||
buffer.append(mPhone).append("|").append("\n")
|
||||
buffer.append(mUrl).append("|").append("\n")
|
||||
return buffer.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
|
||||
class NewsData : RssDataInterface {
|
||||
|
||||
var title : String? = ""
|
||||
var link : String? = ""
|
||||
var guid : String? = ""
|
||||
var description : String? = ""
|
||||
var pubDate : Long = 0L
|
||||
var source : String? = ""
|
||||
|
||||
constructor(title: String?,link: String?) {
|
||||
this.link = link
|
||||
this.title = title
|
||||
}
|
||||
|
||||
override fun title(): String {
|
||||
return title ?: ""
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return source ?: ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
return link ?: ""
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return description ?: ""
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
return pubDate
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return RssDataType.NEWSFEED
|
||||
}
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title()).joinToString("")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
class NotificationItem : RealmObject{
|
||||
var notiId : Int = 0
|
||||
var pkgName : String? = null
|
||||
var tikerMsg : String? = null
|
||||
var title : String? = null
|
||||
var subtext : String? = null
|
||||
var selfDisplayName : String? = null
|
||||
var postTime : Long = 0L
|
||||
@PrimaryKey
|
||||
var uniq_id : String? = null
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import android.view.View
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
|
||||
enum class RssDataType {
|
||||
NO_DATA,
|
||||
YOUTUBE,
|
||||
NEWSFEED,
|
||||
GURU,
|
||||
MOST,
|
||||
TAGS,
|
||||
REDDIT,
|
||||
REDDIT_NSFW,
|
||||
DOTAX,
|
||||
FMKORAE,
|
||||
DCINSIDE,
|
||||
RULIWEB,
|
||||
CLIEN,
|
||||
THEQOO,
|
||||
ARCA;
|
||||
|
||||
fun getResId() = when (this) {
|
||||
YOUTUBE -> R.drawable.youtube
|
||||
REDDIT, REDDIT_NSFW -> R.drawable.reddit
|
||||
DOTAX -> R.drawable.daum
|
||||
FMKORAE -> R.drawable.fmk
|
||||
DCINSIDE -> R.drawable.dcinside
|
||||
ARCA -> R.drawable.arca
|
||||
else -> {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fun defaultImgSize() = when (this) {
|
||||
YOUTUBE -> 200
|
||||
REDDIT_NSFW,GURU,MOST -> 360
|
||||
else -> { 120 }
|
||||
}
|
||||
|
||||
fun getDefaultVisibiliy() = when (this) {
|
||||
REDDIT_NSFW,GURU,MOST,NEWSFEED -> View.GONE
|
||||
else -> { View.VISIBLE }
|
||||
}
|
||||
|
||||
fun isOn(block : ()->Unit) {
|
||||
if(PrefHelper.getBoolean(name,false)) {
|
||||
block.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RssDataInterface {
|
||||
fun title() : String
|
||||
fun thumbnailUrl() : String
|
||||
fun originPage() : String
|
||||
fun description() : String
|
||||
fun pubDate() : Long
|
||||
fun category() : RssDataType
|
||||
fun getCho() : String?
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import io.realm.kotlin.ext.realmListOf
|
||||
import io.realm.kotlin.types.RealmList
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.Ignore
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
class TelegramBotUpdate : RealmObject {
|
||||
var ok : String? = null
|
||||
@Ignore
|
||||
var result : ArrayList<TelegramData> = arrayListOf()
|
||||
var list : RealmList<TelegramData> = realmListOf()
|
||||
fun isOK() = "true".equals(ok)
|
||||
fun fill() {
|
||||
list.clear()
|
||||
list.addAll(result)
|
||||
list.forEach {
|
||||
it.fill()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class TelegramData : RealmObject{
|
||||
fun fill() {
|
||||
message?.fill()
|
||||
}
|
||||
|
||||
@PrimaryKey
|
||||
var update_id : Long = 0L
|
||||
var message : TelegramMessage? = null
|
||||
var my_chat_member : TelegramMessage? = null
|
||||
}
|
||||
|
||||
class TelegramMessage : RealmObject{
|
||||
fun fill() {
|
||||
commandEentitie.clear()
|
||||
commandEentitie.addAll(entities)
|
||||
}
|
||||
|
||||
@PrimaryKey
|
||||
var message_id : Long = 0L
|
||||
var from : TelegramFrom? = null
|
||||
var chat : TelegramChat? = null
|
||||
var date : Long = 0L
|
||||
var text : String? = null
|
||||
@Ignore
|
||||
var entities : ArrayList<BotCommandEentitie> = arrayListOf()
|
||||
set(n) {
|
||||
commandEentitie.clear()
|
||||
commandEentitie.addAll(n)
|
||||
}
|
||||
var commandEentitie : RealmList<BotCommandEentitie> = realmListOf()
|
||||
}
|
||||
|
||||
class TelegramChat : RealmObject{
|
||||
@PrimaryKey
|
||||
var id :Long = 0L
|
||||
var language_code : String? = null
|
||||
var is_bot : String? = null //"id": 7068729507,
|
||||
var first_name : String? = null //"first_name": "BUM",
|
||||
var last_name : String? = null //"last_name": "Han",
|
||||
var type : String? = null //"type": "private"
|
||||
}
|
||||
|
||||
class BotCommandEentitie : RealmObject{
|
||||
var offset : Int = 0
|
||||
var length : Int = 0
|
||||
var type : String? = null
|
||||
}
|
||||
|
||||
class TelegramFrom : RealmObject {
|
||||
@PrimaryKey
|
||||
var id :Long = 0L
|
||||
var language_code : String? = null
|
||||
var is_bot : String? = null //"id": 7068729507,
|
||||
var first_name : String? = null //"first_name": "BUM",
|
||||
var last_name : String? = null //"last_name": "Han",
|
||||
var type : String? = null //"type": "private"
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import io.realm.kotlin.ext.realmListOf
|
||||
import io.realm.kotlin.types.RealmList
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.Ignore
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
|
||||
class WeatherForcast: RealmObject {
|
||||
@PrimaryKey
|
||||
var isOnlyKey = "isOnlyKey"
|
||||
|
||||
var location: Location? = null
|
||||
var current: Current? = null
|
||||
var forecast: Forecast? = null
|
||||
var lastUpdateTime : Long = 0L
|
||||
|
||||
// fun readyForSaving() {
|
||||
// lastUpdateTime = System.currentTimeMillis()
|
||||
// forecast?.fill()
|
||||
// }
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
class Location: RealmObject {
|
||||
var name: String? = null
|
||||
var region: String? = null
|
||||
var country: String? = null
|
||||
var lat = 0.0
|
||||
var lon = 0.0
|
||||
var tz_id: String? = null
|
||||
var localtime_epoch = 0
|
||||
var localtime: String? = null
|
||||
|
||||
// fun getTextLocation(): String = "$name / $country"
|
||||
}
|
||||
|
||||
class Current: RealmObject {
|
||||
var last_updated_epoch = 0
|
||||
var last_updated: String? = null
|
||||
var temp_c = 0.0
|
||||
var temp_f = 0.0
|
||||
var is_day = 0
|
||||
var condition: Condition? = null
|
||||
var wind_mph = 0.0
|
||||
var wind_kph = 0.0
|
||||
var wind_degree = 0
|
||||
var wind_dir: String? = null
|
||||
var pressure_mb = 0.0
|
||||
var pressure_in = 0.0
|
||||
var precip_mm = 0.0
|
||||
var precip_in = 0.0
|
||||
var humidity = 0
|
||||
var cloud = 0
|
||||
var feelslike_c = 0.0
|
||||
var feelslike_f = 0.0
|
||||
var windchill_c = 0.0
|
||||
var windchill_f = 0.0
|
||||
var heatindex_c = 0.0
|
||||
var heatindex_f = 0.0
|
||||
var dewpoint_c = 0.0
|
||||
var dewpoint_f = 0.0
|
||||
var vis_km = 0.0
|
||||
var vis_miles = 0.0
|
||||
var uv = 0.0
|
||||
var gust_mph = 0.0
|
||||
var gust_kph = 0.0
|
||||
}
|
||||
|
||||
class Forecast: RealmObject {
|
||||
@Ignore
|
||||
var forecastday: ArrayList<Forecastday> = arrayListOf()
|
||||
var forecastdayRealm: RealmList<Forecastday> = realmListOf()
|
||||
|
||||
// fun fill() {
|
||||
// if (forecastdayRealm.addAll(forecastday)) {
|
||||
// forecastdayRealm.forEach { f -> f.fill() }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Current
|
||||
class Condition: RealmObject {
|
||||
var text: String? = null
|
||||
var icon: String? = null
|
||||
var code = 0
|
||||
}
|
||||
// Forecast
|
||||
class Forecastday: RealmObject {
|
||||
var date: String? = null
|
||||
@PrimaryKey
|
||||
var date_epoch = 0
|
||||
var day: Day? = null
|
||||
var astro: Astro? = null
|
||||
@Ignore
|
||||
var hour: ArrayList<Hour> = arrayListOf()
|
||||
var hourRealm: RealmList<Hour> = realmListOf()
|
||||
|
||||
// fun fill() {
|
||||
// hourRealm.addAll(hour)
|
||||
// }
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
// Forecastday
|
||||
class Day: RealmObject {
|
||||
var maxtemp_c = 0.0
|
||||
var maxtemp_f = 0.0
|
||||
var mintemp_c = 0.0
|
||||
var mintemp_f = 0.0
|
||||
var avgtemp_c = 0.0
|
||||
var avgtemp_f = 0.0
|
||||
var maxwind_mph = 0.0
|
||||
var maxwind_kph = 0.0
|
||||
var totalprecip_mm = 0.0
|
||||
var totalprecip_in = 0.0
|
||||
var totalsnow_cm = 0.0
|
||||
var avgvis_km = 0.0
|
||||
var avgvis_miles = 0.0
|
||||
var avghumidity = 0
|
||||
var daily_will_it_rain = 0
|
||||
var daily_chance_of_rain = 0
|
||||
var daily_will_it_snow = 0
|
||||
var daily_chance_of_snow = 0
|
||||
var condition: Condition? = null
|
||||
var uv = 0.0
|
||||
}
|
||||
|
||||
class Astro: RealmObject {
|
||||
var sunrise: String? = null
|
||||
var sunset: String? = null
|
||||
var moonrise: String? = null
|
||||
var moonset: String? = null
|
||||
var moon_phase: String? = null
|
||||
var moon_illumination = 0
|
||||
var is_moon_up = 0
|
||||
var is_sun_up = 0
|
||||
}
|
||||
|
||||
class Hour: RealmObject {
|
||||
var lat : Double = 0.0
|
||||
var lon : Double = 0.0
|
||||
|
||||
@PrimaryKey
|
||||
var time_epoch = 0
|
||||
var time: String? = null
|
||||
var temp_c = 0.0
|
||||
var temp_f = 0.0
|
||||
var is_day = 0
|
||||
var condition: Condition? = null
|
||||
var wind_mph = 0.0
|
||||
var wind_kph = 0.0
|
||||
var wind_degree = 0
|
||||
var wind_dir: String? = null
|
||||
var pressure_mb = 0.0
|
||||
var pressure_in = 0.0
|
||||
var precip_mm = 0.0
|
||||
var precip_in = 0.0
|
||||
var snow_cm = 0.0
|
||||
var humidity = 0
|
||||
var cloud = 0
|
||||
var feelslike_c = 0.0
|
||||
var feelslike_f = 0.0
|
||||
var windchill_c = 0.0
|
||||
var windchill_f = 0.0
|
||||
var heatindex_c = 0.0
|
||||
var heatindex_f = 0.0
|
||||
var dewpoint_c = 0.0
|
||||
var dewpoint_f = 0.0
|
||||
var will_it_rain = 0
|
||||
var chance_of_rain = 0
|
||||
var will_it_snow = 0
|
||||
var chance_of_snow = 0
|
||||
var vis_km = 0.0
|
||||
var vis_miles = 0.0
|
||||
var gust_mph = 0.0
|
||||
var gust_kph = 0.0
|
||||
var uv = 0.0
|
||||
|
||||
// fun toZonedDateTime(timeEpoch: Int) = Instant.ofEpochSecond(timeEpoch.toLong())
|
||||
// .atZone(ZoneId.systemDefault())
|
||||
//
|
||||
// fun getAmOrPm(): String = toZonedDateTime(time_epoch).hour.run {
|
||||
// if (this < 12) "오전" else "오후"
|
||||
// }
|
||||
//
|
||||
// fun getTextHour(): String = toZonedDateTime(time_epoch)
|
||||
// .run {
|
||||
// if (this.hour == 0) {
|
||||
// when (this.dayOfYear - Calendar.getInstance().get(Calendar.DAY_OF_YEAR)) {
|
||||
// 1 -> "내일"
|
||||
// 2 -> "모레"
|
||||
// 3 -> "글피"
|
||||
// else -> "${this.dayOfMonth}일"
|
||||
// }
|
||||
// } else {
|
||||
// "${this.hour}시"
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun getDress(): String =
|
||||
// if (temp_c >= 23) {
|
||||
// "반팔"
|
||||
// } else if ((23 > temp_c) && (temp_c >= 20)) {
|
||||
// "긴팔"
|
||||
// } else if ((20 > temp_c) && (temp_c >= 17)) {
|
||||
// "니트"
|
||||
// } else if ((17 > temp_c) && (temp_c >= 12)) {
|
||||
// "얇은겉옷"
|
||||
// } else if ((12 > temp_c) && (temp_c >= 6)) {
|
||||
// "두꺼운겉옷"
|
||||
// } else {
|
||||
// "패딩"
|
||||
// }
|
||||
//
|
||||
// fun getImgDress() =
|
||||
// if (temp_c >= 23) {
|
||||
// R.drawable.dress_short_sleeves
|
||||
// } else if ((23 > temp_c) && (temp_c >= 20)) {
|
||||
// "긴팔"
|
||||
// } else if ((20 > temp_c) && (temp_c >= 17)) {
|
||||
// "니트"
|
||||
// } else if ((17 > temp_c) && (temp_c >= 12)) {
|
||||
// "얇은겉옷"
|
||||
// } else if ((12 > temp_c) && (temp_c >= 6)) {
|
||||
// "두꺼운겉옷"
|
||||
// } else {
|
||||
// "패딩"
|
||||
// }
|
||||
//
|
||||
// fun getTemp(): String = "${temp_c}도"
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.types.RealmList
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.util.Calendar
|
||||
|
||||
class ShowingWeatherInfo() {
|
||||
var textLocation: String? = null
|
||||
var amOrPm: String? = null
|
||||
var textHour: String? = null
|
||||
var dress: String? = null
|
||||
var imgDress: Int? = null
|
||||
var temp: String? = null
|
||||
var textCondition: String? = null
|
||||
var urlImgWeather: String? = null
|
||||
|
||||
fun setLocation(loc: Location?): ShowingWeatherInfo {
|
||||
textLocation = "${loc?.name ?: "도시"} / ${loc?.country ?: "나라"}"
|
||||
return this
|
||||
}
|
||||
fun setHour(hour: Hour): ShowingWeatherInfo {
|
||||
setAmOrPm(hour)
|
||||
setTextHour(hour)
|
||||
setDress(hour)
|
||||
setTemp(hour)
|
||||
setTextCondition(hour)
|
||||
setUrlImgWeather(hour)
|
||||
return this
|
||||
}
|
||||
private fun setAmOrPm(hour: Hour) = WeatherInfoManager.toZonedDateTime(hour.time_epoch).hour.run {
|
||||
amOrPm = if (this < 12) "오전" else "오후"
|
||||
}
|
||||
private fun setTextHour(hour: Hour) {
|
||||
textHour = WeatherInfoManager.toZonedDateTime(hour.time_epoch).run {
|
||||
if (this.hour == 0) {
|
||||
when (this.dayOfYear - Calendar.getInstance().get(Calendar.DAY_OF_YEAR)) {
|
||||
1 -> "내일"
|
||||
2 -> "모레"
|
||||
3 -> "글피"
|
||||
else -> "${this.dayOfMonth}일"
|
||||
}
|
||||
} else {
|
||||
"${this.hour}시"
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun setDress(hour: Hour) {
|
||||
if (hour.temp_c >= 23) {
|
||||
dress = "반팔"
|
||||
imgDress = R.drawable.dress_short_sleeves
|
||||
} else if ((23 > hour.temp_c) && (hour.temp_c >= 20)) {
|
||||
dress = "긴팔"
|
||||
imgDress = R.drawable.dress_long_sleeves
|
||||
} else if ((20 > hour.temp_c) && (hour.temp_c >= 17)) {
|
||||
dress = "니트"
|
||||
imgDress = R.drawable.dress_knitwear
|
||||
} else if ((17 > hour.temp_c) && (hour.temp_c >= 12)) {
|
||||
dress = "얇은겉옷"
|
||||
imgDress = R.drawable.dress_flimsy_outer
|
||||
} else if ((12 > hour.temp_c) && (hour.temp_c >= 6)) {
|
||||
dress = "두꺼운겉옷"
|
||||
imgDress = R.drawable.dress_heavy_outer
|
||||
} else {
|
||||
dress = "패딩"
|
||||
imgDress = R.drawable.dress_padded_coat
|
||||
}
|
||||
}
|
||||
private fun setTemp(hour: Hour) {temp = "${hour.temp_c}도"}
|
||||
|
||||
private fun setTextCondition(hour: Hour) {textCondition = hour.condition?.text}
|
||||
|
||||
private fun setUrlImgWeather(hour: Hour) {
|
||||
hour.condition?.icon?.let {
|
||||
urlImgWeather = if (!it.contains("https:")) "https:$it" else it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object WeatherInfoManager {
|
||||
var info: WeatherForcast? = null
|
||||
// var showingInfo: ShowingWeatherInfo? = null
|
||||
|
||||
|
||||
// fun setInfo(data: WeatherForcast) {
|
||||
// this.info = data
|
||||
// BLog.LOGE("Now WeatherForcast updated. info : ${this.info}")
|
||||
// }
|
||||
|
||||
fun readyForSaving(lat: Double, lon: Double) {
|
||||
this.info?.lastUpdateTime = System.currentTimeMillis()
|
||||
this.fill()
|
||||
setLatitudeAndLongitude(lat, lon)
|
||||
}
|
||||
|
||||
private fun setLatitudeAndLongitude(lat: Double, lon: Double) {
|
||||
info?.forecast?.forecastdayRealm?.forEach {
|
||||
it.hourRealm.forEach {h ->
|
||||
h.lat = lat
|
||||
h.lon = lon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fill() {
|
||||
getForecast()?.let {
|
||||
if (it.forecastdayRealm.addAll(it.forecastday)) {
|
||||
it.forecastdayRealm.forEach { f -> f.hourRealm.addAll(f.hour) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun toZonedDateTime(timeEpoch: Int): ZonedDateTime = Instant.ofEpochSecond(timeEpoch.toLong())
|
||||
.atZone(ZoneId.systemDefault())
|
||||
|
||||
fun getShowingInfo(hour: Hour): ShowingWeatherInfo = ShowingWeatherInfo().setLocation(getLocation()).setHour(hour)
|
||||
|
||||
|
||||
private fun getLocation(): Location? {
|
||||
return if (info?.location == null) {
|
||||
WorkersDb.getRealm().query<Location>().also {
|
||||
BLog.LOGE("re >>> ${it.description()}") // 쿼리 로그
|
||||
}.find().first()
|
||||
} else {
|
||||
info?.location
|
||||
}
|
||||
}
|
||||
|
||||
fun getForecast(): Forecast? = info?.forecast
|
||||
fun getAllForecastDay(): RealmList<Forecastday>? = getForecast()?.forecastdayRealm
|
||||
fun getAllHour(day: Forecastday): RealmList<Hour> = day.hourRealm
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
package bums.lunatic.launcher.model.others
|
||||
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
|
||||
|
||||
class Child {
|
||||
var kind: String? = null
|
||||
var data: Data? = null
|
||||
}
|
||||
|
||||
class CrosspostParentList {
|
||||
var approved_at_utc: Any? = null
|
||||
var subreddit: String? = null
|
||||
var selftext: String? = null
|
||||
var author_fullname: String? = null
|
||||
var saved: Boolean = false
|
||||
var mod_reason_title: Any? = null
|
||||
var gilded: Int = 0
|
||||
var clicked: Boolean = false
|
||||
var title: String? = null
|
||||
var link_flair_richtext: ArrayList<Any>? = null
|
||||
var subreddit_name_prefixed: String? = null
|
||||
var hidden: Boolean = false
|
||||
var pwls: Any? = null
|
||||
var link_flair_css_class: String? = null
|
||||
var downs: Int = 0
|
||||
var thumbnail_height: Int = 0
|
||||
var top_awarded_type: Any? = null
|
||||
var hide_score: Boolean = false
|
||||
var name: String? = null
|
||||
var quarantine: Boolean = false
|
||||
var link_flair_text_color: String? = null
|
||||
var upvote_ratio: Double = 0.0
|
||||
var author_flair_background_color: Any? = null
|
||||
var subreddit_type: String? = null
|
||||
var ups: Int = 0
|
||||
var total_awards_received: Int = 0
|
||||
var media_embed: MediaEmbed? = null
|
||||
var thumbnail_width: Int = 0
|
||||
var author_flair_template_id: Any? = null
|
||||
var is_original_content: Boolean = false
|
||||
var user_reports: ArrayList<Any>? = null
|
||||
var secure_media: SecureMedia? = null
|
||||
var is_reddit_media_domain: Boolean = false
|
||||
var is_meta: Boolean = false
|
||||
var category: Any? = null
|
||||
var secure_media_embed: SecureMediaEmbed? = null
|
||||
var link_flair_text: String? = null
|
||||
var can_mod_post: Boolean = false
|
||||
var score: Int = 0
|
||||
var approved_by: Any? = null
|
||||
var is_created_from_ads_ui: Boolean = false
|
||||
var author_premium: Boolean = false
|
||||
var thumbnail: String? = null
|
||||
var edited: String? = null
|
||||
var author_flair_css_class: Any? = null
|
||||
var author_flair_richtext: ArrayList<Any>? = null
|
||||
var gildings: Gildings? = null
|
||||
var post_hint: String? = null
|
||||
var content_categories: Any? = null
|
||||
var is_self: Boolean = false
|
||||
var mod_note: Any? = null
|
||||
var created: Double = 0.0
|
||||
var link_flair_type: String? = null
|
||||
var wls: Any? = null
|
||||
var removed_by_category: Any? = null
|
||||
var banned_by: Any? = null
|
||||
var author_flair_type: String? = null
|
||||
var domain: String? = null
|
||||
var allow_live_comments: Boolean = false
|
||||
var selftext_html: Any? = null
|
||||
var likes: Any? = null
|
||||
var suggested_sort: Any? = null
|
||||
var banned_at_utc: Any? = null
|
||||
var url_overridden_by_dest: String? = null
|
||||
var view_count: Any? = null
|
||||
var archived: Boolean = false
|
||||
var no_follow: Boolean = false
|
||||
var is_crosspostable: Boolean = false
|
||||
var pinned: Boolean = false
|
||||
var over_18: Boolean = false
|
||||
var preview: Preview? = null
|
||||
var all_awardings: ArrayList<Any>? = null
|
||||
var awarders: ArrayList<Any>? = null
|
||||
var media_only: Boolean = false
|
||||
var can_gild: Boolean = false
|
||||
var spoiler: Boolean = false
|
||||
var locked: Boolean = false
|
||||
var author_flair_text: Any? = null
|
||||
var treatment_tags: ArrayList<Any>? = null
|
||||
var visited: Boolean = false
|
||||
var removed_by: Any? = null
|
||||
var num_reports: Any? = null
|
||||
var distinguished: Any? = null
|
||||
var subreddit_id: String? = null
|
||||
var author_is_blocked: Boolean = false
|
||||
var mod_reason_by: Any? = null
|
||||
var removal_reason: Any? = null
|
||||
var link_flair_background_color: String? = null
|
||||
var id: String? = null
|
||||
var is_robot_indexable: Boolean = false
|
||||
var report_reasons: Any? = null
|
||||
var author: String? = null
|
||||
var discussion_type: Any? = null
|
||||
var num_comments: Int = 0
|
||||
var send_replies: Boolean = false
|
||||
var whitelist_status: Any? = null
|
||||
var contest_mode: Boolean = false
|
||||
var mod_reports: ArrayList<Any>? = null
|
||||
var author_patreon_flair: Boolean = false
|
||||
var author_flair_text_color: Any? = null
|
||||
var permalink: String? = null
|
||||
var parent_whitelist_status: Any? = null
|
||||
var stickied: Boolean = false
|
||||
var url: String? = null
|
||||
var subreddit_subscribers: Int = 0
|
||||
var created_utc: Double = 0.0
|
||||
var num_crossposts: Int = 0
|
||||
var media: Media? = null
|
||||
var is_video: Boolean = false
|
||||
var link_flair_template_id: String? = null
|
||||
}
|
||||
|
||||
class Data : RssDataInterface {
|
||||
var after: String? = null
|
||||
var dist: Int = 0
|
||||
var modhash: String? = null
|
||||
var geo_filter: Any? = null
|
||||
var children: ArrayList<Child>? = null
|
||||
var before: Any? = null
|
||||
var approved_at_utc: Any? = null
|
||||
var subreddit: String? = null
|
||||
var selftext: String? = null
|
||||
var user_reports: ArrayList<Any>? = null
|
||||
var saved: Boolean = false
|
||||
var mod_reason_title: Any? = null
|
||||
var gilded: Int = 0
|
||||
var clicked: Boolean = false
|
||||
var title: String? = null
|
||||
var link_flair_richtext: ArrayList<Any>? = null
|
||||
var subreddit_name_prefixed: String? = null
|
||||
var hidden: Boolean = false
|
||||
var pwls: Any? = null
|
||||
var link_flair_css_class: Any? = null
|
||||
var downs: Int = 0
|
||||
var thumbnail_height: Int = 0
|
||||
var top_awarded_type: Any? = null
|
||||
var hide_score: Boolean = false
|
||||
var name: String? = null
|
||||
var quarantine: Boolean = false
|
||||
var link_flair_text_color: String? = null
|
||||
var upvote_ratio: Double = 0.0
|
||||
var author_flair_background_color: Any? = null
|
||||
var subreddit_type: String? = null
|
||||
var ups: Int = 0
|
||||
var total_awards_received: Int = 0
|
||||
var media_embed: MediaEmbed? = null
|
||||
var thumbnail_width: Int = 0
|
||||
var author_flair_template_id: Any? = null
|
||||
var is_original_content: Boolean = false
|
||||
var author_fullname: String? = null
|
||||
var secure_media: SecureMedia? = null
|
||||
var is_reddit_media_domain: Boolean = false
|
||||
var is_meta: Boolean = false
|
||||
var category: Any? = null
|
||||
var secure_media_embed: SecureMediaEmbed? = null
|
||||
var link_flair_text: Any? = null
|
||||
var can_mod_post: Boolean = false
|
||||
var score: Int = 0
|
||||
var approved_by: Any? = null
|
||||
var is_created_from_ads_ui: Boolean = false
|
||||
var author_premium: Boolean = false
|
||||
var thumbnail: String? = null
|
||||
var edited: String? = null
|
||||
var author_flair_css_class: Any? = null
|
||||
var author_flair_richtext: ArrayList<Any>? = null
|
||||
var gildings: Gildings? = null
|
||||
var post_hint: String? = null
|
||||
var content_categories: Any? = null
|
||||
var is_self: Boolean = false
|
||||
var mod_note: Any? = null
|
||||
var crosspost_parent_list: ArrayList<CrosspostParentList>? = null
|
||||
var created: Double = 0.0
|
||||
var link_flair_type: String? = null
|
||||
var wls: Any? = null
|
||||
var removed_by_category: Any? = null
|
||||
var banned_by: Any? = null
|
||||
var author_flair_type: String? = null
|
||||
var domain: String? = null
|
||||
var allow_live_comments: Boolean = false
|
||||
var selftext_html: Any? = null
|
||||
var likes: Any? = null
|
||||
var suggested_sort: Any? = null
|
||||
var banned_at_utc: Any? = null
|
||||
var url_overridden_by_dest: String? = null
|
||||
var view_count: Any? = null
|
||||
var archived: Boolean = false
|
||||
var no_follow: Boolean = false
|
||||
var is_crosspostable: Boolean = false
|
||||
var pinned: Boolean = false
|
||||
var over_18: Boolean = false
|
||||
var preview: Preview? = null
|
||||
var all_awardings: ArrayList<Any>? = null
|
||||
var awarders: ArrayList<Any>? = null
|
||||
var media_only: Boolean = false
|
||||
var can_gild: Boolean = false
|
||||
var spoiler: Boolean = false
|
||||
var locked: Boolean = false
|
||||
var author_flair_text: Any? = null
|
||||
var treatment_tags: ArrayList<Any>? = null
|
||||
var visited: Boolean = false
|
||||
var removed_by: Any? = null
|
||||
var num_reports: Any? = null
|
||||
var distinguished: Any? = null
|
||||
var subreddit_id: String? = null
|
||||
var author_is_blocked: Boolean = false
|
||||
var mod_reason_by: Any? = null
|
||||
var removal_reason: Any? = null
|
||||
var link_flair_background_color: String? = null
|
||||
var id: String? = null
|
||||
var is_robot_indexable: Boolean = false
|
||||
var report_reasons: Any? = null
|
||||
var author: String? = null
|
||||
var discussion_type: Any? = null
|
||||
var num_comments: Int = 0
|
||||
var send_replies: Boolean = false
|
||||
var whitelist_status: Any? = null
|
||||
var contest_mode: Boolean = false
|
||||
var mod_reports: ArrayList<Any>? = null
|
||||
var author_patreon_flair: Boolean = false
|
||||
var crosspost_parent: String? = null
|
||||
var author_flair_text_color: Any? = null
|
||||
var permalink: String? = null
|
||||
var parent_whitelist_status: Any? = null
|
||||
var stickied: Boolean = false
|
||||
var url: String? = null
|
||||
var subreddit_subscribers: Int = 0
|
||||
var created_utc: Double = 0.0
|
||||
var num_crossposts: Int = 0
|
||||
var media: Media? = null
|
||||
var is_video: Boolean = false
|
||||
|
||||
var nsfw : Boolean = false
|
||||
override fun title(): String {
|
||||
return title ?: ""
|
||||
}
|
||||
|
||||
override fun thumbnailUrl(): String {
|
||||
return thumbnail ?: ""
|
||||
}
|
||||
|
||||
override fun originPage(): String {
|
||||
if (post_hint?.contains("video") == true && (permalink?.length ?: 0 > 3)) {
|
||||
return "https://www.reddit.com".plus(permalink)
|
||||
}
|
||||
return url ?: ""
|
||||
}
|
||||
|
||||
override fun description(): String {
|
||||
return subreddit_name_prefixed ?: subreddit ?: ""
|
||||
}
|
||||
|
||||
override fun pubDate(): Long {
|
||||
// return beforeDay(java.util.Date(),2)
|
||||
if (created_utc != null) {
|
||||
return created_utc!!.toLong() * 1000L
|
||||
} else if (created != null) {
|
||||
return created!!.toLong() * 1000L
|
||||
}
|
||||
return 0L
|
||||
}
|
||||
|
||||
override fun category(): RssDataType {
|
||||
return if (nsfw || description()?.contains("nsfw") == true) {
|
||||
RssDataType.REDDIT_NSFW
|
||||
} else RssDataType.REDDIT
|
||||
}
|
||||
override fun getCho(): String? {
|
||||
return JamoUtils.split(title!!).joinToString("")
|
||||
}
|
||||
}
|
||||
|
||||
class Gif {
|
||||
var source: Source? = null
|
||||
var resolutions: ArrayList<Resolution>? = null
|
||||
}
|
||||
|
||||
class Gildings
|
||||
|
||||
class Image {
|
||||
var source: Source? = null
|
||||
var resolutions: ArrayList<Resolution>? = null
|
||||
var variants: Variants? = null
|
||||
var id: String? = null
|
||||
}
|
||||
|
||||
class Media {
|
||||
var type: String? = null
|
||||
var oembed: Oembed? = null
|
||||
}
|
||||
|
||||
class MediaEmbed {
|
||||
var content: String? = null
|
||||
var width: Int = 0
|
||||
var scrolling: Boolean = false
|
||||
var height: Int = 0
|
||||
}
|
||||
|
||||
class Mp4 {
|
||||
var source: Source? = null
|
||||
var resolutions: ArrayList<Resolution>? = null
|
||||
}
|
||||
|
||||
class Nsfw {
|
||||
var source: Source? = null
|
||||
var resolutions: ArrayList<Resolution>? = null
|
||||
}
|
||||
|
||||
class Obfuscated {
|
||||
var source: Source? = null
|
||||
var resolutions: ArrayList<Resolution>? = null
|
||||
}
|
||||
|
||||
class Oembed {
|
||||
var provider_url: String? = null
|
||||
var version: String? = null
|
||||
var title: String? = null
|
||||
var thumbnail_width: Int = 0
|
||||
var height: Int = 0
|
||||
var width: Int = 0
|
||||
var html: String? = null
|
||||
var provider_name: String? = null
|
||||
var thumbnail_url: String? = null
|
||||
var type: String? = null
|
||||
var thumbnail_height: Int = 0
|
||||
}
|
||||
|
||||
class Preview {
|
||||
var images: ArrayList<Image>? = null
|
||||
var reddit_video_preview: RedditVideoPreview? = null
|
||||
var enabled: Boolean = false
|
||||
}
|
||||
|
||||
class RedditVideoPreview {
|
||||
var bitrate_kbps: Int = 0
|
||||
var fallback_url: String? = null
|
||||
var height: Int = 0
|
||||
var width: Int = 0
|
||||
var scrubber_media_url: String? = null
|
||||
var dash_url: String? = null
|
||||
var duration: Int = 0
|
||||
var hls_url: String? = null
|
||||
var is_gif: Boolean = false
|
||||
var transcoding_status: String? = null
|
||||
}
|
||||
|
||||
class Resolution {
|
||||
var url: String? = null
|
||||
var width: Int = 0
|
||||
var height: Int = 0
|
||||
}
|
||||
|
||||
class Reddit {
|
||||
var kind: String? = null
|
||||
var data: Data? = null
|
||||
}
|
||||
|
||||
class SecureMedia {
|
||||
var type: String? = null
|
||||
var oembed: Oembed? = null
|
||||
}
|
||||
|
||||
class SecureMediaEmbed {
|
||||
var content: String? = null
|
||||
var width: Int = 0
|
||||
var scrolling: Boolean = false
|
||||
var media_domain_url: String? = null
|
||||
var height: Int = 0
|
||||
}
|
||||
|
||||
class Source {
|
||||
var url: String? = null
|
||||
var width: Int = 0
|
||||
var height: Int = 0
|
||||
}
|
||||
|
||||
class Variants {
|
||||
var obfuscated: Obfuscated? = null
|
||||
var nsfw: Nsfw? = null
|
||||
var gif: Gif? = null
|
||||
var mp4: Mp4? = null
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* 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.qaccess
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.BlendMode
|
||||
import android.graphics.BlendModeColorFilter
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.Typeface
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.PowerManager
|
||||
import android.provider.Settings
|
||||
import android.text.InputType
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.appcompat.widget.LinearLayoutCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.QuickAccessBinding
|
||||
import bums.lunatic.launcher.databinding.ShortcutMakerBinding
|
||||
import bums.lunatic.launcher.helpers.ColorPicker
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.DEFAULT_ICON_SIZE
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_ICON_SIZE
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_SHORTCUT_COUNT
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_SHORTCUT_NO_
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.MAX_SHORTCUTS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SHORTCUTS
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.SEPARATOR
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.SHORTCUT_TYPE_PHONE
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.SHORTCUT_TYPE_URL
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import java.util.Objects
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
|
||||
internal class QuickAccess : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: QuickAccessBinding
|
||||
private lateinit var sharedPreferences: SharedPreferences
|
||||
private var iconSize by Delegates.notNull<Int>()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = QuickAccessBinding.inflate(inflater, container, false)
|
||||
|
||||
sharedPreferences = requireContext().getSharedPreferences(PREFS_SHORTCUTS, 0)
|
||||
iconSize = requireContext().getSharedPreferences(PREFS_SETTINGS, 0).getInt(KEY_ICON_SIZE, DEFAULT_ICON_SIZE)
|
||||
|
||||
/* set up volume sliders, brightness slider and favorite apps */
|
||||
volumeControllers()
|
||||
controlBrightness()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
/* enable dismiss animation */
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
/* repopulate shortcuts and apps */
|
||||
shortcuts()
|
||||
}
|
||||
|
||||
/* control the volumes */
|
||||
private fun volumeControllers() {
|
||||
val audioManager = lActivity!!.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
/* max value */
|
||||
binding.notification.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION).toFloat()
|
||||
binding.alarm.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM).toFloat()
|
||||
binding.media.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC).toFloat()
|
||||
binding.voice.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL).toFloat()
|
||||
binding.ring.valueTo = audioManager.getStreamMaxVolume(AudioManager.STREAM_RING).toFloat()
|
||||
/* current value */
|
||||
binding.notification.value = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION).toFloat()
|
||||
binding.alarm.value = audioManager.getStreamVolume(AudioManager.STREAM_ALARM).toFloat()
|
||||
binding.media.value = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat()
|
||||
binding.voice.value = audioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL).toFloat()
|
||||
binding.ring.value = audioManager.getStreamVolume(AudioManager.STREAM_RING).toFloat()
|
||||
|
||||
/* slider change listener for alarm volume */
|
||||
binding.alarm.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_ALARM, value.toInt(), 0)
|
||||
})
|
||||
|
||||
/* slider change listener for media volume */
|
||||
binding.media.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, value.toInt(), 0)
|
||||
})
|
||||
|
||||
/* slider change listener for voice call volume */
|
||||
binding.voice.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL, value.toInt(), 0)
|
||||
})
|
||||
|
||||
/* notify and ring volume sliders will work only if
|
||||
the device isn't in dnd or silent mode */
|
||||
if (Settings.Global.getInt(lActivity!!.contentResolver, "zen_mode") == 0 &&
|
||||
audioManager.ringerMode != AudioManager.RINGER_MODE_SILENT) {
|
||||
/* slider change listener for notify volume */
|
||||
binding.notification.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, value.toInt(), 0)
|
||||
})
|
||||
/* slider change listener for ring volume */
|
||||
binding.ring.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_RING, value.toInt(), 0)
|
||||
})
|
||||
} else {
|
||||
binding.notification.isEnabled = false
|
||||
binding.ring.isEnabled = false
|
||||
}
|
||||
}
|
||||
|
||||
/* set up contact and url shortcuts */
|
||||
private fun shortcuts() {
|
||||
binding.shortcutsGroup.removeAllViews()
|
||||
val shortcutCount =
|
||||
requireContext().getSharedPreferences(PREFS_SETTINGS, 0).getInt(KEY_SHORTCUT_COUNT, MAX_SHORTCUTS)
|
||||
if (shortcutCount == 0) binding.shortcutsGroup.visibility = View.GONE
|
||||
|
||||
for (position in 1..shortcutCount) {
|
||||
val shortcutValue = sharedPreferences.getString(KEY_SHORTCUT_NO_ + position.toString(), "").toString()
|
||||
val splitShortcutValue = shortcutValue.split(SEPARATOR).toTypedArray()
|
||||
|
||||
var shortcutType = ""
|
||||
var intentString = ""
|
||||
var thumbLetter = ""
|
||||
var color = ""
|
||||
|
||||
try {
|
||||
if (splitShortcutValue.size >= 4) {
|
||||
shortcutType = splitShortcutValue[0]
|
||||
intentString = splitShortcutValue[1]
|
||||
thumbLetter = splitShortcutValue[2]
|
||||
color = splitShortcutValue[3]
|
||||
}
|
||||
} catch (exception : Exception) {
|
||||
exception.printStackTrace()
|
||||
}
|
||||
|
||||
shortcutsUtil(textView, shortcutType, intentString, thumbLetter, color, position)
|
||||
}
|
||||
}
|
||||
|
||||
/* control the brightness */
|
||||
private fun controlBrightness() {
|
||||
val resolver = lActivity!!.contentResolver
|
||||
/* set max value */
|
||||
binding.brightness.valueTo = maxBrightness
|
||||
|
||||
/* set slider value to current brightness value */
|
||||
try {
|
||||
binding.brightness.value = Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS).toFloat()
|
||||
} catch (settingNotFoundException: Settings.SettingNotFoundException) {
|
||||
settingNotFoundException.printStackTrace()
|
||||
}
|
||||
|
||||
/* listen slider value changes */
|
||||
binding.brightness.addOnChangeListener(Slider.OnChangeListener { _: Slider?, value: Float, _: Boolean ->
|
||||
/* if write settings permission is not allowed already,
|
||||
again ask for it to be granted */
|
||||
if (!Settings.System.canWrite(lActivity!!)) {
|
||||
lActivity!!.startActivity(
|
||||
Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
|
||||
.setData(Uri.parse("package:" + lActivity!!.packageName))
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
/* set the brightness according to the slider value */
|
||||
} else {
|
||||
Settings.System.putInt(
|
||||
resolver,
|
||||
Settings.System.SCREEN_BRIGHTNESS_MODE,
|
||||
Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL
|
||||
)
|
||||
Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS, value.toInt())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* contact/url shortcuts */
|
||||
private fun shortcutsUtil(textView: MaterialTextView, shortcutType: String, intentString: String,
|
||||
thumbLetter: String, color: String, position: Int) {
|
||||
/* show plus sign for empty positions and set click listener */
|
||||
if (intentString.isEmpty()) {
|
||||
textView.text = "+"
|
||||
textView.setOnClickListener {
|
||||
shortcutsSaverDialog(position, "00000000", "", "", "")
|
||||
}
|
||||
} else {
|
||||
/* show thumbnail letter */
|
||||
textView.text = thumbLetter
|
||||
/* set background color */
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
textView.background.colorFilter =
|
||||
BlendModeColorFilter(Color.parseColor("#$color"), BlendMode.MULTIPLY)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
textView.background.setColorFilter(Color.parseColor("#$color"), PorterDuff.Mode.MULTIPLY)
|
||||
}
|
||||
|
||||
/* on normal click */
|
||||
textView.setOnClickListener {
|
||||
/* type is url */
|
||||
if (shortcutType == SHORTCUT_TYPE_URL) {
|
||||
var url = intentString
|
||||
/* add http before the url if it doesn't have http/https prefix */
|
||||
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||
url = "http://$intentString"
|
||||
}
|
||||
/* open the url */
|
||||
lActivity!!.startActivity(
|
||||
Intent(Intent.ACTION_VIEW, Uri.parse(url)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
/* type is contact */
|
||||
} else if (shortcutType == SHORTCUT_TYPE_PHONE) {
|
||||
/* if the necessary permission is not granted already,
|
||||
ask for it again */
|
||||
if (lActivity!!.checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
|
||||
lActivity!!.requestPermissions(arrayOf(Manifest.permission.CALL_PHONE), 1)
|
||||
} else {
|
||||
/* make phone call */
|
||||
lActivity!!.startActivity(
|
||||
Intent(Intent.ACTION_DIAL, Uri.parse("tel:$intentString"))
|
||||
)
|
||||
}
|
||||
}
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/* reset the shortcut on long click */
|
||||
textView.setOnLongClickListener {
|
||||
shortcutsSaverDialog(position, color, thumbLetter, shortcutType, intentString)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* dialog for creating shortcuts */
|
||||
private fun shortcutsSaverDialog(
|
||||
position: Int, color: String, thumbLetter: String, shortcutType: String, intentString: String) {
|
||||
val dialogBinding = ShortcutMakerBinding.inflate(lActivity!!.layoutInflater)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(lActivity!!)
|
||||
.setView(dialogBinding.root)
|
||||
.setNeutralButton(R.string.delete, null)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
|
||||
dialogBinding.thumbField.setText(thumbLetter)
|
||||
dialogBinding.inputField.setText(intentString)
|
||||
when (shortcutType) {
|
||||
SHORTCUT_TYPE_PHONE -> dialogBinding.shortcutType.check(dialogBinding.contact.id)
|
||||
SHORTCUT_TYPE_URL -> dialogBinding.shortcutType.check(dialogBinding.url.id)
|
||||
}
|
||||
|
||||
/* set up color picker section */
|
||||
ColorPicker(color, dialogBinding.colorPicker.colorInput, dialogBinding.colorPicker.colorA,
|
||||
dialogBinding.colorPicker.colorR, dialogBinding.colorPicker.colorG,
|
||||
dialogBinding.colorPicker.colorB, dialogBinding.root).pickColor()
|
||||
|
||||
/* shortcut type chooser - contact/url */
|
||||
var updatedShortcutType = shortcutType
|
||||
dialogBinding.shortcutType.addOnButtonCheckedListener {
|
||||
_: MaterialButtonToggleGroup?, checkedId: Int, isChecked: Boolean ->
|
||||
if (isChecked) {
|
||||
when (checkedId) {
|
||||
dialogBinding.contact.id -> {
|
||||
updatedShortcutType = SHORTCUT_TYPE_PHONE
|
||||
dialogBinding.inputField.inputType = InputType.TYPE_CLASS_PHONE
|
||||
}
|
||||
dialogBinding.url.id -> {
|
||||
updatedShortcutType = SHORTCUT_TYPE_URL
|
||||
dialogBinding.inputField.inputType = InputType.TYPE_TEXT_VARIATION_URI
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialogBuilder.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {
|
||||
sharedPreferences.edit().remove(KEY_SHORTCUT_NO_ + position).apply()
|
||||
dialogBuilder.dismiss()
|
||||
this.onResume()
|
||||
}
|
||||
|
||||
/* save the shortcut values */
|
||||
dialogBuilder.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
/* get shortcut value */
|
||||
val updatedIntentString =
|
||||
Objects.requireNonNull(dialogBinding.inputField.text).toString().trim { it <= ' ' }
|
||||
/* get thumbnail letter */
|
||||
val updatedThumbLetter =
|
||||
Objects.requireNonNull(dialogBinding.thumbField.text).toString().trim { it <= ' ' }.uppercase()
|
||||
/* get color value */
|
||||
val updatedColor =
|
||||
Objects.requireNonNull(dialogBinding.colorPicker.colorInput.text).toString().trim { it <= ' ' }
|
||||
|
||||
/* save the values if every field is filled */
|
||||
if (updatedShortcutType.isNotEmpty() && updatedIntentString.isNotEmpty() &&
|
||||
updatedThumbLetter.isNotEmpty() && updatedColor.isNotEmpty()) {
|
||||
sharedPreferences.edit().putString(KEY_SHORTCUT_NO_ + position,
|
||||
"$updatedShortcutType$SEPARATOR$updatedIntentString$SEPARATOR" +
|
||||
"$updatedThumbLetter$SEPARATOR$updatedColor").apply()
|
||||
dialogBuilder.dismiss()
|
||||
this.onResume()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* create text view for shortcut thumbnails */
|
||||
private val textView: MaterialTextView get() {
|
||||
val relativeLayout = RelativeLayout(lActivity!!)
|
||||
relativeLayout.apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
LinearLayoutCompat.LayoutParams.WRAP_CONTENT,
|
||||
LinearLayoutCompat.LayoutParams.WRAP_CONTENT, 1F)
|
||||
gravity = Gravity.CENTER
|
||||
}
|
||||
binding.shortcutsGroup.addView(relativeLayout)
|
||||
|
||||
MaterialTextView(requireContext()).apply {
|
||||
layoutParams = LinearLayoutCompat.LayoutParams(
|
||||
(iconSize * resources.displayMetrics.density).toInt(),
|
||||
(iconSize * resources.displayMetrics.density).toInt())
|
||||
gravity = Gravity.CENTER
|
||||
textSize = (iconSize / 4) * resources.displayMetrics.density
|
||||
setTypeface(null, Typeface.BOLD)
|
||||
background = ContextCompat.getDrawable(requireContext(), R.drawable.rounded_bg)
|
||||
}.let {
|
||||
relativeLayout.addView(it)
|
||||
return it
|
||||
}
|
||||
}
|
||||
|
||||
/* returns maximum brightness value of the device */
|
||||
private val maxBrightness: Float get() {
|
||||
val powerManager = requireContext().getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
var value = 255f
|
||||
for (f in powerManager.javaClass.declaredFields) {
|
||||
if (f.name.equals("BRIGHTNESS_ON")) {
|
||||
f.isAccessible = true
|
||||
value = try {
|
||||
f.getInt(powerManager).toFloat()
|
||||
} catch (e: IllegalAccessException) {
|
||||
255f
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package bums.lunatic.launcher.receiver
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.location.Location
|
||||
import android.media.MediaMetadata
|
||||
import android.media.session.MediaSessionManager
|
||||
import android.os.Build
|
||||
import android.service.notification.NotificationListenerService
|
||||
import android.service.notification.StatusBarNotification
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.getSystemService
|
||||
import bums.lunatic.launcher.model.CurrentPlayItem
|
||||
import bums.lunatic.launcher.model.NotificationItem
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.utils.BitmapConverter
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.gms.tasks.OnSuccessListener
|
||||
import io.realm.kotlin.UpdatePolicy
|
||||
import io.realm.kotlin.ext.query
|
||||
|
||||
|
||||
class NLService : NotificationListenerService() {
|
||||
private val TAG: String = this.javaClass.simpleName
|
||||
private var nlservicereciver: NLServiceReceiver? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
nlservicereciver = NLServiceReceiver()
|
||||
val filter = IntentFilter()
|
||||
// filter.addAction("com.kpbird.nlsexample.NOTIFICATION_LISTENER_SERVICE_EXAMPLE")
|
||||
registerReceiver(nlservicereciver, filter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
unregisterReceiver(nlservicereciver)
|
||||
}
|
||||
|
||||
val skips = arrayListOf("com.wssyncmldm")
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
override fun onNotificationPosted(sbn: StatusBarNotification) {
|
||||
// BLog.LOGE("NLService********** onNotificationPosted")
|
||||
// BLog.LOGE("NLServiceID :" + sbn.id + "\t${sbn.notification.tickerText}\t" + sbn.packageName)
|
||||
// sbn.notification.extras.keySet().forEach {
|
||||
// BLog.LOGE("NLService********** keySet >> ${it} ${sbn.notification.extras.get(it)}")
|
||||
// }
|
||||
if (sbn.id != 0 && (sbn.packageName.contains(".") || sbn.packageName.contains("android")) && sbn.packageName.length > 0) {
|
||||
NotificationItem().apply {
|
||||
notiId = sbn.id
|
||||
pkgName = sbn.packageName
|
||||
title = sbn.notification?.extras?.getString("android.title") ?: ""
|
||||
subtext = sbn.notification?.extras?.getString("android.subText") ?: ""
|
||||
selfDisplayName = sbn.notification?.extras?.getString("android.selfDisplayName") ?: ""
|
||||
tikerMsg = sbn.notification?.tickerText?.toString() ?: ""
|
||||
postTime = sbn.postTime
|
||||
var uniq = title ?: subtext ?: selfDisplayName ?: tikerMsg ?: ""
|
||||
uniq_id = "${sbn.id}_${sbn.packageName}_${if (uniq.length > 3) uniq.substring(0,3) else uniq}"
|
||||
// BLog.LOGE("NLService********** enqueue TelegramBotGetter ${true == "bumssavor".equals(title)}")
|
||||
// BLog.LOGE("NLService********** enqueue TelegramBotGetter ${(true == "org.telegram.messenger".equals(pkgName))}")
|
||||
// BLog.LOGE("NLService********** enqueue TelegramBotGetter ${sbn.notification?.extras?.getString("android.text")?.startsWith("/") == true}")
|
||||
}.apply {
|
||||
if (skips.contains(pkgName)) {
|
||||
|
||||
} else {
|
||||
WorkersDb.insertNoti(this)
|
||||
// BLog.LOGE("NLService********** onNotificationPosted ${Gson().toJson(this)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (sbn.packageName.contains("youtube")) {
|
||||
val m = getSystemService<MediaSessionManager>()!!
|
||||
val component = ComponentName(this, NLService::class.java)
|
||||
val sessions = m.getActiveSessions(component)
|
||||
// BLog.LOGE("Sessions", "count: ${sessions.size}")
|
||||
sessions.forEach { session ->
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
if (session.playbackState?.isActive == true) {
|
||||
val result = query<CurrentPlayItem>().find()
|
||||
var current : CurrentPlayItem? = null
|
||||
if (result.size > 0) {
|
||||
current = result.first()
|
||||
} else {
|
||||
current = CurrentPlayItem()
|
||||
copyToRealm(current, UpdatePolicy.ALL)
|
||||
}
|
||||
// BLog.LOGE(
|
||||
// "Sessions",
|
||||
// "$session -- " + (session.playbackState?.state)
|
||||
// )
|
||||
// BLog.LOGE(
|
||||
// "Sessions",
|
||||
// "$session -- " + (session?.metadata?.keySet()?.joinToString())
|
||||
// )
|
||||
// BLog.LOGE(
|
||||
// "Sessions",
|
||||
// "$session -- " + (session?.metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST))
|
||||
// )
|
||||
if (session?.metadata?.containsKey(MediaMetadata.METADATA_KEY_ALBUM_ART) == true) {
|
||||
// BLog.LOGE(
|
||||
// "Sessions",
|
||||
// "$session -- " + (session?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART))
|
||||
// )
|
||||
current.albumArt = BitmapConverter.BitmapToString(
|
||||
session.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
|
||||
)
|
||||
} else {
|
||||
current.albumArt = ""
|
||||
}
|
||||
BLog.LOGE(
|
||||
"Sessions",
|
||||
"$session -- " + (session?.metadata?.getString(MediaMetadata.METADATA_KEY_TITLE))
|
||||
)
|
||||
current.title = session?.metadata?.getString(MediaMetadata.METADATA_KEY_TITLE)
|
||||
current.artists = session?.metadata?.getString(MediaMetadata.METADATA_KEY_ARTIST)
|
||||
|
||||
|
||||
} else {
|
||||
delete(query<CurrentPlayItem>().find())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
// val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i.putExtra("notification_event", "onNotificationPosted :" + sbn.packageName + "\n")
|
||||
// sendBroadcast(i)
|
||||
}
|
||||
|
||||
override fun onNotificationRemoved(sbn: StatusBarNotification) {
|
||||
// BLog.LOGE("NLService********** onNOtificationRemoved")
|
||||
// BLog.LOGE("NLService ID :" + sbn.id + "\t" + sbn.notification.tickerText + "\t" + sbn.packageName)
|
||||
var uniq_id = "${sbn.id}_${sbn.packageName}"
|
||||
try {
|
||||
WorkersDb.getRealm()?.apply {
|
||||
this.writeBlocking {
|
||||
// delete(query<NotificationItem>().query("pkgName == $0", sbn.packageName).find())
|
||||
}
|
||||
}
|
||||
}catch (e : Exception){e.printStackTrace()}
|
||||
// val i = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i.putExtra("notification_event", "onNotificationRemoved :" + sbn.packageName + "\n")
|
||||
// sendBroadcast(i)
|
||||
}
|
||||
|
||||
internal inner class NLServiceReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
// BLog.LOGE("NLService intent >>> ${intent.action}")
|
||||
if (intent.getStringExtra("command") == "clearall") {
|
||||
this@NLService.cancelAllNotifications()
|
||||
} else if (intent.getStringExtra("command") == "list") {
|
||||
// val i1 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i1.putExtra("notification_event", "=====================")
|
||||
// sendBroadcast(i1)
|
||||
var i = 1
|
||||
for (sbn in this@NLService.activeNotifications) {
|
||||
// BLog.LOGE("NLService sbn >>> ${sbn.packageName} , ${Gson().toJson(sbn.notification.extras.keySet())}")
|
||||
// val i2 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i2.putExtra("notification_event", i.toString() + " " + sbn.packageName + "\n")
|
||||
// sendBroadcast(i2)
|
||||
// i++
|
||||
}
|
||||
// val i3 = Intent("com.kpbird.nlsexample.NOTIFICATION_LISTENER_EXAMPLE")
|
||||
// i3.putExtra("notification_event", "===== Notification List ====")
|
||||
// sendBroadcast(i3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fusedLocationProviderClient: FusedLocationProviderClient? = null
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun getLastLocation(context: Context) {
|
||||
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
|
||||
// BLog.LOGE("Location getLastLocation")
|
||||
fusedLocationProviderClient?.getLastLocation()?.addOnSuccessListener(object :
|
||||
OnSuccessListener<Location?> {
|
||||
override fun onSuccess(location: Location?) {
|
||||
// if (location != null) {
|
||||
// // Log the latitude and longitude
|
||||
// BLog.LOGE("Location Latitude: " + location.getLatitude())
|
||||
// BLog.LOGE("Location Longitude: " + location.getLongitude())
|
||||
//
|
||||
// // Use Geocoder to get detailed location information
|
||||
// try {
|
||||
// val geocoder = Geocoder(context, Locale.getDefault())
|
||||
// val addresses: List<Address>? = geocoder.getFromLocation(
|
||||
// location.getLatitude(),
|
||||
// location.getLongitude(),
|
||||
// 1
|
||||
// )
|
||||
//
|
||||
// addresses?.first()?.let {
|
||||
// it.getAddressLine(0)?.let {
|
||||
// Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
// try {
|
||||
// //////-1002450229641
|
||||
// val url =
|
||||
// "https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/sendMessage?chat_id=83268260&text=남편의현위치는${it}"
|
||||
// //7068729507
|
||||
// // OkHttp 클라이언트 객체 생성
|
||||
// val client = OkHttpClient.Builder()
|
||||
// .connectionPool(ConnectionPool(5, 60, TimeUnit.SECONDS))
|
||||
// .build()
|
||||
//
|
||||
// // GET 요청 객체 생성
|
||||
// val builder: Request.Builder = Request.Builder().url(url)
|
||||
// .addHeader("Content-Type", "application/json").get()
|
||||
//
|
||||
// val request: Request = builder.build()
|
||||
//
|
||||
// BLog.LOGE("telegram before request ")
|
||||
// // OkHttp 클라이언트로 GET 요청 객체 전송
|
||||
// val response: Response = client.newCall(request).execute()
|
||||
// if (response.isSuccessful()) {
|
||||
// // 응답 받아서 처리
|
||||
// val body: ResponseBody? = response.body()
|
||||
// if (body != null) {
|
||||
//
|
||||
// }
|
||||
// } else BLog.LOGE("telegram Error Occurred")
|
||||
//
|
||||
// } catch (e: java.lang.Exception) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// }, 5, TimeUnit.SECONDS)
|
||||
// }
|
||||
// }
|
||||
// // Display location details on UI elements
|
||||
// // Log detailed location information
|
||||
// BLog.LOGE("Location Addresses: $addresses")
|
||||
// } catch (e: IOException) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package bums.lunatic.launcher.receiver
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.workers.AppInfoGetter
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import io.realm.kotlin.ext.query
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class PackageEventReceiver : BroadcastReceiver() {
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val action = intent.action
|
||||
|
||||
BLog.LOGE("action >>>> ${action}")
|
||||
when(action) {
|
||||
Intent.ACTION_PACKAGE_ADDED,Intent.ACTION_PACKAGE_INSTALL -> {
|
||||
startAppInfoGetter(context)
|
||||
}
|
||||
Intent.ACTION_PACKAGE_REMOVED -> {
|
||||
val packageName = intent.data?.schemeSpecificPart
|
||||
if (packageName?.length ?: 0 > 0) {
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
val result = query<AppInfo>("pkgName == $0", packageName).find()
|
||||
if (result.size == 1) {
|
||||
delete(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
startAppInfoGetter(context)
|
||||
}
|
||||
else -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun startAppInfoGetter(context: Context) {
|
||||
var mWorkManager = WorkManager.getInstance(context)
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
mWorkManager.enqueue(OneTimeWorkRequest.from(AppInfoGetter::class.java))
|
||||
}, 5, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* 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.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.AboutBinding
|
||||
import bums.lunatic.launcher.databinding.SettingsActivityBinding
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.BOTTOM_SHEET_TAG
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.PREFS_SETTINGS
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
import bums.lunatic.launcher.settings.childs.Advance
|
||||
import bums.lunatic.launcher.settings.childs.Appearances
|
||||
import bums.lunatic.launcher.settings.childs.Apps
|
||||
import bums.lunatic.launcher.settings.childs.HomeSettings
|
||||
import bums.lunatic.launcher.settings.childs.Misc
|
||||
import bums.lunatic.launcher.settings.childs.TimeDate
|
||||
import bums.lunatic.launcher.settings.childs.WeatherSettings
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
|
||||
internal class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: SettingsActivityBinding
|
||||
private val sourceCode = "https://github.com/iamrasel/lunar-launcher"
|
||||
|
||||
companion object {
|
||||
@JvmStatic var settingsPrefs: SharedPreferences? = null
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
DynamicColors.applyToActivityIfAvailable(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
/* set up view */
|
||||
binding = SettingsActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
settingsPrefs = this.getSharedPreferences(PREFS_SETTINGS, 0)
|
||||
|
||||
/* launch child settings dialogs on button clicks */
|
||||
binding.timeDate.setOnClickListener {
|
||||
TimeDate().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
binding.weather.setOnClickListener {
|
||||
WeatherSettings().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
binding.todo.setOnClickListener {
|
||||
HomeSettings().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
binding.apps.setOnClickListener {
|
||||
Apps().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
binding.appearances.setOnClickListener {
|
||||
Appearances().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
binding.misc.setOnClickListener {
|
||||
Misc().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
binding.advance.setOnClickListener {
|
||||
Advance().show(supportFragmentManager, BOTTOM_SHEET_TAG)
|
||||
}
|
||||
|
||||
/* about and support dialogs */
|
||||
binding.about.setOnClickListener { aboutDialog() }
|
||||
binding.support.setOnClickListener { supportDialog() }
|
||||
|
||||
|
||||
|
||||
/* show app version name */
|
||||
binding.version.text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})${if(PrefBoolean.rootPermisssion.get(false)) "[Root]" else ""}"
|
||||
|
||||
binding.version.setOnClickListener {
|
||||
binding.version.removeCallbacks(cancelCount)
|
||||
clickCount += 1
|
||||
binding.version.postDelayed(cancelCount, 2000L)
|
||||
if (clickCount > 5) {
|
||||
PrefHelper.putBoolean("rootPermisssion",!PrefBoolean.rootPermisssion.get(false))
|
||||
binding.version.text = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})${if(PrefBoolean.rootPermisssion.get(false)) "[Root]" else ""}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cancelCount = Runnable{
|
||||
clickCount = 0
|
||||
}
|
||||
|
||||
|
||||
var clickCount = 0
|
||||
|
||||
override fun getTheme(): Resources.Theme {
|
||||
val theme = super.getTheme()
|
||||
theme.applyStyle(R.style.SettingsNavBar, true)
|
||||
return theme
|
||||
}
|
||||
|
||||
/* about dialog */
|
||||
private fun aboutDialog() {
|
||||
val bottomSheetDialog = BottomSheetDialog(this)
|
||||
val aboutBinding = AboutBinding.inflate(this.layoutInflater)
|
||||
bottomSheetDialog.setContentView(aboutBinding.root)
|
||||
bottomSheetDialog.show()
|
||||
bottomSheetDialog.dismissWithAnimation = true
|
||||
|
||||
/* source code at github */
|
||||
aboutBinding.sourceCode.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(sourceCode)))
|
||||
}
|
||||
/* wiki at github */
|
||||
aboutBinding.wiki.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("$sourceCode/wiki")))
|
||||
}
|
||||
/* telegram community */
|
||||
aboutBinding.telegramGroup.setOnClickListener {
|
||||
bottomSheetDialog.dismiss()
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://t.me/LunarLauncher_chats")))
|
||||
}
|
||||
}
|
||||
|
||||
/* support dialog */
|
||||
private fun supportDialog() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.support)
|
||||
.setMessage(R.string.support_message)
|
||||
/* star button */
|
||||
.setNeutralButton(R.string.star) { _, _ ->
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(sourceCode)))
|
||||
}
|
||||
/* affiliate button */
|
||||
.setNegativeButton(R.string.amazon) { _, _ ->
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://amzn.to/44krAw9")))
|
||||
}
|
||||
/* donate button */
|
||||
.setPositiveButton(R.string.donate) { _, _ ->
|
||||
// startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://iamrasel.github.io/donate")))
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.SettingsAdvanceBinding
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
internal class Advance : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding : SettingsAdvanceBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsAdvanceBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
/* open Default Home App screen from device settings */
|
||||
binding.chooseLauncher.setOnClickListener {
|
||||
requireContext().startActivity(Intent(Settings.ACTION_HOME_SETTINGS))
|
||||
this.dismiss()
|
||||
}
|
||||
|
||||
/* reset and restart button click listeners */
|
||||
binding.reset.setOnClickListener { reset() }
|
||||
binding.restart.setOnClickListener { exitProcess(0) }
|
||||
}
|
||||
|
||||
/* reset app data */
|
||||
private fun reset() {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.reset)
|
||||
.setMessage(R.string.reset_message)
|
||||
.setPositiveButton(R.string.proceed) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
Runtime.getRuntime().exec("pm clear " + requireContext().packageName)
|
||||
}
|
||||
.setNeutralButton(android.R.string.cancel) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
import android.Manifest.permission.READ_MEDIA_IMAGES
|
||||
import android.app.Activity.RESULT_OK
|
||||
import android.app.AlertDialog
|
||||
import android.app.WallpaperManager
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.MediaStore
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
|
||||
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.ColorPickerBinding
|
||||
import bums.lunatic.launcher.databinding.SettingsAppearancesBinding
|
||||
import bums.lunatic.launcher.helpers.ColorPicker
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_APPLICATION_THEME
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_STATUS_BAR
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_WINDOW_BACKGROUND
|
||||
import bums.lunatic.launcher.helpers.UniUtils.Companion.getColorResId
|
||||
import bums.lunatic.launcher.settings.SettingsActivity.Companion.settingsPrefs
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.io.IOException
|
||||
import java.util.Objects
|
||||
|
||||
|
||||
internal class Appearances : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding : SettingsAppearancesBinding
|
||||
private lateinit var windowBackground : String
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsAppearancesBinding.inflate(inflater, container, false)
|
||||
|
||||
/* initialize views according to the saved values */
|
||||
when (settingsPrefs!!.getInt(KEY_APPLICATION_THEME, MODE_NIGHT_FOLLOW_SYSTEM)) {
|
||||
MODE_NIGHT_FOLLOW_SYSTEM -> binding.followSystemTheme.isChecked = true
|
||||
MODE_NIGHT_YES -> binding.selectDarkTheme.isChecked = true
|
||||
MODE_NIGHT_NO -> binding.selectLightTheme.isChecked = true
|
||||
}
|
||||
|
||||
when (settingsPrefs!!.getBoolean(KEY_STATUS_BAR, false)) {
|
||||
false -> binding.hideStatusNegative.isChecked = true
|
||||
true -> binding.hideStatusPositive.isChecked = true
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
/* change theme */
|
||||
binding.themeGroup.setOnCheckedStateChangeListener { group, _ ->
|
||||
when (group.checkedChipId) {
|
||||
binding.followSystemTheme.id -> {
|
||||
settingsPrefs!!.edit().putInt(KEY_APPLICATION_THEME, MODE_NIGHT_FOLLOW_SYSTEM).apply()
|
||||
AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_FOLLOW_SYSTEM)
|
||||
}
|
||||
binding.selectDarkTheme.id -> {
|
||||
settingsPrefs!!.edit().putInt(KEY_APPLICATION_THEME, MODE_NIGHT_YES).apply()
|
||||
AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_YES)
|
||||
}
|
||||
binding.selectLightTheme.id -> {
|
||||
settingsPrefs!!.edit().putInt(KEY_APPLICATION_THEME, MODE_NIGHT_NO).apply()
|
||||
AppCompatDelegate.setDefaultNightMode(MODE_NIGHT_NO)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding.background.setOnClickListener { selectBackground() }
|
||||
binding.changeWallpaper.setOnClickListener { selectWallpaper() }
|
||||
|
||||
binding.hideStatusGroup.setOnCheckedStateChangeListener { group, _ ->
|
||||
when (group.checkedChipId) {
|
||||
binding.hideStatusNegative.id -> settingsPrefs!!.edit().putBoolean(KEY_STATUS_BAR, false).apply()
|
||||
binding.hideStatusPositive.id -> settingsPrefs!!.edit().putBoolean(KEY_STATUS_BAR, true).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
windowBackground = settingsPrefs!!.getString(KEY_WINDOW_BACKGROUND, defaultColorString).toString()
|
||||
binding.background.iconTint = ColorStateList.valueOf(Color.parseColor("#$windowBackground"))
|
||||
}
|
||||
|
||||
private fun selectBackground() {
|
||||
val colorPickerBinding = ColorPickerBinding.inflate(requireActivity().layoutInflater)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(requireActivity())
|
||||
.setView(colorPickerBinding.root)
|
||||
.setNeutralButton(R.string.default_, null)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
settingsPrefs!!.edit().putString(KEY_WINDOW_BACKGROUND,
|
||||
Objects.requireNonNull(colorPickerBinding.colorInput.text).toString().trim { it <= ' ' }).apply()
|
||||
this.onResume()
|
||||
}
|
||||
.show()
|
||||
|
||||
/* set up color picker section */
|
||||
ColorPicker(windowBackground, colorPickerBinding.colorInput,
|
||||
colorPickerBinding.colorA, colorPickerBinding.colorR, colorPickerBinding.colorG,
|
||||
colorPickerBinding.colorB, colorPickerBinding.root).pickColor()
|
||||
|
||||
dialogBuilder.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {
|
||||
colorPickerBinding.colorInput.text =
|
||||
SpannableStringBuilder(defaultColorString)
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectWallpaper() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// only for TIRAMISU and newer versions
|
||||
if (requireActivity().checkSelfPermission(READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
|
||||
requireActivity().requestPermissions(arrayOf(READ_MEDIA_IMAGES), 1)
|
||||
} else {
|
||||
wallpaperChangeLauncher.launch(Intent(Intent.ACTION_PICK).setType("image/*"))
|
||||
}
|
||||
} else {
|
||||
if (requireActivity().checkSelfPermission(READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
requireActivity().requestPermissions(arrayOf(READ_EXTERNAL_STORAGE), 1)
|
||||
} else {
|
||||
wallpaperChangeLauncher.launch(Intent(Intent.ACTION_PICK).setType("image/*"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val defaultColorString: String get() =
|
||||
requireActivity().getString(getColorResId(requireContext(), android.R.attr.colorBackground))
|
||||
.replace("#", "")
|
||||
|
||||
private var wallpaperChangeLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
try {
|
||||
val uri = result.data?.data
|
||||
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||
val cursor = requireContext().contentResolver.query(
|
||||
uri!!, projection, null, null, null
|
||||
)
|
||||
cursor?.moveToFirst()
|
||||
val index = cursor!!.getColumnIndex(projection[0])
|
||||
val filePath = cursor.getString(index)
|
||||
cursor.close()
|
||||
val bitmap = BitmapFactory.decodeFile(filePath)
|
||||
val matrix = Matrix()
|
||||
matrix.postRotate(0F)
|
||||
try {
|
||||
if (bitmap != null) {
|
||||
WallpaperManager.getInstance(requireContext()).setBitmap(bitmap)
|
||||
Toast.makeText(requireContext(),
|
||||
requireActivity().getString(R.string.wallpaper_change_success), Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(requireContext(),
|
||||
requireActivity().getString(R.string.image_pick_failed), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
Toast.makeText(requireContext(),
|
||||
requireActivity().getString(R.string.something_went_wrong), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Dialog
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.SettingsAppsBinding
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
import bums.lunatic.launcher.helpers.PrefKey
|
||||
import bums.lunatic.launcher.helpers.PrefLong
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
internal class Apps : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding: SettingsAppsBinding
|
||||
private var settingsChanged: Boolean = false
|
||||
private var packageManager: PackageManager? = null
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val bottomSheetView = View.inflate(context, R.layout.settings_apps, null)
|
||||
val dialog = super.onCreateDialog(savedInstanceState).apply {
|
||||
setContentView(bottomSheetView)
|
||||
}
|
||||
|
||||
// Set the height to wrap_content
|
||||
BottomSheetBehavior.from(bottomSheetView.parent as View)
|
||||
.peekHeight = resources.displayMetrics.heightPixels // Adjust this if needed
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsAppsBinding.inflate(inflater, container, false)
|
||||
packageManager = requireActivity().packageManager
|
||||
|
||||
|
||||
binding.keyboardAutoGroup.isChecked = PrefHelper.openWithKayboard
|
||||
binding.keyboardAutoGroup.setOnCheckedChangeListener { c,v ->
|
||||
settingsChanged = true
|
||||
PrefHelper.openWithKayboard = v
|
||||
}
|
||||
|
||||
binding.quickLaunchGroup.isChecked = PrefHelper.useQuickLaunch
|
||||
binding.quickLaunchGroup.setOnCheckedChangeListener { c,v ->
|
||||
settingsChanged = true
|
||||
PrefHelper.useQuickLaunch = v
|
||||
}
|
||||
|
||||
binding.appsCountGroup.isChecked = PrefHelper.showAppResultCount
|
||||
binding.appsCountGroup.setOnCheckedChangeListener { c,v ->
|
||||
settingsChanged = true
|
||||
PrefHelper.showAppResultCount = v
|
||||
}
|
||||
|
||||
|
||||
((PrefKey.maxQueryCount.get(18L) as? Long)?.toFloat() ?: 18F).let {
|
||||
binding.columnsCountTitle.text = getString(R.string.grid_columns_count) +" [${it.toInt()}]"
|
||||
binding.columnsCount.value = it
|
||||
}
|
||||
|
||||
PrefLong.shortTimePeriod.get(20L).let {
|
||||
binding.shortTimeTitle.text = getString(R.string.shortTimeTitle) +" [${it.toInt()}분 마다]"
|
||||
binding.shortTime.value = it.toFloat()
|
||||
}
|
||||
PrefLong.midTimePeriod.get(30L).let {
|
||||
binding.middleTimeTitle.text = getString(R.string.middleTimeTitle) +" [${it.toInt()}분 마다]"
|
||||
binding.middleTime.value = it.toFloat()
|
||||
}
|
||||
|
||||
PrefLong.longTimePeriod.get(60L).let {
|
||||
binding.longTimeTitle.text = getString(R.string.longTimeTitle) +" [${it.toInt()}분 마다]"
|
||||
binding.longTime.value = it.toFloat()
|
||||
}
|
||||
|
||||
PrefLong.locationTimePeriod.get(20L).let {
|
||||
binding.locationTimeTitle.text = getString(R.string.locationTimeTitle) +" [${it.toInt()}분 마다]"
|
||||
binding.locationTime.value = it.toFloat()
|
||||
}
|
||||
|
||||
PrefLong.locationDistance.get(200L).let {
|
||||
binding.locationDistanceTitle.text = getString(R.string.locationDistanceTitle) +" [${it.toInt()}분 마다]"
|
||||
binding.locationDistance.value = it.toFloat()
|
||||
}
|
||||
|
||||
// binding.scrollbarHeight.value = settingsPrefs!!.getInt(KEY_SCROLLBAR_HEIGHT, DEFAULT_SCROLLBAR_HEIGHT).toFloat()
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("RtlHardcoded")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
|
||||
binding.columnsCount.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
settingsChanged = true
|
||||
PrefKey.maxQueryCount.set(value.toLong())
|
||||
binding.columnsCountTitle.text = getString(R.string.grid_columns_count) +" [${value.toInt()}]"
|
||||
})
|
||||
|
||||
binding.shortTime.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
settingsChanged = true
|
||||
PrefKey.shortTimePeriod.set(value.toLong())
|
||||
binding.shortTimeTitle.text = getString(R.string.shortTimeTitle) +" [${value.toInt()}분 마다]"
|
||||
})
|
||||
binding.middleTime.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
settingsChanged = true
|
||||
PrefKey.midTimePeriod.set(value.toLong())
|
||||
binding.middleTimeTitle.text = getString(R.string.middleTimeTitle) +" [${value.toInt()}분 마다]"
|
||||
})
|
||||
binding.longTime.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
settingsChanged = true
|
||||
PrefKey.longTimePeriod.set(value.toLong())
|
||||
binding.longTimeTitle.text = getString(R.string.longTimeTitle) +" [${value.toInt()}]분 마다]"
|
||||
})
|
||||
|
||||
binding.locationDistance.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
settingsChanged = true
|
||||
PrefLong.locationDistance.set(value.toLong())
|
||||
binding.locationDistanceTitle.text = getString(R.string.locationDistanceTitle) +" [${value.toInt()}]미터 마다]"
|
||||
})
|
||||
binding.locationTime.addOnChangeListener(Slider.OnChangeListener { _, value, _ ->
|
||||
settingsChanged = true
|
||||
PrefLong.locationTimePeriod.set(value.toLong())
|
||||
binding.locationTimeTitle.text = getString(R.string.locationTimeTitle) +" [${value.toInt()}]분 마다]"
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (settingsChanged) {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.restart_now)
|
||||
.setMessage(R.string.restart_message)
|
||||
.setPositiveButton(R.string.restart) { _, _ ->
|
||||
exitProcess(0)
|
||||
}
|
||||
.setNeutralButton(R.string.later, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TableRow
|
||||
import androidx.core.view.children
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.SettingsTodoBinding
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
internal class HomeSettings : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding : SettingsTodoBinding
|
||||
private var settingsChanged: Boolean = false
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsTodoBinding.inflate(inflater, container, false)
|
||||
|
||||
|
||||
setTableItem(binding.admin02)
|
||||
setTableItem(binding.admin01)
|
||||
setTableItem(binding.normal01)
|
||||
setTableItem(binding.normal02)
|
||||
setTableItem(binding.normal03)
|
||||
setTableItem(binding.normal04)
|
||||
|
||||
|
||||
binding.callInfo.isChecked = PrefBoolean.showCallHistory.get(false)
|
||||
binding.callInfo.setOnCheckedChangeListener { buttonView, isChecked -> PrefBoolean.showCallHistory.set(isChecked)
|
||||
settingsChanged = true}
|
||||
binding.smsInfos.isChecked = PrefBoolean.showSMSHistory.get(false)
|
||||
binding.smsInfos.setOnCheckedChangeListener { buttonView, isChecked -> PrefBoolean.showSMSHistory.set(isChecked)
|
||||
settingsChanged = true}
|
||||
binding.notificationInfos.isChecked = PrefBoolean.showNotificationHistory.get(false)
|
||||
binding.notificationInfos.setOnCheckedChangeListener { buttonView, isChecked -> PrefBoolean.showNotificationHistory.set(isChecked)
|
||||
settingsChanged = true}
|
||||
binding.nowPlaying.isChecked = PrefBoolean.showNowPlaying.get(false)
|
||||
binding.nowPlaying.setOnCheckedChangeListener { buttonView, isChecked -> PrefBoolean.showNowPlaying.set(isChecked)
|
||||
settingsChanged = true}
|
||||
|
||||
|
||||
|
||||
if(!PrefHelper.getBoolean("rootPermisssion",false)) {
|
||||
binding.admin01.visibility = View.GONE
|
||||
binding.admin02.visibility = View.GONE
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
var checkdCount = 0
|
||||
set(value) {
|
||||
field = value
|
||||
PrefBoolean.showNewsHistory.set(field > 0)
|
||||
}
|
||||
|
||||
|
||||
fun setTableItem(tableRow : TableRow) {
|
||||
tableRow.children.forEach { (it as? SwitchMaterial)?.let {
|
||||
it.text?.toString()?.toUpperCase()?.let { key ->
|
||||
it.isChecked = PrefHelper.getBoolean(key, false)
|
||||
checkdCount += if (it.isChecked) 1 else -1
|
||||
it.setOnCheckedChangeListener { v , isBool ->
|
||||
PrefHelper.putBoolean(key, isBool)
|
||||
checkdCount += if (isBool) 1 else -1
|
||||
settingsChanged = true
|
||||
}
|
||||
} }
|
||||
}
|
||||
}
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
if (settingsChanged) {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.restart_now)
|
||||
.setMessage(R.string.restart_message)
|
||||
.setPositiveButton(R.string.restart) { _, _ ->
|
||||
exitProcess(0)
|
||||
}
|
||||
.setNeutralButton(R.string.later, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import bums.lunatic.launcher.databinding.SettingsPrivitServiceBinding
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_RSS_URL
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_RSS_URL2
|
||||
import bums.lunatic.launcher.helpers.PrefString
|
||||
import bums.lunatic.launcher.settings.SettingsActivity.Companion.settingsPrefs
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import java.util.Objects
|
||||
|
||||
|
||||
internal class Misc : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding : SettingsPrivitServiceBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsPrivitServiceBinding.inflate(inflater, container, false)
|
||||
|
||||
/* initialize views according to the saved values */
|
||||
|
||||
|
||||
|
||||
binding.inputFeedUrl.setText(PrefString.telegramBotApi.get(""))
|
||||
binding.inputFeedUrl.doOnTextChanged { t,s,b,l -> PrefString.telegramBotApi.set(t.toString())
|
||||
|
||||
}
|
||||
binding.inputFeedUrl2.setText(PrefString.telegramMyId.get(""))
|
||||
binding.inputFeedUrl2.doOnTextChanged { t,s,b,l -> PrefString.telegramMyId.set(t.toString())
|
||||
|
||||
}
|
||||
binding.inputFeedUrl3.setText(PrefString.telegramSendTarget.get(""))
|
||||
binding.inputFeedUrl3.doOnTextChanged { t,s,b,l -> PrefString.telegramSendTarget.set(t.toString())
|
||||
|
||||
}
|
||||
binding.inputFeedUrl4.setText(PrefString.locationApi.get(""))
|
||||
binding.inputFeedUrl4.doOnTextChanged { t,s,b,l -> PrefString.locationApi.set(t.toString())}
|
||||
|
||||
binding.inputFeedUrl5.setText(PrefString.carName.get(""))
|
||||
binding.inputFeedUrl5.doOnTextChanged { t,s,b,l -> PrefString.carName.set(t.toString())}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
}
|
||||
|
||||
/* save input field value while closing the dialog */
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
settingsPrefs!!.edit().putString(KEY_RSS_URL,
|
||||
Objects.requireNonNull(binding.inputFeedUrl.text).toString().trim { it <= ' ' }).apply()
|
||||
settingsPrefs!!.edit().putString(KEY_RSS_URL2,
|
||||
Objects.requireNonNull(binding.inputFeedUrl2.text).toString().trim { it <= ' ' }).apply()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import bums.lunatic.launcher.databinding.SettingsTimeDateBinding
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.DEFAULT_DATE_FORMAT
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_DATE_FORMAT
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_TIME_FORMAT
|
||||
import bums.lunatic.launcher.settings.SettingsActivity.Companion.settingsPrefs
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import java.util.Objects
|
||||
|
||||
|
||||
internal class TimeDate : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding : SettingsTimeDateBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsTimeDateBinding.inflate(inflater, container, false)
|
||||
|
||||
/* initialize views according to the saved values */
|
||||
when (settingsPrefs!!.getInt(KEY_TIME_FORMAT, 0)) {
|
||||
0 -> binding.followSystemTime.isChecked = true
|
||||
1 -> binding.selectTwelve.isChecked = true
|
||||
2 -> binding.selectTwentyFour.isChecked = true
|
||||
}
|
||||
|
||||
binding.dateFormat
|
||||
.setText(settingsPrefs!!.getString(KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT).toString())
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
/* change time format value */
|
||||
binding.timeGroup.setOnCheckedStateChangeListener { group, _ ->
|
||||
when (group.checkedChipId) {
|
||||
binding.followSystemTime.id -> settingsPrefs!!.edit().putInt(KEY_TIME_FORMAT, 0).apply()
|
||||
binding.selectTwelve.id -> settingsPrefs!!.edit().putInt(KEY_TIME_FORMAT, 1).apply()
|
||||
binding.selectTwentyFour.id -> settingsPrefs!!.edit().putInt(KEY_TIME_FORMAT, 2).apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* if the input field is empty, then save the default value.
|
||||
else save the value from input field while closing the dialog */
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
val dateFormat = Objects.requireNonNull(binding.dateFormat.text).toString().trim { it <= ' ' }
|
||||
if (dateFormat.isEmpty()) settingsPrefs!!.edit().putString(KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT).apply()
|
||||
else settingsPrefs!!.edit().putString(KEY_DATE_FORMAT, dateFormat).apply()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.settings.childs
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import bums.lunatic.launcher.databinding.SettingsWeatherBinding
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_CITY_NAME
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_OWM_API
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_SHOW_CITY
|
||||
import bums.lunatic.launcher.helpers.Constants.Companion.KEY_TEMP_UNIT
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.helpers.PrefString
|
||||
import bums.lunatic.launcher.settings.SettingsActivity.Companion.settingsPrefs
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import java.util.Objects
|
||||
|
||||
|
||||
internal class WeatherSettings : BottomSheetDialogFragment() {
|
||||
|
||||
private lateinit var binding : SettingsWeatherBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = SettingsWeatherBinding.inflate(inflater, container, false)
|
||||
|
||||
/* initialize views according to the saved values */
|
||||
|
||||
binding.inputOwm.setText(PrefString.weatherApiKey.get(""))
|
||||
binding.inputOwm.doOnTextChanged { text, start, before, count ->
|
||||
PrefString.weatherApiKey.set(text.toString())
|
||||
}
|
||||
binding.dress.isChecked = PrefBoolean.weatherDress.get(false)
|
||||
binding.weather.isChecked = PrefBoolean.weatherState.get(false)
|
||||
binding.weather.setOnCheckedChangeListener { buttonView, isChecked -> PrefBoolean.weatherState.set(isChecked)}
|
||||
binding.dress.setOnCheckedChangeListener { buttonView, isChecked -> PrefBoolean.weatherDress.set(isChecked)}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
(requireDialog() as BottomSheetDialog).dismissWithAnimation = true
|
||||
|
||||
}
|
||||
|
||||
/* save input field values while closing the dialog */
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
object AlphabetToChosungMap {
|
||||
val map = hashMapOf<String,String>(
|
||||
Pair("a","o"),
|
||||
Pair("b","ㅂ"),
|
||||
Pair("c","ㅋㅅ"),
|
||||
Pair("d","ㄷ"),
|
||||
Pair("e","o"),
|
||||
Pair("f","ㅍ"),
|
||||
Pair("g","ㄱㅈ"),
|
||||
Pair("h","ㅎ"),
|
||||
Pair("i","o"),
|
||||
Pair("j","ㅈ"),
|
||||
Pair("k","ㅋ"),
|
||||
Pair("l","ㄹ"),
|
||||
Pair("m","ㅁ"),
|
||||
Pair("n","ㄴ"),
|
||||
Pair("o","o"),
|
||||
Pair("p","ㅍ"),
|
||||
Pair("q","ㅋ"),
|
||||
Pair("r","ㄹ"),
|
||||
Pair("s","ㅅ"),
|
||||
Pair("t","ㅌ"),
|
||||
Pair("u","o"),
|
||||
Pair("v","ㅂ"),
|
||||
Pair("w","o"),
|
||||
Pair("x","ㅋㅅㅈ"),
|
||||
Pair("y","이"),
|
||||
Pair("z","ㅈ"),
|
||||
Pair("10","ㅅㅌ"),
|
||||
Pair("0","ㅇㅈ"),
|
||||
Pair("9","ㄱㄴ"),
|
||||
Pair("8","ㅍㅇ"),
|
||||
Pair("7","ㅊㅅ"),
|
||||
Pair("6","ㅇㅅ"),
|
||||
Pair("5","ㅇㅍ"),
|
||||
Pair("4","ㅅㅍ"),
|
||||
Pair("3","ㅅㅆ"),
|
||||
Pair("2","ㅇㅌ"),
|
||||
Pair("1","ㅇ"),
|
||||
)
|
||||
|
||||
fun getCho(string : String) = string.split("").filter { it.length > 0 && map.containsKey(it.toLowerCase()) }.map { map.get(it) }.joinToString("")
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.util.Log
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
|
||||
object BLog {
|
||||
val DEFAULT_TAG = "Lunatic"
|
||||
enum class BLogType {
|
||||
D,I,E
|
||||
}
|
||||
fun w(tag : String = DEFAULT_TAG, log: String){
|
||||
LOG(BLogType.D,tag,log)
|
||||
}
|
||||
|
||||
fun LOGD(tag : String = DEFAULT_TAG, log: String){
|
||||
LOG(BLogType.D,tag,log)
|
||||
}
|
||||
|
||||
fun LOGI(tag : String = DEFAULT_TAG, log: String){
|
||||
LOG(BLogType.I,tag,log)
|
||||
}
|
||||
|
||||
fun LOGE(tag : String = DEFAULT_TAG, log: Throwable){
|
||||
LOG(BLogType.E,tag,log.toString())
|
||||
}
|
||||
|
||||
fun LOGE(tag : String = DEFAULT_TAG, log: Exception){
|
||||
LOG(BLogType.E,tag,log.toString())
|
||||
}
|
||||
|
||||
fun LOGE(tag : String = DEFAULT_TAG, log: String){
|
||||
LOG(BLogType.E,tag,log)
|
||||
}
|
||||
|
||||
fun LOGE(log: String){
|
||||
LOG(BLogType.E,DEFAULT_TAG,log)
|
||||
}
|
||||
|
||||
private fun LOG(type : BLogType, tag : String, log : String) {
|
||||
if (BuildConfig.DEBUG || BuildConfig.BUILD_TYPE.contains("debug")) {
|
||||
when(type) {
|
||||
BLogType.D -> {
|
||||
Log.d(tag,log)
|
||||
}
|
||||
BLogType.I -> {
|
||||
Log.i(tag,log)
|
||||
}
|
||||
BLogType.E -> {
|
||||
Log.e(tag,log)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
|
||||
object BitmapConverter {
|
||||
/*
|
||||
* String형을 BitMap으로 변환시켜주는 함수
|
||||
* */
|
||||
fun StringToBitmap(encodedString: String?): Bitmap? {
|
||||
try {
|
||||
val encodeByte: ByteArray = Base64.decode(encodedString, Base64.DEFAULT)
|
||||
val bitmap = BitmapFactory.decodeByteArray(encodeByte, 0, encodeByte.size)
|
||||
return bitmap
|
||||
} catch (e: Exception) {
|
||||
e.message
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitmap을 String형으로 변환
|
||||
* */
|
||||
fun BitmapToString(bitmap: Bitmap?): String {
|
||||
if (bitmap == null) return ""
|
||||
val baos = ByteArrayOutputStream()
|
||||
bitmap?.compress(Bitmap.CompressFormat.PNG, 70, baos)
|
||||
val bytes = baos.toByteArray()
|
||||
val temp: String = Base64.encodeToString(bytes, Base64.DEFAULT)
|
||||
return temp
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitmap을 byte배열로 변환
|
||||
* */
|
||||
fun BitmapToByteArray(bitmap: Bitmap): ByteArray {
|
||||
val baos = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos)
|
||||
return baos.toByteArray()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.provider.ContactsContract.PhoneLookup
|
||||
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
fun before30Min(date: Date): Long {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(date)
|
||||
cal.add(Calendar.MINUTE, -30)
|
||||
return cal.timeInMillis
|
||||
}
|
||||
|
||||
fun beforeDay(date: Date?, day: Int): Long {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(date)
|
||||
cal.add(Calendar.DAY_OF_YEAR, Math.abs(day) * -1)
|
||||
return cal.timeInMillis
|
||||
}
|
||||
|
||||
fun make0H(date: Date?): Long {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(date)
|
||||
cal.set(Calendar.HOUR, 1)
|
||||
return cal.timeInMillis
|
||||
}
|
||||
|
||||
fun afterDay(date: Date?, day: Int): Long {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(date)
|
||||
cal.add(Calendar.DAY_OF_YEAR, Math.abs(day) * 1)
|
||||
return cal.timeInMillis
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun getContactName(contentResolver: ContentResolver, phoneNumber: String?): String? {
|
||||
var contactName: String? = null
|
||||
if (phoneNumber != null && phoneNumber.length > 0) {
|
||||
val cr = contentResolver
|
||||
val uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber))
|
||||
val cursor =
|
||||
cr.query(uri, arrayOf(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME), null, null, null)
|
||||
?: return null
|
||||
if (cursor.moveToFirst()) {
|
||||
contactName = cursor.getString(cursor.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
|
||||
}
|
||||
|
||||
if (cursor != null && !cursor.isClosed) {
|
||||
cursor.close()
|
||||
}
|
||||
}
|
||||
return contactName
|
||||
}
|
||||
|
||||
fun getContactId(contentResolver: ContentResolver, phoneNumber: String?): String? {
|
||||
val cr = contentResolver
|
||||
val uri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(phoneNumber))
|
||||
val cursor =
|
||||
cr.query(uri, arrayOf(PhoneLookup._ID), null, null, null)
|
||||
?: return null
|
||||
var contactName: String? = null
|
||||
if (cursor.moveToFirst()) {
|
||||
contactName = cursor.getString(cursor.getColumnIndexOrThrow(PhoneLookup._ID))
|
||||
}
|
||||
|
||||
if (cursor != null && !cursor.isClosed) {
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
return contactName
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,235 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
|
||||
object EnToKo {
|
||||
var ignoreChars: String = "`1234567890-=[]\\;',./~!@#$%^&*()_+{}|:\"<>? "
|
||||
|
||||
/** * 영어를 한글로... */
|
||||
fun engToKor(eng: String): String {
|
||||
val sb = StringBuffer()
|
||||
var initialCode = 0
|
||||
var medialCode = 0
|
||||
var finalCode = 0
|
||||
var tempMedialCode: Int
|
||||
var tempFinalCode: Int
|
||||
var i = 0
|
||||
while (i < eng.length) {
|
||||
// 숫자특수문자 처리
|
||||
if (ignoreChars.indexOf(eng.substring(i, i + 1)) > -1) {
|
||||
sb.append(eng.substring(i, i + 1))
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// 초성코드 추출
|
||||
initialCode = getCode(CodeType.chosung, eng.substring(i, i + 1))
|
||||
i++
|
||||
|
||||
// 다음문자로
|
||||
// 중성코드 추출
|
||||
tempMedialCode = getDoubleMedial(i, eng)
|
||||
|
||||
// 두 자로 이루어진 중성코드 추출
|
||||
if (tempMedialCode != -1) {
|
||||
medialCode = tempMedialCode
|
||||
i += 2
|
||||
} else {
|
||||
// 없다면,
|
||||
medialCode = getSingleMedial(i, eng)
|
||||
|
||||
// 한 자로 이루어진 중성코드 추출
|
||||
i++
|
||||
}
|
||||
|
||||
// 종성코드 추출
|
||||
tempFinalCode = getDoubleFinal(i, eng)
|
||||
|
||||
// 두 자로 이루어진 종성코드 추출
|
||||
if (tempFinalCode != -1) {
|
||||
finalCode = tempFinalCode
|
||||
|
||||
// 그 다음의 중성 문자에 대한 코드를 추출한다.
|
||||
tempMedialCode = getSingleMedial(i + 2, eng)
|
||||
if (tempMedialCode != -1) {
|
||||
// 코드 값이 있을 경우
|
||||
finalCode = getSingleFinal(i, eng)
|
||||
|
||||
// 종성 코드 값을 저장한다.
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
} else {
|
||||
// 코드 값이 없을 경우 ,
|
||||
tempMedialCode = getSingleMedial(i + 1, eng)
|
||||
|
||||
// 그 다음의 중성 문자에 대한 코드 추출.
|
||||
if (tempMedialCode != -1) {
|
||||
// 그 다음에 중성 문자가 존재할 경우,
|
||||
finalCode = 0 // 종성 문자는 없음.
|
||||
i--
|
||||
} else {
|
||||
finalCode = getSingleFinal(i, eng)
|
||||
|
||||
// 종성 문자 추출
|
||||
if (finalCode == -1) {
|
||||
finalCode = 0
|
||||
i--
|
||||
|
||||
// 초성,중성 + 숫자,특수문자,
|
||||
//기호가 나오는 경우 index를 줄임.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 추출한 초성 문자 코드,
|
||||
//중성 문자 코드, 종성 문자 코드를 합한 후 변환하여 스트링버퍼에 넘김
|
||||
sb.append((0xAC00 + initialCode + medialCode + finalCode).toChar())
|
||||
i++
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
/** * 해당 문자에 따른 코드를 추출한다. * * @param type * 초성 : chosung, 중성 : jungsung, 종성 : jongsung 구분 * @param char 해당 문자 */
|
||||
private fun getCode(type: CodeType, c: String): Int {
|
||||
// 초성
|
||||
val init = "rRseEfaqQtTdwWczxvg"
|
||||
|
||||
// 중성
|
||||
val mid = arrayOf(
|
||||
"k",
|
||||
"o",
|
||||
"i",
|
||||
"O",
|
||||
"j",
|
||||
"p",
|
||||
"u",
|
||||
"P",
|
||||
"h",
|
||||
"hk",
|
||||
"ho",
|
||||
"hl",
|
||||
"y",
|
||||
"n",
|
||||
"nj",
|
||||
"np",
|
||||
"nl",
|
||||
"b",
|
||||
"m",
|
||||
"ml",
|
||||
"l"
|
||||
)
|
||||
|
||||
// 종성
|
||||
val fin = arrayOf(
|
||||
"r",
|
||||
"R",
|
||||
"rt",
|
||||
"s",
|
||||
"sw",
|
||||
"sg",
|
||||
"e",
|
||||
"f",
|
||||
"fr",
|
||||
"fa",
|
||||
"fq",
|
||||
"ft",
|
||||
"fx",
|
||||
"fv",
|
||||
"fg",
|
||||
"a",
|
||||
"q",
|
||||
"qt",
|
||||
"t",
|
||||
"T",
|
||||
"d",
|
||||
"w",
|
||||
"c",
|
||||
"z",
|
||||
"x",
|
||||
"v",
|
||||
"g"
|
||||
)
|
||||
when (type) {
|
||||
CodeType.chosung -> {
|
||||
val index = init.indexOf(c)
|
||||
if (index != -1) {
|
||||
return index * 21 * 28
|
||||
}
|
||||
}
|
||||
|
||||
CodeType.jungsung -> {
|
||||
var i = 0
|
||||
while (i < mid.size) {
|
||||
if (mid[i] == c) {
|
||||
return i * 28
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
CodeType.jongsung -> {
|
||||
var i = 0
|
||||
while (i < fin.size) {
|
||||
if (fin[i] == c) {
|
||||
return i + 1
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
else -> println("잘못된 타입 입니다")
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// 한 자로 된 중성값을 리턴한다
|
||||
// 인덱스를 벗어낫다면 -1을 리턴
|
||||
private fun getSingleMedial(i: Int, eng: String): Int {
|
||||
return if ((i + 1) <= eng.length) {
|
||||
getCode(CodeType.jungsung, eng.substring(i, i + 1))
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// 두 자로 된 중성을 체크하고, 있다면 값을 리턴한다.
|
||||
// 없으면 리턴값은 -1
|
||||
private fun getDoubleMedial(i: Int, eng: String): Int {
|
||||
val result: Int
|
||||
if ((i + 2) > eng.length) {
|
||||
return -1
|
||||
} else {
|
||||
result = getCode(CodeType.jungsung, eng.substring(i, i + 2))
|
||||
return if (result != -1) {
|
||||
result
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 한 자로된 종성값을 리턴한다
|
||||
// 인덱스를 벗어낫다면 -1을 리턴
|
||||
private fun getSingleFinal(i: Int, eng: String): Int {
|
||||
return if ((i + 1) <= eng.length) {
|
||||
getCode(CodeType.jongsung, eng.substring(i, i + 1))
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
// 두 자로된 종성을 체크하고, 있다면 값을 리턴한다.
|
||||
// 없으면 리턴값은 -1
|
||||
private fun getDoubleFinal(i: Int, eng: String): Int {
|
||||
return if ((i + 2) > eng.length) {
|
||||
-1
|
||||
} else {
|
||||
getCode(CodeType.jongsung, eng.substring(i, i + 2))
|
||||
}
|
||||
}
|
||||
|
||||
// 코드타입 - 초성, 중성, 종성
|
||||
internal enum class CodeType {
|
||||
chosung, jungsung, jongsung
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import java.lang.Long.toHexString
|
||||
|
||||
class Hashids(salt: String = defaultSalt, minHashLength: Int = defaultMinimalHashLength, alphabet: String = defaultAlphabet) {
|
||||
companion object {
|
||||
const val defaultSalt = ""
|
||||
const val defaultMinimalHashLength = 0
|
||||
const val defaultAlphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||
const val defaultSeparators = "cfhistuCFHISTU"
|
||||
const val minimalAlphabetLength = 16
|
||||
const val separatorDiv = 3.5
|
||||
const val guardDiv = 12
|
||||
|
||||
const val version = "1.1.0"
|
||||
|
||||
private const val emptyString = ""
|
||||
private const val space = " "
|
||||
private const val maxNumber = 9007199254740992
|
||||
}
|
||||
|
||||
private val finalSalt = whatSalt(salt)
|
||||
private val finalHashLength = whatHashLength(minHashLength)
|
||||
private val alphabetSeparatorsAndGuards = calculateAlphabetAndSeparators(alphabet)
|
||||
private val finalAlphabet = alphabetSeparatorsAndGuards.alphabet
|
||||
private val finalSeparators = alphabetSeparatorsAndGuards.separators
|
||||
private val finalGuards = alphabetSeparatorsAndGuards.guards
|
||||
|
||||
|
||||
/**
|
||||
* Encodes numbers to string
|
||||
*
|
||||
* @param numbers the numbers to encode
|
||||
* @return The encoded string
|
||||
*/
|
||||
fun encode(vararg numbers: Long): String = when {
|
||||
numbers.isEmpty() -> emptyString
|
||||
numbers.any { it > maxNumber } -> throw IllegalArgumentException("Number can not be greater than ${maxNumber}L")
|
||||
else -> {
|
||||
val numbersHash = numbers.indices
|
||||
.map { (numbers[it] % (it + 100)).toInt() }
|
||||
.sum()
|
||||
|
||||
val initialCharacter = finalAlphabet.toCharArray()[numbersHash % finalAlphabet.length]
|
||||
val (encodedString, encodingAlphabet) = initialEncode(numbers.asList(), finalSeparators.toCharArray(), initialCharacter.toString(), 0, finalAlphabet, initialCharacter.toString())
|
||||
val tempReturnString = addGuardsIfNecessary(encodedString, numbersHash)
|
||||
|
||||
val halfLength = finalAlphabet.length / 2
|
||||
ensureMinimalLength(halfLength, encodingAlphabet, tempReturnString)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes string to numbers
|
||||
*
|
||||
* @param hash the encoded string
|
||||
* @return Decoded numbers
|
||||
*/
|
||||
fun decode(hash: String): LongArray = when {
|
||||
hash.isEmpty() -> longArrayOf()
|
||||
else -> {
|
||||
val guardsRegex = "[$finalGuards]".toRegex()
|
||||
val hashWithSpacesInsteadOfGuards = hash.replace(guardsRegex, space)
|
||||
val initialSplit = hashWithSpacesInsteadOfGuards.split(space)
|
||||
|
||||
val (lottery, hashBreakdown) = extractLotteryCharAndHashArray(initialSplit)
|
||||
val returnValue = unhashSubHashes(hashBreakdown.iterator(), lottery, mutableListOf(), finalAlphabet)
|
||||
|
||||
when {
|
||||
encode(*returnValue) != hash -> longArrayOf()
|
||||
else -> returnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun guardIndex(numbersHash: Int, returnString: String, index: Int): Int = (numbersHash + returnString.toCharArray()[index].toInt()) % finalGuards.length
|
||||
|
||||
/**
|
||||
* Encoded hex string to string
|
||||
*
|
||||
* @param hex the hex string to encode
|
||||
* @return The encoded string
|
||||
*/
|
||||
fun encodeHex(hex: String): String = when {
|
||||
!hex.matches("^[0-9a-fA-F]+$".toRegex()) -> emptyString
|
||||
else -> {
|
||||
val toEncode = "[\\w\\W]{1,12}".toRegex().findAll(hex)
|
||||
.map { it.groupValues }
|
||||
.flatten()
|
||||
.map { it.toLong(16) }
|
||||
.toList()
|
||||
.toLongArray()
|
||||
encode(*toEncode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes string to hex numbers string
|
||||
*
|
||||
* @param hash the encoded string
|
||||
* @return decoded hex numbers string
|
||||
*/
|
||||
fun decodeHex(hash: String): String = decode(hash)
|
||||
.map { toHexString(it).substring(1) }
|
||||
.toString()
|
||||
|
||||
private fun whatSalt(aSalt: String) = when {
|
||||
aSalt.isEmpty() -> defaultSalt
|
||||
else -> aSalt
|
||||
}
|
||||
|
||||
private fun whatHashLength(aLength: Int) = when {
|
||||
aLength > 0 -> aLength
|
||||
else -> defaultMinimalHashLength
|
||||
}
|
||||
|
||||
private fun calculateAlphabetAndSeparators(userAlphabet: String): AlphabetAndSeparators {
|
||||
val uniqueAlphabet = unique(userAlphabet)
|
||||
when {
|
||||
uniqueAlphabet.length < minimalAlphabetLength -> throw IllegalArgumentException("alphabet must contain at least $minimalAlphabetLength unique characters")
|
||||
uniqueAlphabet.contains(space) -> throw IllegalArgumentException("alphabet cannot contains spaces")
|
||||
else -> {
|
||||
val legalSeparators = defaultSeparators.toSet().intersect(uniqueAlphabet.toSet())
|
||||
val alphabetWithoutSeparators = uniqueAlphabet.toSet().minus(legalSeparators).joinToString(emptyString)
|
||||
val shuffledSeparators = consistentShuffle(legalSeparators.joinToString(emptyString), finalSalt)
|
||||
val (adjustedAlphabet, adjustedSeparators) = adjustAlphabetAndSeparators(alphabetWithoutSeparators, shuffledSeparators)
|
||||
|
||||
val guardCount = Math.ceil(adjustedAlphabet.length.toDouble() / guardDiv).toInt()
|
||||
return if (adjustedAlphabet.length < 3) {
|
||||
val guards = adjustedSeparators.substring(0, guardCount)
|
||||
val seps = adjustedSeparators.substring(guardCount)
|
||||
AlphabetAndSeparators(adjustedAlphabet, seps, guards)
|
||||
} else {
|
||||
val guards = adjustedAlphabet.substring(0, guardCount)
|
||||
val alphabet = adjustedAlphabet.substring(guardCount)
|
||||
AlphabetAndSeparators(alphabet, adjustedSeparators, guards)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun adjustAlphabetAndSeparators(alphabetWithoutSeparators: String, shuffledSeparators: String): AlphabetAndSeparators =
|
||||
if (shuffledSeparators.isEmpty() ||
|
||||
(alphabetWithoutSeparators.length / shuffledSeparators.length).toFloat() > separatorDiv) {
|
||||
|
||||
val sepsLength = calculateSeparatorsLength(alphabetWithoutSeparators)
|
||||
|
||||
if (sepsLength > shuffledSeparators.length) {
|
||||
val difference = sepsLength - shuffledSeparators.length
|
||||
val seps = shuffledSeparators + alphabetWithoutSeparators.substring(0, difference)
|
||||
val alpha = alphabetWithoutSeparators.substring(difference)
|
||||
AlphabetAndSeparators(consistentShuffle(alpha, finalSalt), seps)
|
||||
} else {
|
||||
val seps = shuffledSeparators.substring(0, sepsLength)
|
||||
AlphabetAndSeparators(consistentShuffle(alphabetWithoutSeparators, finalSalt), seps)
|
||||
}
|
||||
} else {
|
||||
AlphabetAndSeparators(consistentShuffle(alphabetWithoutSeparators, finalSalt), shuffledSeparators)
|
||||
}
|
||||
|
||||
private fun calculateSeparatorsLength(alphabet: String): Int = when (val s = Math.ceil(alphabet.length / separatorDiv).toInt()) {
|
||||
1 -> 2
|
||||
else -> s
|
||||
}
|
||||
|
||||
private fun unique(input: String) = input.toSet().joinToString(emptyString)
|
||||
|
||||
private fun addGuardsIfNecessary(encodedString: String, numbersHash: Int): String =
|
||||
if (encodedString.length < finalHashLength) {
|
||||
val guard0 = finalGuards.toCharArray()[guardIndex(numbersHash, encodedString, 0)]
|
||||
val retString = guard0 + encodedString
|
||||
|
||||
if (retString.length < finalHashLength) {
|
||||
val guard2 = finalGuards.toCharArray()[guardIndex(numbersHash, retString, 2)]
|
||||
retString + guard2
|
||||
} else {
|
||||
retString
|
||||
}
|
||||
} else {
|
||||
encodedString
|
||||
}
|
||||
|
||||
private fun extractLotteryCharAndHashArray(initialSplit: List<String>): Pair<Char, List<String>> {
|
||||
val separatorsRegex = "[$finalSeparators]".toRegex()
|
||||
val i = when {
|
||||
initialSplit.size == 2 || initialSplit.size == 3 -> 1
|
||||
else -> 0
|
||||
}
|
||||
val ithElementOfSplit = initialSplit[i]
|
||||
|
||||
val lotteryChar = ithElementOfSplit.first()
|
||||
val finalBreakdown = ithElementOfSplit
|
||||
.substring(1)
|
||||
.replace(separatorsRegex, space)
|
||||
.split(space)
|
||||
return Pair(lotteryChar, finalBreakdown)
|
||||
}
|
||||
|
||||
private tailrec fun unhashSubHashes(hashes: Iterator<String>, lottery: Char, currentReturn: MutableList<Long>, alphabet: String): LongArray {
|
||||
return when {
|
||||
hashes.hasNext() -> {
|
||||
val subHash = hashes.next()
|
||||
val buffer = "$lottery$finalSalt$alphabet"
|
||||
val newAlphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length))
|
||||
currentReturn.add(unhash(subHash, newAlphabet))
|
||||
unhashSubHashes(hashes, lottery, currentReturn, newAlphabet)
|
||||
}
|
||||
else -> currentReturn.toLongArray()
|
||||
}
|
||||
}
|
||||
|
||||
private fun hash(input: Long, alphabet: String): String =
|
||||
doHash(input, alphabet.toCharArray(), HashData(emptyString, input)).hash
|
||||
|
||||
private tailrec fun doHash(number: Long, alphabet: CharArray, data: HashData): HashData = when {
|
||||
data.current > 0 -> {
|
||||
val newHashCharacter = alphabet[(data.current % alphabet.size.toLong()).toInt()]
|
||||
val newCurrent = data.current / alphabet.size
|
||||
doHash(number, alphabet, HashData("$newHashCharacter${data.hash}", newCurrent))
|
||||
}
|
||||
else -> data
|
||||
}
|
||||
|
||||
private fun unhash(input: String, alphabet: String): Long =
|
||||
doUnhash(input.toCharArray(), alphabet, alphabet.length.toDouble(), 0, 0)
|
||||
|
||||
private tailrec fun doUnhash(input: CharArray, alphabet: String, alphabetLengthDouble: Double, currentNumber: Long, currentIndex: Int): Long =
|
||||
when {
|
||||
currentIndex < input.size -> {
|
||||
val position = alphabet.indexOf(input[currentIndex])
|
||||
val newNumber = currentNumber + (position * alphabetLengthDouble.pow((input.size.toDouble() - currentIndex - 1))).toLong()
|
||||
doUnhash(input, alphabet, alphabetLengthDouble, newNumber, currentIndex + 1)
|
||||
}
|
||||
else -> currentNumber
|
||||
}
|
||||
|
||||
fun Double.pow(d : Double) : Double{
|
||||
return Math.pow(this,d)
|
||||
}
|
||||
|
||||
private fun consistentShuffle(alphabet: String, salt: String) = when {
|
||||
salt.isEmpty() -> alphabet
|
||||
else -> {
|
||||
val initial = ShuffleData(alphabet.toList(), salt, 0, 0)
|
||||
shuffle(initial, alphabet.length - 1, 1).alphabet.joinToString(emptyString)
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun shuffle(data: ShuffleData, currentPosition: Int, limit: Int): ShuffleData = when {
|
||||
currentPosition < limit -> data
|
||||
else -> {
|
||||
val currentAlphabet = data.alphabet.toCharArray()
|
||||
val saltReminder = data.saltReminder % data.salt.length
|
||||
val asciiValue = data.salt[saltReminder].toInt()
|
||||
val cumulativeValue = data.cumulative + asciiValue
|
||||
val positionToSwap = (asciiValue + saltReminder + cumulativeValue) % currentPosition
|
||||
currentAlphabet[positionToSwap] = currentAlphabet[currentPosition].also {
|
||||
currentAlphabet[currentPosition] = currentAlphabet[positionToSwap]
|
||||
}
|
||||
shuffle(ShuffleData(currentAlphabet.toList(), data.salt, cumulativeValue, saltReminder + 1), currentPosition - 1, limit)
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun initialEncode(numbers: List<Long>,
|
||||
separators: CharArray,
|
||||
bufferSeed: String,
|
||||
currentIndex: Int,
|
||||
alphabet: String,
|
||||
currentReturnString: String): Pair<String, String> = when {
|
||||
currentIndex < numbers.size -> {
|
||||
val currentNumber = numbers[currentIndex]
|
||||
val buffer = bufferSeed + finalSalt + alphabet
|
||||
val nextAlphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length))
|
||||
val last = hash(currentNumber, nextAlphabet)
|
||||
|
||||
val newReturnString = if (currentIndex + 1 < numbers.size) {
|
||||
val nextNumber = currentNumber % (last.toCharArray()[0].toInt() + currentIndex)
|
||||
val sepsIndex = (nextNumber % separators.size).toInt()
|
||||
currentReturnString + last + separators[sepsIndex]
|
||||
} else {
|
||||
currentReturnString + last
|
||||
}
|
||||
initialEncode(numbers, separators, bufferSeed, currentIndex + 1, nextAlphabet, newReturnString)
|
||||
}
|
||||
else -> Pair(currentReturnString, alphabet)
|
||||
}
|
||||
|
||||
private tailrec fun ensureMinimalLength(halfLength: Int, alphabet: String, returnString: String): String = when {
|
||||
returnString.length < finalHashLength -> {
|
||||
val newAlphabet = consistentShuffle(alphabet, alphabet)
|
||||
val tempReturnString = newAlphabet.substring(halfLength) + returnString + newAlphabet.substring(0, halfLength)
|
||||
val excess = tempReturnString.length - finalHashLength
|
||||
val newReturnString = if (excess > 0) {
|
||||
val position = excess / 2
|
||||
tempReturnString.substring(position, position + finalHashLength)
|
||||
} else {
|
||||
tempReturnString
|
||||
}
|
||||
ensureMinimalLength(halfLength, newAlphabet, newReturnString)
|
||||
}
|
||||
else -> returnString
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private data class AlphabetAndSeparators(val alphabet: String, val separators: String, val guards: String = "")
|
||||
|
||||
private data class ShuffleData(val alphabet: List<Char>, val salt: String, val cumulative: Int, val saltReminder: Int)
|
||||
|
||||
private data class HashData(val hash: String, val current: Long)
|
||||
@@ -0,0 +1,31 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
object ImageUtils {
|
||||
|
||||
fun bitmapToBase64String(resource : Bitmap) : String? {
|
||||
val baos = ByteArrayOutputStream()
|
||||
resource.compress(Bitmap.CompressFormat.PNG, 100, baos)
|
||||
val iconArray = baos.toByteArray()
|
||||
|
||||
return if (iconArray != null && iconArray.size > 0) {
|
||||
Base64.encodeToString(iconArray, Base64.DEFAULT)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun stringToBitmap(src : String) : Bitmap? {
|
||||
val iconArray = Base64.decode(src?.toByteArray(), Base64.DEFAULT)
|
||||
return if (iconArray != null && iconArray.size > 0) {
|
||||
BitmapFactory.decodeByteArray(iconArray, 0, iconArray.size)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
||||
object JamoUtils {
|
||||
val CHOSUNG = listOf(
|
||||
"ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ",
|
||||
"ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ",
|
||||
)
|
||||
val JUNGSUNG = listOf(
|
||||
"ㅏ", "ㅐ", "ㅑ", "ㅒ", "ㅓ", "ㅔ", "ㅕ", "ㅖ", "ㅗ", "ㅘ",
|
||||
"ㅙ", "ㅚ", "ㅛ", "ㅜ", "ㅝ", "ㅞ", "ㅟ", "ㅠ", "ㅡ", "ㅢ", "ㅣ",
|
||||
)
|
||||
val JONGSUNG = listOf(
|
||||
"", "ㄱ", "ㄲ", "ᆪ", "ᆫ", "ᆬ", "ᆭ", "ㄷ",
|
||||
"ㄹ", "ᆰ", "ᆱ", "ᆲ", "ᆳ", "ᆴ", "ᆵ", "ᆶ", "ㅁ", "ㅂ", "ᆹ", "ᆺ", "ᆻ", "ᆼ",
|
||||
"ᆽ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ",
|
||||
)
|
||||
|
||||
fun split(target: String?): List<String> {
|
||||
if (target.isNullOrEmpty()) return arrayListOf()
|
||||
return target.split("")
|
||||
.filter(String::isNotEmpty)
|
||||
.map(JamoUtils::splitOne)
|
||||
.toList()
|
||||
}
|
||||
|
||||
@SuppressLint("SuspiciousIndentation")
|
||||
fun splitOne(target: String): String {
|
||||
val codePoint = Character.codePointAt(target, 0)
|
||||
return if (codePoint in 0xAC00..0xD79D) {
|
||||
val startValue = codePoint - 0xAC00
|
||||
val jong = startValue % 28
|
||||
val jung = (startValue - jong) / 28 % 21
|
||||
val cho = ((startValue - jong) / 28 - jung) / 21
|
||||
CHOSUNG[cho]
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import bums.lunatic.launcher.home.LauncherHome.Companion.lastedFinishedPageUrl
|
||||
import bums.lunatic.launcher.model.MostItem
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.dateFormat
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import io.realm.kotlin.ext.query
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Base64
|
||||
import java.util.Date
|
||||
|
||||
val USAGT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
|
||||
fun String.getJ() = Jsoup.connect(this).userAgent(USAGT).get()
|
||||
object FeedParseManager {
|
||||
val parsers = listOf<SoInterface>(QVZTb2dpcmw,SkFWTW9zdA)
|
||||
fun parse(doc : Document, consoleLog : (String)-> Unit) {
|
||||
consoleLog("FeedParseManager START")
|
||||
try {
|
||||
parsers.filter { doc.title().contains(it.getName()) }.first()?.let {
|
||||
it.parse(doc,consoleLog)
|
||||
}
|
||||
} catch (e : Exception) {
|
||||
consoleLog(e.message ?: "Exception")
|
||||
e.printStackTrace()
|
||||
}
|
||||
consoleLog("FeedParseManager END")
|
||||
}
|
||||
}
|
||||
interface SoInterface{
|
||||
fun getName() : String
|
||||
fun parse(doc : Document, consoleLog : (String)-> Unit)
|
||||
}
|
||||
object QVZTb2dpcmw : SoInterface {
|
||||
override fun getName(): String {
|
||||
return String(Base64.getMimeDecoder().decode(this.javaClass.simpleName.plus("==").toByteArray()))
|
||||
}
|
||||
override fun parse(doc : Document, consoleLog : (String)-> Unit) {
|
||||
doc.getElementsByTag("article").forEach { article ->
|
||||
consoleLog("ogirl article >>> ${article.text()}")
|
||||
val title = article.getElementsByTag("a").get(0).attr("title")
|
||||
val href = article.getElementsByTag("a").get(0).attr("href")
|
||||
val img = article.getElementsByTag("img").get(0).attr("data-src")
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
if (query<RssData>("originPage == $0", href).find().size == 0) {
|
||||
RssData().apply {
|
||||
this.originPage = href
|
||||
this.title = title
|
||||
this.description = "Sogirl"
|
||||
this.thumbnail = img
|
||||
this.pubDate = Date().time
|
||||
this.category = RssDataType.GURU.name
|
||||
this.chosung =
|
||||
JamoUtils.split(title).joinToString("")
|
||||
copyToRealm(this)
|
||||
}
|
||||
consoleLog("title $title | href $href | img $img" )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
object SkFWTW9zdA : SoInterface {
|
||||
var dmy = SimpleDateFormat("dd-MM-yyyy")
|
||||
override fun getName(): String {
|
||||
return String(Base64.getMimeDecoder().decode(this.javaClass.simpleName.plus("==").toByteArray()))
|
||||
}
|
||||
override fun parse(doc: Document, consoleLog: (String) -> Unit) {
|
||||
consoleLog("$lastedFinishedPageUrl >>> ${doc.title()}")
|
||||
doc.getElementsByClass("card").forEach { card ->
|
||||
var thumb = if(card.getElementsByTag("img").size > 0) card.getElementsByTag("img").get(0).attr("src") else ""
|
||||
if (thumb.contains("No+Poster")) thumb = if(card.getElementsByTag("img").size > 0) card.getElementsByTag("img").get(0).attr("data-src") else thumb
|
||||
var model = if(card.getElementsByTag("img").size > 0) card.getElementsByTag("img").get(0).attr("alt") else ""
|
||||
if(card.getElementsByClass("card-block").size > 0) if(card.getElementsByClass("card-block").size > 0) {
|
||||
val link = card.getElementsByClass("card-block").get(0).getElementsByTag("a").get(0).attr("href")
|
||||
val title = card.getElementsByClass("card-block").get(0).getElementsByTag("a").get(0).attr("title")
|
||||
val date = card.getElementsByTag("span").get(0).text()
|
||||
MostItem().let { ms ->
|
||||
ms.model = model
|
||||
ms.image = thumb
|
||||
ms.pageLink = link
|
||||
ms.title = title
|
||||
try {
|
||||
ms.date = dmy.parse(date).time
|
||||
consoleLog("dateFormat.format(Date(ms.date)) ${dateFormat.format(Date(ms.date))}")
|
||||
}catch (e : Exception) {e.printStackTrace()}
|
||||
if (ms.isValid()) {
|
||||
WorkersDb.insertData(ms.getRssData())
|
||||
}
|
||||
}
|
||||
consoleLog(" model >>>>> ${model}\n | thumb >>>>> ${thumb}\n | title >>>>> ${title}\n | date >>>>> ${date} | ")
|
||||
}
|
||||
}
|
||||
consoleLog("excuted j req() ${WorkersDb.getRealm().query<RssData>("category == $0", RssDataType.GURU.name).find().size}")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,78 @@
|
||||
//package com.example.ch16_provider
|
||||
//
|
||||
//import android.content.Intent
|
||||
//import android.content.pm.PackageManager
|
||||
//import android.os.Bundle
|
||||
//import android.provider.ContactsContract
|
||||
//import android.util.Log
|
||||
//import androidx.activity.result.ActivityResultLauncher
|
||||
//import androidx.activity.result.contract.ActivityResultContracts
|
||||
//import androidx.appcompat.app.AppCompatActivity
|
||||
//import androidx.core.app.ActivityCompat
|
||||
//import androidx.core.content.ContextCompat
|
||||
//import com.example.ch16_provider.databinding.ActivityMainBinding
|
||||
//
|
||||
//class MainActivity : AppCompatActivity() {
|
||||
// lateinit var binding: ActivityMainBinding
|
||||
// lateinit var requestLauncher: ActivityResultLauncher<Intent>
|
||||
//
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
// setContentView(binding.root)
|
||||
//
|
||||
// // 퍼미션 허용했는지 확인
|
||||
// val status = ContextCompat.checkSelfPermission(this, "android.permission.READ_CONTACTS")
|
||||
// if (status == PackageManager.PERMISSION_GRANTED) {
|
||||
// Log.d("test", "permission granted")
|
||||
// } else {
|
||||
// // 퍼미션 요청 다이얼로그 표시
|
||||
// ActivityCompat.requestPermissions(this, arrayOf<String>("android.permission.READ_CONTACTS"), 100)
|
||||
// Log.d("test", "permission denied")
|
||||
// }
|
||||
//
|
||||
// // ActivityResultLauncher 초기화, 결과 콜백 정의
|
||||
// requestLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
// if (it.resultCode == RESULT_OK) {
|
||||
// Log.d("test", "Uri : ${it.data!!.data!!}")
|
||||
// val cursor = contentResolver.query(
|
||||
// it.data!!.data!!,
|
||||
// arrayOf<String>(
|
||||
// ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||
// ContactsContract.CommonDataKinds.Phone.NUMBER,
|
||||
// ),
|
||||
// null,
|
||||
// null,
|
||||
// null
|
||||
// )
|
||||
// Log.d("test", "cursor size : ${cursor?.count}")
|
||||
//
|
||||
// if (cursor!!.moveToFirst()) {
|
||||
// val name = cursor.getString(0)
|
||||
// val phone = cursor.getString(1)
|
||||
// binding.textView.text = "name: $name, phone: $phone"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// binding.button.setOnClickListener {
|
||||
// // 주소록 앱 연동
|
||||
// val intent = Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
|
||||
// requestLauncher.launch(intent)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // 다이얼로그에서 퍼미션 허용했는지 확인
|
||||
// override fun onRequestPermissionsResult(
|
||||
// requestCode: Int,
|
||||
// permissions: Array<out String>,
|
||||
// grantResults: IntArray
|
||||
// ) {
|
||||
// super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
// if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
// Log.d("test", "permission granted")
|
||||
// } else {
|
||||
// Log.d("test", "permission denied")
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,73 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import org.jsoup.nodes.Document
|
||||
import java.net.URLEncoder
|
||||
|
||||
object RssList {
|
||||
|
||||
val TEST_PAG2 = "https://torrentsee246.com/topic/index?category1=129&category2=132"
|
||||
val jGuruMain = "https://jav.guru/"
|
||||
val jGuruRanks ="https://jav.guru/most-watched-rank/"
|
||||
|
||||
|
||||
val youtubeUrls = arrayListOf(
|
||||
"https://www.youtube.com/@zzanbro",
|
||||
"https://www.youtube.com/@sungsikyung",
|
||||
"https://www.youtube.com/@%EC%A7%80%EB%AC%B4%EB%B9%84",
|
||||
"https://www.youtube.com/@gyeomsonisnothing",
|
||||
"https://www.youtube.com/@ddeunddeun"
|
||||
)
|
||||
val newsFeeds = arrayListOf(
|
||||
"https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko",
|
||||
)
|
||||
|
||||
val feedJsons = arrayListOf(
|
||||
// "https://www.reddit.com/r/nsfw/.json",
|
||||
"https://www.reddit.com/r/Mogong/.json",
|
||||
// "https://www.reddit.com/r/Mogong/comments/e6tu50/19_%ED%9B%84%EB%B0%A9%EA%B0%80%EB%93%9D%ED%95%9C_%EC%84%9C%EB%B8%8C%EB%A0%88%EB%94%A7/.json"
|
||||
|
||||
// "https://www.reddit.com/r/${URLEncoder.encode("모공")}/.json",
|
||||
)
|
||||
val feedJsons_nsfw = arrayListOf(
|
||||
"https://www.reddit.com/r/nsfw/.json",
|
||||
"https://www.reddit.com/r/nsfw411/.json",
|
||||
"https://www.reddit.com/r/nsfw2/.json",
|
||||
"https://www.reddit.com/r/nudes/.json",
|
||||
"https://www.reddit.com/r/cuckold/.json",
|
||||
// "https://www.reddit.com/r/Mogong/.json",
|
||||
// "https://www.reddit.com/r/Mogong/comments/e6tu50/19_%ED%9B%84%EB%B0%A9%EA%B0%80%EB%93%9D%ED%95%9C_%EC%84%9C%EB%B8%8C%EB%A0%88%EB%94%A7/.json"
|
||||
|
||||
// "https://www.reddit.com/r/${URLEncoder.encode("모공")}/.json",
|
||||
)
|
||||
|
||||
fun getFeedUrls() = keyWords.map { "https://news.google.com/rss/search?q=${URLEncoder.encode(it)}=ko&gl=KR&ceid=KR%3Ako/" }
|
||||
|
||||
val keyWords = listOf(
|
||||
"영화",
|
||||
"개발",
|
||||
"신작",
|
||||
"신보",
|
||||
"날씨",
|
||||
"테크",
|
||||
"래퍼",
|
||||
"부동산",
|
||||
"과학",
|
||||
"당뇨",
|
||||
"신장",
|
||||
"여행",
|
||||
"음반",
|
||||
"도끼",
|
||||
"힙합",
|
||||
)
|
||||
}
|
||||
|
||||
object DocParserManager {
|
||||
fun parse(doc : Document) {}
|
||||
}
|
||||
interface DocParser { fun <T>parse(doc : Document) : T }
|
||||
|
||||
class JGuruMain {
|
||||
var maxDate : Long = Long.MIN_VALUE
|
||||
var minDate : Long = Long.MAX_VALUE
|
||||
|
||||
}
|
||||
@@ -0,0 +1,811 @@
|
||||
package bums.lunatic.launcher.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.View.OnTouchListener
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
class GestureAnalyser @JvmOverloads constructor(
|
||||
swipeSlopeIntolerance: Int = 2,
|
||||
doubleTapMaxDelayMillis: Int = 500,
|
||||
doubleTapMaxDownMillis: Int = 100
|
||||
) {
|
||||
var minValue = 100
|
||||
private val initialX = DoubleArray(5)
|
||||
private val initialY = DoubleArray(5)
|
||||
private val finalX = DoubleArray(5)
|
||||
private val finalY = DoubleArray(5)
|
||||
private val currentX = DoubleArray(5)
|
||||
private val currentY = DoubleArray(5)
|
||||
private val delX = DoubleArray(5)
|
||||
private val delY = DoubleArray(5)
|
||||
|
||||
private var numFingers = 0
|
||||
private var initialT: Long = 0
|
||||
private var finalT: Long = 0
|
||||
private var currentT: Long = 0
|
||||
|
||||
private var prevInitialT: Long = 0
|
||||
private var prevFinalT: Long = 0
|
||||
|
||||
private var swipeSlopeIntolerance = 2
|
||||
|
||||
private val doubleTapMaxDelayMillis: Long
|
||||
private val doubleTapMaxDownMillis: Long
|
||||
|
||||
init {
|
||||
this.swipeSlopeIntolerance = swipeSlopeIntolerance
|
||||
this.doubleTapMaxDownMillis = doubleTapMaxDownMillis.toLong()
|
||||
this.doubleTapMaxDelayMillis = doubleTapMaxDelayMillis.toLong()
|
||||
}
|
||||
|
||||
fun trackGesture(ev: MotionEvent) {
|
||||
val n = ev.pointerCount
|
||||
for (i in 0 until n) {
|
||||
initialX[i] = ev.getX(i).toDouble()
|
||||
initialY[i] = ev.getY(i).toDouble()
|
||||
}
|
||||
numFingers = n
|
||||
initialT = SystemClock.uptimeMillis()
|
||||
}
|
||||
|
||||
fun untrackGesture() {
|
||||
numFingers = 0
|
||||
prevFinalT = SystemClock.uptimeMillis()
|
||||
prevInitialT = initialT
|
||||
}
|
||||
|
||||
fun getGesture(ev: MotionEvent): GestureType {
|
||||
var averageDistance = 0.0
|
||||
for (i in 0 until numFingers) {
|
||||
finalX[i] = ev.getX(i).toDouble()
|
||||
finalY[i] = ev.getY(i).toDouble()
|
||||
delX[i] = finalX[i] - initialX[i]
|
||||
delY[i] = finalY[i] - initialY[i]
|
||||
|
||||
averageDistance += sqrt(
|
||||
(finalX[i] - initialX[i]).pow(2.0) + (finalY[i] - initialY[i]).pow(
|
||||
2.0
|
||||
)
|
||||
)
|
||||
}
|
||||
averageDistance /= numFingers.toDouble()
|
||||
|
||||
finalT = SystemClock.uptimeMillis()
|
||||
val gt = GestureType()
|
||||
gt.gestureFlag = calcGesture()
|
||||
gt.gestureDuration = finalT - initialT
|
||||
gt.gestureDistance = averageDistance
|
||||
return gt
|
||||
}
|
||||
|
||||
fun getOngoingGesture(ev: MotionEvent): Int {
|
||||
for (i in 0 until numFingers) {
|
||||
currentX[i] = ev.getX(i).toDouble()
|
||||
currentY[i] = ev.getY(i).toDouble()
|
||||
delX[i] = finalX[i] - initialX[i]
|
||||
delY[i] = finalY[i] - initialY[i]
|
||||
}
|
||||
currentT = SystemClock.uptimeMillis()
|
||||
return calcGesture()
|
||||
}
|
||||
|
||||
private fun calcGesture(): Int {
|
||||
if (isDoubleTap) {
|
||||
return DOUBLE_TAP_1
|
||||
}
|
||||
|
||||
if (numFingers == 1) {
|
||||
if ((-(delY[0])) > (swipeSlopeIntolerance * (abs(
|
||||
delX[0]
|
||||
))) && abs(delY[0]) > minValue
|
||||
) {
|
||||
return SWIPE_1_UP
|
||||
}
|
||||
|
||||
if (((delY[0])) > (swipeSlopeIntolerance * (abs(
|
||||
delX[0]
|
||||
))) && abs(delY[0]) > minValue
|
||||
) {
|
||||
return SWIPE_1_DOWN
|
||||
}
|
||||
|
||||
if ((-(delX[0])) > (swipeSlopeIntolerance * (abs(
|
||||
delY[0]
|
||||
))) && abs(delX[0]) > minValue
|
||||
) {
|
||||
return SWIPE_1_LEFT
|
||||
}
|
||||
|
||||
if (((delX[0])) > (swipeSlopeIntolerance * (abs(
|
||||
delY[0]
|
||||
))) && abs(delX[0]) > minValue
|
||||
) {
|
||||
return SWIPE_1_RIGHT
|
||||
}
|
||||
// BLog.LOGE("initialT = ${initialT} , finalT = ${finalT} , result = ${finalT - initialT}")
|
||||
if (finalT - initialT < 300) {
|
||||
return CLICK_1
|
||||
} else if(finalT - initialT > doubleTapMaxDelayMillis) {
|
||||
return LONG_CLICK_1
|
||||
}
|
||||
}
|
||||
if (numFingers == 2) {
|
||||
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
|
||||
delX[0]
|
||||
))) && ((-delY[1]) > (swipeSlopeIntolerance * abs(
|
||||
delX[1]
|
||||
))) && (abs(delY[0]) > minValue || abs(delY[1]) > minValue)
|
||||
) {
|
||||
return SWIPE_2_UP
|
||||
}
|
||||
if (((delY[0]) > (swipeSlopeIntolerance * abs(
|
||||
delX[0]
|
||||
))) && ((delY[1]) > (swipeSlopeIntolerance * abs(
|
||||
delX[1]
|
||||
))) && (abs(delY[0]) > minValue || abs(delY[1]) > minValue)
|
||||
) {
|
||||
return SWIPE_2_DOWN
|
||||
}
|
||||
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
|
||||
delY[0]
|
||||
))) && ((-delX[1]) > (swipeSlopeIntolerance * abs(
|
||||
delY[1]
|
||||
))) && (abs(delX[0]) > minValue || abs(delX[1]) > minValue)
|
||||
) {
|
||||
return SWIPE_2_LEFT
|
||||
}
|
||||
if (((delX[0]) > (swipeSlopeIntolerance * abs(
|
||||
delY[0]
|
||||
))) && ((delX[1]) > (swipeSlopeIntolerance * abs(
|
||||
delY[1]
|
||||
))) && (abs(delX[0]) > minValue || abs(delX[1]) > minValue)
|
||||
) {
|
||||
return SWIPE_2_RIGHT
|
||||
}
|
||||
if (finalFingDist(0, 1) > 2 * (initialFingDist(0, 1))) {
|
||||
return UNPINCH_2
|
||||
}
|
||||
if (finalFingDist(0, 1) < 0.5 * (initialFingDist(0, 1))) {
|
||||
return PINCH_2
|
||||
}
|
||||
if (finalT - initialT < 300) {
|
||||
return CLICK_2
|
||||
} else if(finalT - initialT > doubleTapMaxDelayMillis) {
|
||||
return LONG_CLICK_2
|
||||
}
|
||||
}
|
||||
if (numFingers == 3) {
|
||||
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
|
||||
delX[0]
|
||||
)))
|
||||
&& ((-delY[1]) > (swipeSlopeIntolerance * abs(
|
||||
delX[1]
|
||||
)))
|
||||
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
|
||||
delX[2]
|
||||
))) && (abs(delY[0]) > minValue || abs(delY[1]) > minValue && abs(delY[2]) > minValue)
|
||||
) {
|
||||
return SWIPE_3_UP
|
||||
}
|
||||
if (((delY[0]) > (swipeSlopeIntolerance * abs(
|
||||
delX[0]
|
||||
)))
|
||||
&& ((delY[1]) > (swipeSlopeIntolerance * abs(
|
||||
delX[1]
|
||||
)))
|
||||
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
|
||||
delX[2]
|
||||
))) && (abs(delY[0]) > minValue || abs(delY[1]) > minValue && abs(delY[2]) > minValue)
|
||||
) {
|
||||
return SWIPE_3_DOWN
|
||||
}
|
||||
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
|
||||
delY[0]
|
||||
)))
|
||||
&& ((-delX[1]) > (swipeSlopeIntolerance * abs(
|
||||
delY[1]
|
||||
)))
|
||||
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
|
||||
delY[2]
|
||||
))) && (abs(delX[0]) > minValue || abs(delX[1]) > minValue && abs(delX[2]) > minValue)
|
||||
) {
|
||||
return SWIPE_3_LEFT
|
||||
}
|
||||
if (((delX[0]) > (swipeSlopeIntolerance * abs(
|
||||
delY[0]
|
||||
)))
|
||||
&& ((delX[1]) > (swipeSlopeIntolerance * abs(
|
||||
delY[1]
|
||||
)))
|
||||
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
|
||||
delY[2]
|
||||
))) && (abs(delX[0]) > minValue || abs(delX[1]) > minValue && abs(delX[2]) > minValue)
|
||||
) {
|
||||
return SWIPE_3_RIGHT
|
||||
}
|
||||
|
||||
if ((finalFingDist(0, 1) > 1.75 * (initialFingDist(0, 1)))
|
||||
&& (finalFingDist(1, 2) > 1.75 * (initialFingDist(1, 2)))
|
||||
&& (finalFingDist(2, 0) > 1.75 * (initialFingDist(2, 0)))
|
||||
) {
|
||||
return UNPINCH_3
|
||||
}
|
||||
if ((finalFingDist(0, 1) < 0.66 * (initialFingDist(0, 1)))
|
||||
&& (finalFingDist(1, 2) < 0.66 * (initialFingDist(1, 2)))
|
||||
&& (finalFingDist(2, 0) < 0.66 * (initialFingDist(2, 0)))
|
||||
) {
|
||||
return PINCH_3
|
||||
}
|
||||
|
||||
if (finalT - initialT < 300) {
|
||||
return CLICK_3
|
||||
} else if(finalT - initialT > doubleTapMaxDelayMillis) {
|
||||
return LONG_CLICK_3
|
||||
}
|
||||
}
|
||||
if (numFingers == 4) {
|
||||
if (((-delY[0]) > (swipeSlopeIntolerance * abs(
|
||||
delX[0]
|
||||
)))
|
||||
&& ((-delY[1]) > (swipeSlopeIntolerance * abs(
|
||||
delX[1]
|
||||
)))
|
||||
&& ((-delY[2]) > (swipeSlopeIntolerance * abs(
|
||||
delX[2]
|
||||
)))
|
||||
&& ((-delY[3]) > (swipeSlopeIntolerance * abs(
|
||||
delX[3]
|
||||
)))
|
||||
) {
|
||||
return SWIPE_4_UP
|
||||
}
|
||||
if (((delY[0]) > (swipeSlopeIntolerance * abs(
|
||||
delX[0]
|
||||
)))
|
||||
&& ((delY[1]) > (swipeSlopeIntolerance * abs(
|
||||
delX[1]
|
||||
)))
|
||||
&& ((delY[2]) > (swipeSlopeIntolerance * abs(
|
||||
delX[2]
|
||||
)))
|
||||
&& ((delY[3]) > (swipeSlopeIntolerance * abs(
|
||||
delX[3]
|
||||
)))
|
||||
) {
|
||||
return SWIPE_4_DOWN
|
||||
}
|
||||
if (((-delX[0]) > (swipeSlopeIntolerance * abs(
|
||||
delY[0]
|
||||
)))
|
||||
&& ((-delX[1]) > (swipeSlopeIntolerance * abs(
|
||||
delY[1]
|
||||
)))
|
||||
&& ((-delX[2]) > (swipeSlopeIntolerance * abs(
|
||||
delY[2]
|
||||
)))
|
||||
&& ((-delX[3]) > (swipeSlopeIntolerance * abs(
|
||||
delY[3]
|
||||
)))
|
||||
) {
|
||||
return SWIPE_4_LEFT
|
||||
}
|
||||
if (((delX[0]) > (swipeSlopeIntolerance * abs(
|
||||
delY[0]
|
||||
)))
|
||||
&& ((delX[1]) > (swipeSlopeIntolerance * abs(
|
||||
delY[1]
|
||||
)))
|
||||
&& ((delX[2]) > (swipeSlopeIntolerance * abs(
|
||||
delY[2]
|
||||
)))
|
||||
&& ((delX[3]) > (swipeSlopeIntolerance * abs(
|
||||
delY[3]
|
||||
)))
|
||||
) {
|
||||
return SWIPE_4_RIGHT
|
||||
}
|
||||
if ((finalFingDist(0, 1) > 1.5 * (initialFingDist(0, 1)))
|
||||
&& (finalFingDist(1, 2) > 1.5 * (initialFingDist(1, 2)))
|
||||
&& (finalFingDist(2, 3) > 1.5 * (initialFingDist(2, 3)))
|
||||
&& (finalFingDist(3, 0) > 1.5 * (initialFingDist(3, 0)))
|
||||
) {
|
||||
return UNPINCH_4
|
||||
}
|
||||
if ((finalFingDist(0, 1) < 0.8 * (initialFingDist(0, 1)))
|
||||
&& (finalFingDist(1, 2) < 0.8 * (initialFingDist(1, 2)))
|
||||
&& (finalFingDist(2, 3) < 0.8 * (initialFingDist(2, 3)))
|
||||
&& (finalFingDist(3, 0) < 0.8 * (initialFingDist(3, 0)))
|
||||
) {
|
||||
return PINCH_4
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private fun initialFingDist(fingNum1: Int, fingNum2: Int): Double {
|
||||
return sqrt(
|
||||
(initialX[fingNum1] - initialX[fingNum2]).pow(2.0) + (initialY[fingNum1] - initialY[fingNum2]).pow(
|
||||
2.0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun finalFingDist(fingNum1: Int, fingNum2: Int): Double {
|
||||
return sqrt(
|
||||
(finalX[fingNum1] - finalX[fingNum2]).pow(2.0) + (finalY[fingNum1] - finalY[fingNum2]).pow(
|
||||
2.0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val isDoubleTap: Boolean
|
||||
get() = if (initialT - prevFinalT < doubleTapMaxDelayMillis && finalT - initialT < doubleTapMaxDownMillis && prevFinalT - prevInitialT < doubleTapMaxDownMillis) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
inner class GestureType {
|
||||
var gestureFlag: Int = 0
|
||||
var gestureDuration: Long = 0
|
||||
|
||||
var gestureDistance: Double = 0.0
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val DEBUG: Boolean = true
|
||||
|
||||
// Finished gestures flags
|
||||
const val SWIPE_1_UP: Int = 11
|
||||
const val SWIPE_1_DOWN: Int = 12
|
||||
const val SWIPE_1_LEFT: Int = 13
|
||||
const val SWIPE_1_RIGHT: Int = 14
|
||||
const val SWIPE_2_UP: Int = 21
|
||||
const val SWIPE_2_DOWN: Int = 22
|
||||
const val SWIPE_2_LEFT: Int = 23
|
||||
const val SWIPE_2_RIGHT: Int = 24
|
||||
const val SWIPE_3_UP: Int = 31
|
||||
const val SWIPE_3_DOWN: Int = 32
|
||||
const val SWIPE_3_LEFT: Int = 33
|
||||
const val SWIPE_3_RIGHT: Int = 34
|
||||
const val SWIPE_4_UP: Int = 41
|
||||
const val SWIPE_4_DOWN: Int = 42
|
||||
const val SWIPE_4_LEFT: Int = 43
|
||||
const val SWIPE_4_RIGHT: Int = 44
|
||||
const val PINCH_2: Int = 25
|
||||
const val UNPINCH_2: Int = 26
|
||||
const val PINCH_3: Int = 35
|
||||
const val UNPINCH_3: Int = 36
|
||||
const val PINCH_4: Int = 45
|
||||
const val UNPINCH_4: Int = 46
|
||||
|
||||
const val DOUBLE_TAP_1: Int = 107
|
||||
|
||||
const val CLICK_1 = 19001
|
||||
const val CLICK_2 = 19002
|
||||
const val CLICK_3 = 19003
|
||||
|
||||
|
||||
|
||||
const val LONG_CLICK_1 = 29001
|
||||
const val LONG_CLICK_2 = 29002
|
||||
const val LONG_CLICK_3 = 29003
|
||||
|
||||
//Ongoing gesture flags
|
||||
const val SWIPING_1_UP: Int = 101
|
||||
const val SWIPING_1_DOWN: Int = 102
|
||||
const val SWIPING_1_LEFT: Int = 103
|
||||
const val SWIPING_1_RIGHT: Int = 104
|
||||
const val SWIPING_2_UP: Int = 201
|
||||
const val SWIPING_2_DOWN: Int = 202
|
||||
const val SWIPING_2_LEFT: Int = 203
|
||||
const val SWIPING_2_RIGHT: Int = 204
|
||||
const val PINCHING: Int = 205
|
||||
const val UNPINCHING: Int = 206
|
||||
private const val TAG = "GestureAnalyser"
|
||||
}
|
||||
}
|
||||
|
||||
///https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/getUpdates
|
||||
|
||||
class SimpleFingerGestures : OnTouchListener {
|
||||
private var debug = BuildConfig.DEBUG
|
||||
var consumeTouchEvents: Boolean = false
|
||||
var screenHeight : Int = 100
|
||||
protected var tracking: BooleanArray = booleanArrayOf(false, false, false, false, false)
|
||||
private var ga: GestureAnalyser
|
||||
private var onFingerGestureListener: OnFingerGestureListener? = null
|
||||
var targetView : View? = null
|
||||
var mContext : Context? = null
|
||||
constructor(context : Context,targetView : View, onFingerGestureListener: OnFingerGestureListener) {
|
||||
this.mContext = context
|
||||
|
||||
|
||||
this.targetView = targetView
|
||||
ga = GestureAnalyser()
|
||||
this.mContext?.resources?.displayMetrics?.let {
|
||||
screenHeight = (it.heightPixels * 0.2).toInt()
|
||||
ga.minValue = Math.max(screenHeight, 100)
|
||||
}
|
||||
this.onFingerGestureListener = onFingerGestureListener
|
||||
this.targetView?.setOnClickListener { onFingerGestureListener.onClick(it,1) }
|
||||
this.targetView?.setOnLongClickListener { onFingerGestureListener.onLongPress(it,1) }
|
||||
}
|
||||
/**
|
||||
* Constructor that creates an internal [in.championswimmer.sfg.lib.GestureAnalyser] object as well
|
||||
*/
|
||||
constructor() {
|
||||
ga = GestureAnalyser()
|
||||
}
|
||||
|
||||
constructor(
|
||||
swipeSlopeIntolerance: Int,
|
||||
doubleTapMaxDelayMillis: Int,
|
||||
doubleTapMaxDownMillis: Int
|
||||
) {
|
||||
ga = GestureAnalyser(swipeSlopeIntolerance, doubleTapMaxDelayMillis, doubleTapMaxDownMillis)
|
||||
}
|
||||
|
||||
fun setDebug(debug: Boolean) {
|
||||
this.debug = debug
|
||||
}
|
||||
|
||||
constructor(omfgl: OnFingerGestureListener?) {
|
||||
ga = GestureAnalyser()
|
||||
setOnFingerGestureListener(omfgl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when multi-finger gestures take place
|
||||
*
|
||||
*
|
||||
* <br></br>
|
||||
*
|
||||
*
|
||||
* For the callbacks implemented via this, check the interface [in.championswimmer.sfg.lib.SimpleFingerGestures.OnFingerGestureListener]
|
||||
*
|
||||
*
|
||||
* @param omfgl The callback that will run
|
||||
*/
|
||||
fun setOnFingerGestureListener(omfgl: OnFingerGestureListener?) {
|
||||
onFingerGestureListener = omfgl
|
||||
}
|
||||
|
||||
|
||||
override fun onTouch(view: View, ev: MotionEvent): Boolean {
|
||||
// if (debug) Log.d(TAG, "onTouch")
|
||||
when (ev.action and MotionEvent.ACTION_MASK) {
|
||||
MotionEvent.ACTION_DOWN -> {
|
||||
// if (debug) Log.d(TAG, "ACTION_DOWN")
|
||||
startTracking(0)
|
||||
ga.trackGesture(ev)
|
||||
return consumeTouchEvents
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
// if (debug) Log.d(TAG, "ACTION_UP")
|
||||
if (tracking[0]) {
|
||||
doCallBack(view,ga.getGesture(ev))
|
||||
}
|
||||
stopTracking(0)
|
||||
ga.untrackGesture()
|
||||
return consumeTouchEvents
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||
// if (debug) Log.d(TAG, "ACTION_POINTER_DOWN" + " " + "num" + ev.pointerCount)
|
||||
startTracking(ev.pointerCount - 1)
|
||||
ga.trackGesture(ev)
|
||||
return consumeTouchEvents
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_POINTER_UP -> {
|
||||
// if (debug) Log.d(TAG, "ACTION_POINTER_UP" + " " + "num" + ev.pointerCount)
|
||||
if (tracking[1]) {
|
||||
doCallBack(view,ga.getGesture(ev))
|
||||
}
|
||||
stopTracking(ev.pointerCount - 1)
|
||||
ga.untrackGesture()
|
||||
return consumeTouchEvents
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
// if (debug) Log.d(TAG, "ACTION_CANCEL")
|
||||
return true
|
||||
}
|
||||
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
// if (debug) Log.d(TAG, "ACTION_MOVE")
|
||||
return consumeTouchEvents
|
||||
}
|
||||
}
|
||||
return consumeTouchEvents
|
||||
}
|
||||
|
||||
private fun doCallBack(targetView : View, mGt: GestureAnalyser.GestureType) {
|
||||
when (mGt.gestureFlag) {
|
||||
GestureAnalyser.SWIPE_1_UP -> onFingerGestureListener!!.onSwipeUp(
|
||||
targetView,
|
||||
1,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_1_DOWN -> onFingerGestureListener!!.onSwipeDown(
|
||||
targetView,
|
||||
1,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_1_LEFT -> onFingerGestureListener!!.onSwipeLeft(
|
||||
targetView,
|
||||
1,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_1_RIGHT -> onFingerGestureListener!!.onSwipeRight(
|
||||
targetView,
|
||||
1,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_2_UP -> onFingerGestureListener!!.onSwipeUp(
|
||||
targetView,
|
||||
2,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_2_DOWN -> onFingerGestureListener!!.onSwipeDown(
|
||||
targetView,
|
||||
2,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_2_LEFT -> onFingerGestureListener!!.onSwipeLeft(
|
||||
targetView,
|
||||
2,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_2_RIGHT -> onFingerGestureListener!!.onSwipeRight(
|
||||
targetView,
|
||||
2,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.PINCH_2 -> onFingerGestureListener!!.onPinch(
|
||||
targetView,
|
||||
2,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.UNPINCH_2 -> onFingerGestureListener!!.onUnpinch(
|
||||
targetView,
|
||||
2,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_3_UP -> onFingerGestureListener!!.onSwipeUp(
|
||||
targetView,
|
||||
3,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_3_DOWN -> onFingerGestureListener!!.onSwipeDown(
|
||||
targetView,
|
||||
3,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_3_LEFT -> onFingerGestureListener!!.onSwipeLeft(
|
||||
targetView,
|
||||
3,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_3_RIGHT -> onFingerGestureListener!!.onSwipeRight(
|
||||
targetView,
|
||||
3,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.PINCH_3 -> onFingerGestureListener!!.onPinch(
|
||||
targetView,
|
||||
3,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.UNPINCH_3 -> onFingerGestureListener!!.onUnpinch(
|
||||
targetView,
|
||||
3,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_4_UP -> onFingerGestureListener!!.onSwipeUp(
|
||||
targetView,
|
||||
4,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_4_DOWN -> onFingerGestureListener!!.onSwipeDown(
|
||||
targetView,
|
||||
4,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_4_LEFT -> onFingerGestureListener!!.onSwipeLeft(
|
||||
targetView,
|
||||
4,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.SWIPE_4_RIGHT -> onFingerGestureListener!!.onSwipeRight(
|
||||
targetView,
|
||||
4,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.PINCH_4 -> onFingerGestureListener!!.onPinch(
|
||||
targetView,
|
||||
4,
|
||||
mGt.gestureDuration,
|
||||
mGt.gestureDistance
|
||||
)
|
||||
|
||||
GestureAnalyser.UNPINCH_4 -> {
|
||||
onFingerGestureListener!!.onUnpinch(targetView,4, mGt.gestureDuration, mGt.gestureDistance)
|
||||
// onFingerGestureListener!!.onDoubleTap(1)
|
||||
}
|
||||
GestureAnalyser.CLICK_1 -> {
|
||||
// BLog.LOGE("GestureAnalyser.CLICK_1")
|
||||
onFingerGestureListener!!.onClick(targetView, 1)
|
||||
// onFingerGestureListener!!.onDoubleTap(1)
|
||||
}
|
||||
GestureAnalyser.CLICK_2 -> {
|
||||
// BLog.LOGE("GestureAnalyser.CLICK_2")
|
||||
onFingerGestureListener!!.onClick(targetView, 2)
|
||||
// onFingerGestureListener!!.onDoubleTap(1)
|
||||
}
|
||||
GestureAnalyser.CLICK_3 -> {
|
||||
// BLog.LOGE("GestureAnalyser.CLICK_3")
|
||||
onFingerGestureListener!!.onClick(targetView, 3)
|
||||
// onFingerGestureListener!!.onDoubleTap(1)
|
||||
}
|
||||
// GestureAnalyser.CLICK_2 -> {
|
||||
// onFingerGestureListener!!.onUnpinch(targetView,4, mGt.gestureDuration, mGt.gestureDistance)
|
||||
//// onFingerGestureListener!!.onDoubleTap(1)
|
||||
// }
|
||||
// GestureAnalyser.CLICK_3 -> {
|
||||
// onFingerGestureListener!!.onUnpinch(targetView,4, mGt.gestureDuration, mGt.gestureDistance)
|
||||
//// onFingerGestureListener!!.onDoubleTap(1)
|
||||
// }
|
||||
|
||||
GestureAnalyser.LONG_CLICK_1 -> {
|
||||
// BLog.LOGE("GestureAnalyser.LONG_CLICK_1")
|
||||
onFingerGestureListener!!.onLongPress(targetView,1)
|
||||
}
|
||||
GestureAnalyser.LONG_CLICK_2 -> {
|
||||
// BLog.LOGE("GestureAnalyser.LONG_CLICK_2")
|
||||
onFingerGestureListener!!.onLongPress(targetView,2)
|
||||
}
|
||||
GestureAnalyser.LONG_CLICK_3 -> {
|
||||
// BLog.LOGE("GestureAnalyser.LONG_CLICK_3")
|
||||
onFingerGestureListener!!.onLongPress(targetView,3)
|
||||
}
|
||||
|
||||
GestureAnalyser.DOUBLE_TAP_1 -> onFingerGestureListener!!.onDoubleTap(targetView,1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTracking(nthPointer: Int) {
|
||||
for (i in 0..nthPointer) {
|
||||
tracking[i] = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopTracking(nthPointer: Int) {
|
||||
for (i in nthPointer until tracking.size) {
|
||||
tracking[i] = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Interface definition for the callback to be invoked when 2-finger gestures are performed
|
||||
*/
|
||||
interface OnFingerGestureListener {
|
||||
/**
|
||||
* Called when user swipes **up** with two fingers
|
||||
*
|
||||
* @param fingers number of fingers involved in this gesture
|
||||
* @param gestureDuration duration in milliSeconds
|
||||
* @return
|
||||
*/
|
||||
fun onSwipeUp(targetView : View, fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
|
||||
|
||||
/**
|
||||
* Called when user swipes **down** with two fingers
|
||||
*
|
||||
* @param fingers number of fingers involved in this gesture
|
||||
* @param gestureDuration duration in milliSeconds
|
||||
* @return
|
||||
*/
|
||||
fun onSwipeDown(targetView : View,fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
|
||||
|
||||
/**
|
||||
* Called when user swipes **left** with two fingers
|
||||
*
|
||||
* @param fingers number of fingers involved in this gesture
|
||||
* @param gestureDuration duration in milliSeconds
|
||||
* @return
|
||||
*/
|
||||
fun onSwipeLeft(targetView : View,fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
|
||||
|
||||
/**
|
||||
* Called when user swipes **right** with two fingers
|
||||
*
|
||||
* @param fingers number of fingers involved in this gesture
|
||||
* @param gestureDuration duration in milliSeconds
|
||||
* @return
|
||||
*/
|
||||
fun onSwipeRight(targetView : View,fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
|
||||
|
||||
/**
|
||||
* Called when user **pinches** with two fingers (bring together)
|
||||
*
|
||||
* @param fingers number of fingers involved in this gesture
|
||||
* @param gestureDuration duration in milliSeconds
|
||||
* @return
|
||||
*/
|
||||
fun onPinch(targetView : View,fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
|
||||
|
||||
/**
|
||||
* Called when user **un-pinches** with two fingers (take apart)
|
||||
*
|
||||
* @param fingers number of fingers involved in this gesture
|
||||
* @param gestureDuration duration in milliSeconds
|
||||
* @return
|
||||
*/
|
||||
fun onUnpinch(targetView : View,fingers: Int, gestureDuration: Long, gestureDistance: Double): Boolean
|
||||
|
||||
fun onDoubleTap(targetView : View,fingers: Int): Boolean
|
||||
fun onLongPress(targetView : View, fingers: Int): Boolean
|
||||
fun onClick(targetView : View, fingers: Int): Boolean
|
||||
}
|
||||
|
||||
companion object {
|
||||
// Will see if these need to be used. For now just returning duration in milliS
|
||||
const val GESTURE_SPEED_SLOW: Long = 1500
|
||||
const val GESTURE_SPEED_MEDIUM: Long = 1000
|
||||
const val GESTURE_SPEED_FAST: Long = 500
|
||||
private const val TAG = "SimpleFingerGestures"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
package bums.lunatic.launcher.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapShader
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.ColorFilter
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.Outline
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.RectF
|
||||
import android.graphics.Shader
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewOutlineProvider
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.annotation.RequiresApi
|
||||
import bums.lunatic.launcher.R
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class CircleImageView : androidx.appcompat.widget.AppCompatImageView {
|
||||
private val mDrawableRect = RectF()
|
||||
private val mBorderRect = RectF()
|
||||
|
||||
private val mShaderMatrix: Matrix = Matrix()
|
||||
private val mBitmapPaint: Paint = Paint()
|
||||
private val mBorderPaint: Paint = Paint()
|
||||
private val mLabelPaint: Paint = Paint()
|
||||
private val mCircleBackgroundPaint: Paint = Paint()
|
||||
|
||||
private var mBorderColor = DEFAULT_BORDER_COLOR
|
||||
private var mBorderWidth = DEFAULT_BORDER_WIDTH
|
||||
private var mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR
|
||||
private var mImageAlpha = DEFAULT_IMAGE_ALPHA
|
||||
|
||||
private var mBitmap: Bitmap? = null
|
||||
private var mBitmapCanvas: Canvas? = null
|
||||
|
||||
private var mDrawableRadius = 0f
|
||||
private var mBorderRadius = 0f
|
||||
|
||||
private var mColorFilter: ColorFilter? = null
|
||||
|
||||
private var mInitialized = false
|
||||
private var mRebuildShader = false
|
||||
private var mDrawableDirty = false
|
||||
|
||||
private var mBorderOverlay = false
|
||||
private var mDisableCircularTransformation = false
|
||||
|
||||
private var label : String = ""
|
||||
constructor(context: Context) : super(context) {
|
||||
init()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyle: Int = 0) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyle
|
||||
) {
|
||||
val a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle, 0)
|
||||
|
||||
mBorderWidth = a.getDimensionPixelSize(
|
||||
R.styleable.CircleImageView_civ_border_width,
|
||||
DEFAULT_BORDER_WIDTH
|
||||
)
|
||||
mBorderColor =
|
||||
a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR)
|
||||
mBorderOverlay =
|
||||
a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)
|
||||
mCircleBackgroundColor = a.getColor(
|
||||
R.styleable.CircleImageView_civ_circle_background_color,
|
||||
DEFAULT_CIRCLE_BACKGROUND_COLOR
|
||||
)
|
||||
|
||||
label = a.getString(R.styleable.CircleImageView_civ_label) ?: ""
|
||||
|
||||
a.recycle()
|
||||
|
||||
init()
|
||||
}
|
||||
|
||||
private fun init() {
|
||||
mInitialized = true
|
||||
|
||||
super.setScaleType(SCALE_TYPE)
|
||||
|
||||
mBitmapPaint.setAntiAlias(true)
|
||||
mBitmapPaint.setDither(true)
|
||||
mBitmapPaint.setFilterBitmap(true)
|
||||
mBitmapPaint.setAlpha(mImageAlpha)
|
||||
mBitmapPaint.setColorFilter(mColorFilter)
|
||||
|
||||
mBorderPaint.setStyle(Paint.Style.STROKE)
|
||||
mBorderPaint.setAntiAlias(true)
|
||||
mBorderPaint.setColor(mBorderColor)
|
||||
mBorderPaint.setStrokeWidth(mBorderWidth.toFloat())
|
||||
|
||||
mCircleBackgroundPaint.setStyle(Paint.Style.FILL)
|
||||
mCircleBackgroundPaint.setAntiAlias(true)
|
||||
mCircleBackgroundPaint.setColor(mCircleBackgroundColor)
|
||||
|
||||
mLabelPaint.color = Color.WHITE
|
||||
mLabelPaint.isFakeBoldText = true
|
||||
mLabelPaint.textAlign = Paint.Align.CENTER
|
||||
mLabelPaint.textSize = height * 0.2f
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
outlineProvider = OutlineProvider()
|
||||
}
|
||||
}
|
||||
|
||||
override fun setScaleType(scaleType: ScaleType) {
|
||||
// require(scaleType == SCALE_TYPE) { String.format("ScaleType %s not supported.", scaleType) }
|
||||
super.setScaleType(scaleType)
|
||||
}
|
||||
|
||||
override fun setAdjustViewBounds(adjustViewBounds: Boolean) {
|
||||
// require(!adjustViewBounds) { "adjustViewBounds not supported." }
|
||||
super.setAdjustViewBounds(adjustViewBounds)
|
||||
}
|
||||
|
||||
@SuppressLint("CanvasSize")
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
if (mDisableCircularTransformation) {
|
||||
super.onDraw(canvas)
|
||||
return
|
||||
}
|
||||
|
||||
if (mCircleBackgroundColor != Color.TRANSPARENT) {
|
||||
canvas.drawCircle(
|
||||
mDrawableRect.centerX(),
|
||||
mDrawableRect.centerY(),
|
||||
mDrawableRadius,
|
||||
mCircleBackgroundPaint
|
||||
)
|
||||
}
|
||||
|
||||
if (mBitmap != null) {
|
||||
if (mDrawableDirty && mBitmapCanvas != null) {
|
||||
mDrawableDirty = false
|
||||
val drawable = drawable
|
||||
drawable.setBounds(0, 0, mBitmapCanvas!!.getWidth(), mBitmapCanvas!!.getHeight())
|
||||
drawable.draw(mBitmapCanvas!!)
|
||||
}
|
||||
|
||||
if (mRebuildShader) {
|
||||
mRebuildShader = false
|
||||
|
||||
val bitmapShader =
|
||||
BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
|
||||
bitmapShader.setLocalMatrix(mShaderMatrix)
|
||||
|
||||
mBitmapPaint.setShader(bitmapShader)
|
||||
}
|
||||
|
||||
canvas.drawCircle(
|
||||
mDrawableRect.centerX(),
|
||||
mDrawableRect.centerY(),
|
||||
mDrawableRadius,
|
||||
mBitmapPaint
|
||||
)
|
||||
}
|
||||
|
||||
if (mBorderWidth > 0) {
|
||||
canvas.drawCircle(
|
||||
mBorderRect.centerX(),
|
||||
mBorderRect.centerY(),
|
||||
mBorderRadius,
|
||||
mBorderPaint
|
||||
)
|
||||
}
|
||||
|
||||
if (label.length > 0) {
|
||||
canvas.drawText(label.toUpperCase(),mBorderRect.centerX(),mBorderRect.height() * 0.98f, mLabelPaint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidateDrawable(@NonNull dr: Drawable) {
|
||||
mDrawableDirty = true
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
updateDimensions()
|
||||
mLabelPaint.textSize = h * 0.2f
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.setPadding(left, top, right, bottom)
|
||||
updateDimensions()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {
|
||||
super.setPaddingRelative(start, top, end, bottom)
|
||||
updateDimensions()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var borderColor: Int
|
||||
get() = mBorderColor
|
||||
set(borderColor) {
|
||||
if (borderColor == mBorderColor) {
|
||||
return
|
||||
}
|
||||
|
||||
mBorderColor = borderColor
|
||||
mBorderPaint.setColor(borderColor)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var circleBackgroundColor: Int
|
||||
get() = mCircleBackgroundColor
|
||||
set(circleBackgroundColor) {
|
||||
if (circleBackgroundColor == mCircleBackgroundColor) {
|
||||
return
|
||||
}
|
||||
|
||||
mCircleBackgroundColor = circleBackgroundColor
|
||||
mCircleBackgroundPaint.setColor(circleBackgroundColor)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
|
||||
@Deprecated("Use {@link #setCircleBackgroundColor(int)} instead")
|
||||
fun setCircleBackgroundColorResource(@ColorRes circleBackgroundRes: Int) {
|
||||
circleBackgroundColor = context.resources.getColor(circleBackgroundRes)
|
||||
}
|
||||
|
||||
var borderWidth: Int
|
||||
get() = mBorderWidth
|
||||
set(borderWidth) {
|
||||
if (borderWidth == mBorderWidth) {
|
||||
return
|
||||
}
|
||||
|
||||
mBorderWidth = borderWidth
|
||||
mBorderPaint.setStrokeWidth(borderWidth.toFloat())
|
||||
updateDimensions()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var isBorderOverlay: Boolean
|
||||
get() = mBorderOverlay
|
||||
set(borderOverlay) {
|
||||
if (borderOverlay == mBorderOverlay) {
|
||||
return
|
||||
}
|
||||
|
||||
mBorderOverlay = borderOverlay
|
||||
updateDimensions()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var isDisableCircularTransformation: Boolean
|
||||
get() = mDisableCircularTransformation
|
||||
set(disableCircularTransformation) {
|
||||
if (disableCircularTransformation == mDisableCircularTransformation) {
|
||||
return
|
||||
}
|
||||
|
||||
mDisableCircularTransformation = disableCircularTransformation
|
||||
|
||||
if (disableCircularTransformation) {
|
||||
mBitmap = null
|
||||
mBitmapCanvas = null
|
||||
mBitmapPaint.setShader(null)
|
||||
} else {
|
||||
initializeBitmap()
|
||||
}
|
||||
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setImageBitmap(bm: Bitmap) {
|
||||
super.setImageBitmap(bm)
|
||||
initializeBitmap()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setImageDrawable(drawable: Drawable?) {
|
||||
super.setImageDrawable(drawable)
|
||||
initializeBitmap()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setImageResource(@DrawableRes resId: Int) {
|
||||
super.setImageResource(resId)
|
||||
initializeBitmap()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setImageURI(uri: Uri?) {
|
||||
super.setImageURI(uri)
|
||||
initializeBitmap()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun setImageAlpha(alpha: Int) {
|
||||
var alpha = alpha
|
||||
alpha = alpha and 0xFF
|
||||
|
||||
if (alpha == mImageAlpha) {
|
||||
return
|
||||
}
|
||||
|
||||
mImageAlpha = alpha
|
||||
|
||||
// This might be called during ImageView construction before
|
||||
// member initialization has finished on API level >= 16.
|
||||
if (mInitialized) {
|
||||
mBitmapPaint.setAlpha(alpha)
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getImageAlpha(): Int {
|
||||
return mImageAlpha
|
||||
}
|
||||
|
||||
override fun setColorFilter(cf: ColorFilter) {
|
||||
if (cf === mColorFilter) {
|
||||
return
|
||||
}
|
||||
|
||||
mColorFilter = cf
|
||||
|
||||
// This might be called during ImageView construction before
|
||||
// member initialization has finished on API level <= 19.
|
||||
if (mInitialized) {
|
||||
mBitmapPaint.setColorFilter(cf)
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getColorFilter(): ColorFilter {
|
||||
return mColorFilter ?: ColorFilter()
|
||||
}
|
||||
|
||||
private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {
|
||||
if (drawable == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (drawable is BitmapDrawable) {
|
||||
return drawable.bitmap
|
||||
}
|
||||
|
||||
try {
|
||||
val bitmap = if (drawable is ColorDrawable) {
|
||||
Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG)
|
||||
} else {
|
||||
Bitmap.createBitmap(
|
||||
drawable.intrinsicWidth,
|
||||
drawable.intrinsicHeight,
|
||||
BITMAP_CONFIG
|
||||
)
|
||||
}
|
||||
|
||||
val canvas: Canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight())
|
||||
drawable.draw(canvas)
|
||||
return bitmap
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeBitmap() {
|
||||
mBitmap = getBitmapFromDrawable(drawable)
|
||||
|
||||
if (mBitmap != null && mBitmap!!.isMutable) {
|
||||
mBitmapCanvas = Canvas(mBitmap!!)
|
||||
} else {
|
||||
mBitmapCanvas = null
|
||||
}
|
||||
|
||||
if (!mInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
if (mBitmap != null) {
|
||||
updateShaderMatrix()
|
||||
} else {
|
||||
mBitmapPaint.setShader(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDimensions() {
|
||||
mBorderRect.set(calculateBounds())
|
||||
mBorderRadius = min(
|
||||
((mBorderRect.height() - mBorderWidth) / 2.0f).toDouble(),
|
||||
((mBorderRect.width() - mBorderWidth) / 2.0f).toDouble()
|
||||
)
|
||||
.toFloat()
|
||||
|
||||
mDrawableRect.set(mBorderRect)
|
||||
if (!mBorderOverlay && mBorderWidth > 0) {
|
||||
mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f)
|
||||
}
|
||||
mDrawableRadius = min(
|
||||
(mDrawableRect.height() / 2.0f).toDouble(),
|
||||
(mDrawableRect.width() / 2.0f).toDouble()
|
||||
)
|
||||
.toFloat()
|
||||
|
||||
updateShaderMatrix()
|
||||
}
|
||||
|
||||
private fun calculateBounds(): RectF {
|
||||
val availableWidth = width - paddingLeft - paddingRight
|
||||
val availableHeight = height - paddingTop - paddingBottom
|
||||
|
||||
val sideLength =
|
||||
min(availableWidth.toDouble(), availableHeight.toDouble()).toInt()
|
||||
|
||||
val left = paddingLeft + (availableWidth - sideLength) / 2f
|
||||
val top = paddingTop + (availableHeight - sideLength) / 2f
|
||||
|
||||
return RectF(left, top, left + sideLength, top + sideLength)
|
||||
}
|
||||
|
||||
private fun updateShaderMatrix() {
|
||||
if (mBitmap == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val scale: Float
|
||||
var dx = 0f
|
||||
var dy = 0f
|
||||
|
||||
mShaderMatrix.set(null)
|
||||
|
||||
val bitmapHeight = mBitmap!!.height
|
||||
val bitmapWidth = mBitmap!!.width
|
||||
|
||||
if (bitmapWidth * mDrawableRect.height() > mDrawableRect.width() * bitmapHeight) {
|
||||
scale = mDrawableRect.height() / bitmapHeight.toFloat()
|
||||
dx = (mDrawableRect.width() - bitmapWidth * scale) * 0.5f
|
||||
} else {
|
||||
scale = mDrawableRect.width() / bitmapWidth.toFloat()
|
||||
dy = (mDrawableRect.height() - bitmapHeight * scale) * 0.5f
|
||||
}
|
||||
|
||||
mShaderMatrix.setScale(scale, scale)
|
||||
mShaderMatrix.postTranslate(
|
||||
(dx + 0.5f).toInt() + mDrawableRect.left,
|
||||
(dy + 0.5f).toInt() + mDrawableRect.top
|
||||
)
|
||||
|
||||
mRebuildShader = true
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (mDisableCircularTransformation) {
|
||||
return super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
return inTouchableArea(event.x, event.y) && super.onTouchEvent(event)
|
||||
}
|
||||
|
||||
private fun inTouchableArea(x: Float, y: Float): Boolean {
|
||||
if (mBorderRect.isEmpty) {
|
||||
return true
|
||||
}
|
||||
|
||||
return (x - mBorderRect.centerX()).pow(2.0F) + (y - mBorderRect.centerY()).pow(2.0F) <= mBorderRadius.pow(2.0F)
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
private inner class OutlineProvider : ViewOutlineProvider() {
|
||||
override fun getOutline(view: View?, outline: Outline) {
|
||||
if (mDisableCircularTransformation) {
|
||||
BACKGROUND.getOutline(view, outline)
|
||||
} else {
|
||||
val bounds: Rect = Rect()
|
||||
mBorderRect.roundOut(bounds)
|
||||
outline.setRoundRect(bounds, bounds.width() / 2.0f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val SCALE_TYPE = ScaleType.CENTER_CROP
|
||||
|
||||
private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888
|
||||
private const val COLORDRAWABLE_DIMENSION = 2
|
||||
|
||||
private const val DEFAULT_BORDER_WIDTH = 0
|
||||
private val DEFAULT_BORDER_COLOR: Int = Color.BLACK
|
||||
private val DEFAULT_CIRCLE_BACKGROUND_COLOR: Int = Color.TRANSPARENT
|
||||
private const val DEFAULT_IMAGE_ALPHA = 255
|
||||
private const val DEFAULT_BORDER_OVERLAY = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package bums.lunatic.launcher.view
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.style.AbsoluteSizeSpan
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
|
||||
class DateTimeView : AppCompatTextView {
|
||||
lateinit var mHandler : Handler
|
||||
val simpleTimeFormat = SimpleDateFormat("a HH:mm:ss")
|
||||
val simpleDateFormat = SimpleDateFormat("yyyy년 MM월 W주차 dd일 E요일")
|
||||
var runable = {
|
||||
setTime()
|
||||
}
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
init {
|
||||
mHandler = Handler(Looper.getMainLooper())
|
||||
mHandler.postDelayed(runable, DateTimeView.DALEY)
|
||||
}
|
||||
|
||||
val spannableBuilder = SpannableStringBuilder()
|
||||
|
||||
private fun setTime() {
|
||||
spannableBuilder.clear()
|
||||
val now = Date(System.currentTimeMillis())
|
||||
val time = SpannableString(simpleTimeFormat.format(now))
|
||||
val date = SpannableString(simpleDateFormat.format(now))
|
||||
spannableBuilder.append(time)
|
||||
spannableBuilder.setSpan(AbsoluteSizeSpan(48,true), 0, time.length , Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
spannableBuilder.append("\n")
|
||||
spannableBuilder.append(date)
|
||||
var start = time.length + "\n".length
|
||||
spannableBuilder.setSpan(AbsoluteSizeSpan(30,true), start , start+date.length , Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
mHandler.removeCallbacks(runable)
|
||||
setText(spannableBuilder)
|
||||
mHandler.postDelayed(runable, DateTimeView.DALEY)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DALEY = 500L
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package bums.lunatic.launcher.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.View.OnClickListener
|
||||
import android.view.ViewGroup
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TableLayout
|
||||
import android.widget.TableRow
|
||||
|
||||
|
||||
class TableRadioGroup : TableLayout {
|
||||
var checkedRadioButtonId: Int = -1
|
||||
private set
|
||||
private var onCheckedChangeListener: OnCheckedChangeListener? = null
|
||||
private var maxColumns = -1 // Maximum number of columns (-1 for unlimited)
|
||||
private var maxRows = -1 // Maximum number of rows (-1 for unlimited)
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
fun setMaxColumns(maxColumns: Int) {
|
||||
this.maxColumns = maxColumns
|
||||
}
|
||||
|
||||
fun setMaxRows(maxRows: Int) {
|
||||
this.maxRows = maxRows
|
||||
}
|
||||
|
||||
override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) {
|
||||
if (child is TableRow) {
|
||||
val numChildren = child.childCount
|
||||
for (i in 0 until numChildren) {
|
||||
val view = child.getChildAt(i)
|
||||
if (view is RadioButton) {
|
||||
setupRadioButton(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
super.addView(child, index, params)
|
||||
|
||||
// Adjust the number of rows and columns if necessary
|
||||
adjustTableLayout()
|
||||
}
|
||||
|
||||
private fun setupRadioButton(radioButton: RadioButton?) {
|
||||
if (radioButton == null) {
|
||||
return
|
||||
}
|
||||
|
||||
radioButton.setOnClickListener(OnClickListener {
|
||||
if (checkedRadioButtonId != -1) {
|
||||
setCheckedStateForView(checkedRadioButtonId, false)
|
||||
}
|
||||
val id = radioButton.id
|
||||
setCheckedStateForView(id, true)
|
||||
checkedRadioButtonId = id
|
||||
if (onCheckedChangeListener != null) {
|
||||
onCheckedChangeListener!!.onCheckedChanged(
|
||||
this@TableRadioGroup,
|
||||
checkedRadioButtonId
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setCheckedStateForView(viewId: Int, checked: Boolean) {
|
||||
val checkedView = findViewById<View>(viewId)
|
||||
if (checkedView != null && checkedView is RadioButton) {
|
||||
checkedView.isChecked = checked
|
||||
}
|
||||
}
|
||||
|
||||
fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) {
|
||||
onCheckedChangeListener = listener
|
||||
}
|
||||
|
||||
fun clearCheck() {
|
||||
setCheckedStateForView(checkedRadioButtonId, false)
|
||||
checkedRadioButtonId = -1
|
||||
}
|
||||
|
||||
private fun adjustTableLayout() {
|
||||
if (maxColumns > 0 && maxRows > 0) {
|
||||
val childCount = childCount
|
||||
var rowCount = 0
|
||||
var currentRow: TableRow? = null
|
||||
for (i in 0 until childCount) {
|
||||
val child = getChildAt(i)
|
||||
if (child is TableRow) {
|
||||
currentRow = child
|
||||
rowCount++
|
||||
} else if (currentRow != null) {
|
||||
val columnCount = currentRow.childCount
|
||||
if (columnCount >= maxColumns) {
|
||||
// Create a new row if the maximum column count is reached
|
||||
if (rowCount < maxRows) {
|
||||
val newRow = TableRow(context)
|
||||
super.addView(newRow, getChildIndex(currentRow) + 1)
|
||||
currentRow = newRow
|
||||
rowCount++
|
||||
} else {
|
||||
// Remove extra children that exceed the maximum row count
|
||||
removeView(child)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChildIndex(child: View): Int {
|
||||
val childCount = childCount
|
||||
for (i in 0 until childCount) {
|
||||
if (getChildAt(i) === child) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
interface OnCheckedChangeListener {
|
||||
fun onCheckedChanged(group: TableRadioGroup?, checkedId: Int)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.os.Build
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.apps.AppDrawer.Companion.appName
|
||||
import bums.lunatic.launcher.apps.AppDrawer.Companion.getCategory
|
||||
import bums.lunatic.launcher.apps.normalize
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.utils.AlphabetToChosungMap
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import io.realm.kotlin.ext.query
|
||||
|
||||
class AppInfoGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "AppInfoGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
override fun realWork(): Result {
|
||||
try {
|
||||
|
||||
var packageManager = lActivity?.packageManager
|
||||
var packageInfoList: MutableList<ResolveInfo> = mutableListOf()
|
||||
packageInfoList = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager?.queryIntentActivities(
|
||||
Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER),
|
||||
PackageManager.ResolveInfoFlags.of(0)
|
||||
)
|
||||
} else {
|
||||
(packageManager?.queryIntentActivities(
|
||||
Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER), 0
|
||||
))
|
||||
})?.apply {
|
||||
removeIf { it.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID) }
|
||||
forEach {
|
||||
val result = WorkersDb.getRealm().query<AppInfo>().query("pkgName == $0",it.activityInfo.packageName).find()
|
||||
if (result.size < 1) {
|
||||
val info = AppInfo()
|
||||
info.appName = normalize(appName(it))
|
||||
info.pkgName = it.activityInfo.packageName
|
||||
info.category = getCategory(it.activityInfo.applicationInfo.category)
|
||||
info.alphaCho = AlphabetToChosungMap.getCho(info.appName!!)
|
||||
info.appNameChosung = JamoUtils.split(info.appName).joinToString("")
|
||||
WorkersDb.update(info)
|
||||
} else{
|
||||
val info = WorkersDb.getRealm().copyFromRealm(result.first())
|
||||
info.alphaCho = AlphabetToChosungMap.getCho(info.appName!!)
|
||||
info.appNameChosung = JamoUtils.split(info.appName).joinToString("")
|
||||
WorkersDb.update(info)
|
||||
}
|
||||
}
|
||||
}!!
|
||||
|
||||
} catch (e : Exception) {e.printStackTrace()}
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.Arca
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.model.getT
|
||||
import bums.lunatic.launcher.utils.beforeDay
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.Date
|
||||
|
||||
class ArcaGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "DCGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
override fun realWork(): Result {
|
||||
RssDataType.ARCA.isOn {
|
||||
try {
|
||||
temp.clear()
|
||||
val urls = arrayListOf(
|
||||
"https://arca.live/b/singbung?mode=best",
|
||||
// "https://arca.live/b/headline",
|
||||
// "https://arca.live/b/live",
|
||||
"https://arca.live/b/namuhotnow",
|
||||
"https://arca.live/b/society",
|
||||
// "https://arca.live/b/replay",
|
||||
// "https://arca.live/b/breaking"
|
||||
)
|
||||
urls.forEach {
|
||||
Jsoup.connect(it)
|
||||
.userAgent(USAGT)
|
||||
.get().let { arca ->
|
||||
// BLog.LOGE("url >> ${it} >> ${arca}")
|
||||
arca.getElementsByClass("vrow hybrid").forEach { araca_li ->
|
||||
if (araca_li.html().contains("title ") == true) {
|
||||
parseArcaLi(araca_li).apply {
|
||||
this.forEach {
|
||||
if (it.pubDate() > commicsDateTime) {
|
||||
temp.add(it.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Jsoup.connect("https://projrctjav.com").userAgent(USAGT).get().let { projectj ->
|
||||
// BLog.LOGE("projectj >>>>> ${projectj}")
|
||||
// }
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseArcaLi(aracaLi: Element) : ArrayList<RssDataInterface> {
|
||||
var tempArray = arrayListOf<RssDataInterface>()
|
||||
// BLog.LOGE("aracaLi >>> ${aracaLi}")
|
||||
var title = aracaLi.getElementsByClass("title hybrid-title").getT()
|
||||
var desc = aracaLi.getElementsByClass("badge").getT()
|
||||
desc.plus(aracaLi.getElementsByClass("user-info ").getT())
|
||||
var dateTime = aracaLi.getElementsByTag("time").attr("datetime")
|
||||
var tumbnail = aracaLi.getElementsByTag("img").attr("src")
|
||||
var link = "https://arca.live".plus(if(aracaLi.getElementsByClass("title hybrid-title").size > 0) aracaLi.getElementsByClass("title hybrid-title").get(0).attr("href") else if(aracaLi.getElementsByTag("a").size > 0) aracaLi.getElementsByTag("a").get(0).attr("href") else "")
|
||||
if (title.length > 0 && link.length > 20) {
|
||||
Arca().apply {
|
||||
this.link = link
|
||||
this.title = title
|
||||
if (tumbnail.length > 0) {
|
||||
this.thumbnail = "https:".plus(tumbnail)
|
||||
//
|
||||
// BLog.LOGE("Arca thumbnail >>> ${thumbnail}")
|
||||
}
|
||||
this.desc = desc
|
||||
this.dateTiem = dateTime
|
||||
}.apply {
|
||||
// BLog.LOGE("parseArcaLi >>>> ${this}")
|
||||
if(this.pubDate() > beforeDay(Date(),3)) {
|
||||
tempArray.add(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tempArray
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.utils.beforeDay
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
open abstract class BaseGetter : Worker {
|
||||
protected companion object {
|
||||
var lastedUpdateTime = 0L
|
||||
val defaultDay = 3
|
||||
fun before10Min(): Long {
|
||||
val cal: Calendar = Calendar.getInstance()
|
||||
cal.setTime(Date())
|
||||
cal.add(Calendar.MINUTE, -10)
|
||||
return cal.timeInMillis
|
||||
}
|
||||
}
|
||||
val USAGT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
|
||||
val now = Date()
|
||||
val limitDateTime = beforeDay(now,3)
|
||||
val commicsDateTime = beforeDay(now,1)
|
||||
val temp = arrayListOf<RssData>()
|
||||
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun doWork(): Result {
|
||||
val currentTime = before10Min()
|
||||
if (lastedUpdateTime > 0L && currentTime > lastedUpdateTime) {
|
||||
return Result.success().apply {
|
||||
|
||||
}
|
||||
}
|
||||
return realWork().apply {
|
||||
|
||||
}
|
||||
}
|
||||
abstract fun realWork() : Result
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
|
||||
|
||||
class CalendarGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "DCGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
override fun realWork(): Result {
|
||||
setCalendar()
|
||||
return Result.success().apply {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun setCalendar() {
|
||||
|
||||
|
||||
val calendars = Uri.parse("content://com.android.calendar/events")
|
||||
|
||||
val projection = arrayOf(
|
||||
"calendar_id",
|
||||
// "htmlUri",
|
||||
"title",
|
||||
// "eventLocation",
|
||||
"description",
|
||||
// "eventStatus",
|
||||
// "selfAttendeeStatus",
|
||||
// "commentsUri",
|
||||
"dtstart",
|
||||
"dtend",
|
||||
// "eventTimezone",
|
||||
// "duration",
|
||||
// "allDay",
|
||||
// "visibility",
|
||||
// "transparency",
|
||||
// "hasAlarm",
|
||||
// "hasExtendedProperties",
|
||||
// "rrule",
|
||||
"rdate",
|
||||
// "exrule",
|
||||
// "exdate",
|
||||
// "originalEvent",
|
||||
// "originalInstanceTime",
|
||||
// "originalAllDay",
|
||||
// "lastDate",
|
||||
// "hasAttendeeData",
|
||||
// "guestsCanModify",
|
||||
// "guestsCanInviteOthers",
|
||||
// "guestsCanSeeGuests",
|
||||
// "organizer",
|
||||
// "deleted"
|
||||
)
|
||||
// val managedCursor: Cursor =
|
||||
lActivity?.contentResolver?.query(calendars, projection, null, null, null)?.let { managedCursor ->
|
||||
if (managedCursor.moveToFirst()) {
|
||||
val calendar_id = IntArray(managedCursor.count)
|
||||
|
||||
// val htmlUri = arrayOfNulls<String>(managedCursor.count)
|
||||
val title = arrayOfNulls<String>(managedCursor.count)
|
||||
// val eventLocation = arrayOfNulls<String>(managedCursor.count)
|
||||
val description = arrayOfNulls<String>(managedCursor.count)
|
||||
// val eventStatus = IntArray(managedCursor.count)
|
||||
// val selfAttendeeStatus = IntArray(managedCursor.count)
|
||||
// val commentsUri = arrayOfNulls<String>(managedCursor.count)
|
||||
val dtstart = arrayOfNulls<String>(managedCursor.count)
|
||||
val dtend = arrayOfNulls<String>(managedCursor.count)
|
||||
// val eventTimezone = arrayOfNulls<String>(managedCursor.count)
|
||||
// val duration = arrayOfNulls<String>(managedCursor.count)
|
||||
// val allDay = IntArray(managedCursor.count)
|
||||
// val visibility = IntArray(managedCursor.count)
|
||||
// val transparency = IntArray(managedCursor.count)
|
||||
// val hasAlarm = IntArray(managedCursor.count)
|
||||
// val hasExtendedProperties = IntArray(managedCursor.count)
|
||||
// val rrule = arrayOfNulls<String>(managedCursor.count)
|
||||
val rdate = arrayOfNulls<String>(managedCursor.count)
|
||||
// val exrule = arrayOfNulls<String>(managedCursor.count)
|
||||
// val exdate = arrayOfNulls<String>(managedCursor.count)
|
||||
// val originalEvent = arrayOfNulls<String>(managedCursor.count)
|
||||
// val originalInstanceTime = IntArray(managedCursor.count)
|
||||
// val originalAllDay = IntArray(managedCursor.count)
|
||||
// val lastDate = IntArray(managedCursor.count)
|
||||
// val hasAttendeeData = IntArray(managedCursor.count)
|
||||
// val guestsCanModify = IntArray(managedCursor.count)
|
||||
// val guestsCanInviteOthers = IntArray(managedCursor.count)
|
||||
// val guestsCanSeeGuests = IntArray(managedCursor.count)
|
||||
// val organizer = arrayOfNulls<String>(managedCursor.count)
|
||||
// val deleted = IntArray(managedCursor.count)
|
||||
|
||||
for (i in title.indices) {
|
||||
calendar_id[i] = managedCursor.getInt(0)
|
||||
BLog.LOGE("Calendar ID : " + calendar_id[i])
|
||||
// htmlUri[i] = managedCursor.getString(1)
|
||||
// Log.i("Calendar", "htmlUri : " + htmlUri[i])
|
||||
title[i] = managedCursor.getString(1)
|
||||
BLog.LOGE("Calendar title : " + title[i])
|
||||
// eventLocation[i] = managedCursor.getString(3)
|
||||
// Log.i("Calendar", "eventLocation : " + eventLocation[i])
|
||||
description[i] = managedCursor.getString(2)
|
||||
// eventStatus[i] = managedCursor.getInt(5)
|
||||
// selfAttendeeStatus[i] = managedCursor.getInt(6)
|
||||
// commentsUri[i] = managedCursor.getString(7)
|
||||
dtstart[i] = managedCursor.getString(3)
|
||||
BLog.LOGE("Calendar dtstart : " + rdate[i])
|
||||
dtend[i] = managedCursor.getString(4)
|
||||
BLog.LOGE("Calendar dtend : " + rdate[i])
|
||||
// eventTimezone[i] = managedCursor.getString(10)
|
||||
// duration[i] = managedCursor.getString(11)
|
||||
// allDay[i] = managedCursor.getInt(12)
|
||||
// visibility[i] = managedCursor.getInt(13)
|
||||
// transparency[i] = managedCursor.getInt(14)
|
||||
// hasAlarm[i] = managedCursor.getInt(15)
|
||||
// hasExtendedProperties[i] = managedCursor.getInt(16)
|
||||
// rrule[i] = managedCursor.getString(17)
|
||||
rdate[i] = managedCursor.getString(5)
|
||||
BLog.LOGE("Calendar rdate : " + rdate[i])
|
||||
// exrule[i] = managedCursor.getString(19)
|
||||
// exdate[i] = managedCursor.getString(20)
|
||||
// originalEvent[i] = managedCursor.getString(21)
|
||||
// originalInstanceTime[i] = managedCursor.getInt(22)
|
||||
// originalAllDay[i] = managedCursor.getInt(23)
|
||||
// lastDate[i] = managedCursor.getInt(24)
|
||||
// hasAttendeeData[i] = managedCursor.getInt(25)
|
||||
// guestsCanModify[i] = managedCursor.getInt(26)
|
||||
// guestsCanInviteOthers[i] = managedCursor.getInt(27)
|
||||
// guestsCanSeeGuests[i] = managedCursor.getInt(28)
|
||||
// organizer[i] = managedCursor.getString(29)
|
||||
// deleted[i] = managedCursor.getInt(30)
|
||||
|
||||
if (title[i] != null) {
|
||||
BLog.LOGE("title[i] ${title[i]}")
|
||||
}
|
||||
|
||||
managedCursor.moveToNext()
|
||||
}
|
||||
}
|
||||
managedCursor.close()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.Clien
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getHref
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.model.getT
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class ClienGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "ClienGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
fun parseClien(div_clien : org.jsoup.nodes.Element) {
|
||||
// BLog.LOGE("div_clien >>>> ${div_clien}")
|
||||
//
|
||||
// BLog.LOGE("div_clien >>>> ${div_clien.getElementsByClass("subject_fixed").getT()}")
|
||||
// BLog.LOGE("div_clien >>>> ${div_clien.getElementsByClass("shortname fixed").getT()}")
|
||||
// BLog.LOGE("div_clien >>>> ${div_clien.getElementsByClass("list_subject").getHref()}")
|
||||
// BLog.LOGE("div_clien >>>> ${div_clien.getElementsByClass("timestamp").getT()}")
|
||||
|
||||
val title = div_clien.getElementsByClass("subject_fixed").getT()
|
||||
val desc = div_clien.getElementsByClass("shortname fixed").getT()
|
||||
val link = div_clien.getElementsByClass("list_subject").getHref()
|
||||
val timeStamp = div_clien.getElementsByClass("timestamp").getT()
|
||||
if (title.length > 0 && timeStamp.length > 0) {
|
||||
Clien().let { c ->
|
||||
c.title = title
|
||||
c.link = "https://www.clien.net".plus(link)
|
||||
c.desc = desc
|
||||
c.dateTiem = timeStamp
|
||||
if (c.pubDate() > limitDateTime) {
|
||||
temp.add(c.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
// var desc = tq_tr.getElementsByClass("cate").getT()
|
||||
// var title = tq_tr.getElementsByClass("title").getT()
|
||||
// var pageLink = tq_tr.getElementsByTag("a").getHref()
|
||||
// var dateTime = tq_tr.getElementsByClass("time").getT()
|
||||
|
||||
// BLog.LOGE("${TAG} :::: desc >>> $desc")
|
||||
// BLog.LOGE("${TAG} :::: title >>> $title")
|
||||
// BLog.LOGE("${TAG} :::: pageLink >>> $pageLink")
|
||||
// BLog.LOGE("${TAG} :::: dateTime >>> $dateTime")
|
||||
|
||||
// if (title.length > 0 && pageLink.length > 0) {
|
||||
// TheQoo().let { tq ->
|
||||
// tq.title = title
|
||||
// tq.link = "https://theqoo.net".plus(pageLink)
|
||||
// tq.dateTiem = dateTime
|
||||
// tq.desc = desc
|
||||
// if (tq.pubDate() > limitDateTime) {
|
||||
// temp.add(tq.getRssData())
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
RssDataType.CLIEN.isOn {
|
||||
try {
|
||||
temp.clear()
|
||||
val testUrl2 = arrayListOf("https://www.clien.net/service/group/community")
|
||||
testUrl2.forEach { url ->
|
||||
Jsoup.connect(url)
|
||||
.userAgent(USAGT)
|
||||
.get().let { ruli ->
|
||||
// BLog.LOGE("test ${url} >> ${ruli.title()}")
|
||||
ruli.getElementsByClass("list_item symph_row ").forEach { ruli_tr ->
|
||||
parseClien(ruli_tr)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.ContactsContract
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.apps.SimpleContact
|
||||
import io.realm.kotlin.ext.query
|
||||
|
||||
class ContactInfoGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "ContactInfoGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
override fun realWork(): Result {
|
||||
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
|
||||
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||
ContactsContract.CommonDataKinds.Phone.NUMBER,
|
||||
)
|
||||
|
||||
try {
|
||||
val cursor = lActivity?.contentResolver?.query(phoneUri, projection, null, null, null)
|
||||
if (cursor != null) {
|
||||
while (cursor.moveToNext()) {
|
||||
val idx =cursor.getColumnIndex(projection[0])
|
||||
val nameIndex = cursor.getColumnIndex(projection[1])
|
||||
val numberIndex = cursor.getColumnIndex(projection[2])
|
||||
var contactId = cursor.getString(idx)
|
||||
val name = cursor.getString(nameIndex)
|
||||
var number = cursor.getString(numberIndex)
|
||||
number = number.replace("-", "")
|
||||
if (name?.length ?: 0 > 0 && number?.length ?: 0 > 0) {
|
||||
if (WorkersDb.getRealm().query<SimpleContact>("id == $0", contactId).find().size == 0) {
|
||||
WorkersDb.update(SimpleContact(contactId,name,number))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 데이터 계열은 반드시 닫아줘야 한다.
|
||||
cursor?.close()
|
||||
} catch ( e : Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.DcInside
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class DCGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "DCGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
fun parseDcLi(dc_li : org.jsoup.nodes.Element) : ArrayList<RssDataInterface>{
|
||||
var temp = arrayListOf<RssDataInterface>()
|
||||
if (dc_li.html().contains("<ul class=>") && dc_li.html().contains("con_list img")) {
|
||||
dc_li.child(0).getElementsByTag("li").forEach {
|
||||
parseDcLi(it)
|
||||
}
|
||||
} else {
|
||||
var link = if (dc_li.getElementsByTag("a").size > 0) dc_li.getElementsByTag("a").get(0)
|
||||
.attr("href") else ""
|
||||
var title =
|
||||
if (dc_li.getElementsByClass("box besttxt").size > 0) dc_li.getElementsByClass("box besttxt")
|
||||
.get(0)
|
||||
.text() else if (dc_li.getElementsByClass("tit").size > 0) dc_li.getElementsByClass(
|
||||
"tit"
|
||||
).get(0).text() else ""
|
||||
var thumbnail =
|
||||
if (dc_li.getElementsByTag("img").size > 0) dc_li.getElementsByTag("img").get(0)
|
||||
.attr("src") else ""
|
||||
var desc =
|
||||
if (dc_li.getElementsByClass("box best_info").size > 0) dc_li.getElementsByClass("box best_info")
|
||||
.get(0).text() else ""
|
||||
var dateTiem =
|
||||
if (dc_li.getElementsByClass("time").size > 0) dc_li.getElementsByClass("time")
|
||||
.get(0).text() else ""
|
||||
link = link.replace("&","&")
|
||||
thumbnail = thumbnail.replace("&","&")
|
||||
// BLog.LOGE("DC_LI >>>> link >>>> ${link}")
|
||||
// BLog.LOGE("DC_LI >>>> title >>>> ${title}")
|
||||
// BLog.LOGE("DC_LI >>>> thumbnail >>>> ${thumbnail}")
|
||||
// BLog.LOGE("DC_LI >>>> desc >>>> ${desc}")
|
||||
// BLog.LOGE("DC_LI >>>> dateTiem >>>> ${dateTiem}")
|
||||
if (title.length > 0 && link.length > 0) {
|
||||
DcInside().apply {
|
||||
this.link = link
|
||||
this.title = title
|
||||
this.thumbnail = thumbnail
|
||||
this.desc = desc
|
||||
this.dateTiem = dateTiem
|
||||
}.apply {
|
||||
if (this.pubDate() > limitDateTime) {
|
||||
temp.add(this.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
RssDataType.DCINSIDE.isOn {
|
||||
temp.clear()
|
||||
try {
|
||||
val testUrl2 = "https://www.dcinside.com/"
|
||||
Jsoup.connect(testUrl2)
|
||||
.userAgent(USAGT)
|
||||
.get().let { dc ->
|
||||
// BLog.LOGE("test ${testUrl2} >> ${this}")
|
||||
dc.getElementsByTag("li").forEach { dc_li ->
|
||||
if (dc_li.html().contains("main_log") == true) {
|
||||
parseDcLi(dc_li).apply {
|
||||
this.forEach {
|
||||
if (it.pubDate() > commicsDateTime) {
|
||||
temp.add(it.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.Dotax
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class DotaxGetter : BaseGetter {
|
||||
companion object {
|
||||
val COMIC2_WORK_TAG = "ComicGetter2"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
RssDataType.DOTAX.isOn {
|
||||
try {
|
||||
temp.clear()
|
||||
val dotaxUrls = arrayListOf("https://m.cafe.daum.net/dotax",
|
||||
"https://m.cafe.daum.net/dotax/_rec?page=2",
|
||||
"https://m.cafe.daum.net/dotax/_rec?page=3"
|
||||
)
|
||||
dotaxUrls?.forEach {
|
||||
Jsoup.connect(it).userAgent(USAGT).get()?.let { dotax ->
|
||||
dotax.getElementsByTag("li").forEach { dotax_li ->
|
||||
if (dotax_li.getElementsByTag("a").size > 0 && dotax_li.getElementsByClass("board_name")
|
||||
.html().contains("웃긴")
|
||||
) {
|
||||
val pageLink = dotax_li.getElementsByTag("a").get(0).attr("href")
|
||||
val desc = dotax_li.getElementsByClass("board_name").text()
|
||||
val dateTime = dotax_li.getElementsByClass("created_at").text()
|
||||
val title = dotax_li.getElementsByClass("txt_detail").text()
|
||||
val thumbnail = dotax_li.getElementsByClass("article_thumb").text()
|
||||
if (pageLink.length > 0 && desc.length > 0 && dateTime.length > 0 && title.length > 0) {
|
||||
Dotax(pageLink, desc, dateTime, title, thumbnail).let { dotax ->
|
||||
if(dotax.pubDate() > commicsDateTime) {
|
||||
temp.add(dotax.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e : Exception) {e.printStackTrace()}}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.FmKorea
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.Date
|
||||
|
||||
class FmKoreaGetter : BaseGetter {
|
||||
companion object {
|
||||
val COMIC_WORK_TAG = "ComicGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
RssDataType.FMKORAE.isOn {
|
||||
val now = Date()
|
||||
try {
|
||||
val fmkoreaUrls = arrayListOf("https://www.fmkorea.com")
|
||||
fmkoreaUrls.forEach {
|
||||
Jsoup.connect(it).userAgent(USAGT).get().let { fmkorea ->
|
||||
// BLog.LOGE("fmkorea >>> ${fmkorea.title()}")
|
||||
fmkorea.getElementsByTag("li").forEach { fmkorea_li ->
|
||||
if (fmkorea_li.getElementsByClass("title")
|
||||
.text().length > 0 && fmkorea_li.getElementsByTag("a").size > 0 && fmkorea_li.getElementsByTag(
|
||||
"a"
|
||||
).get(0).attr("href").length > 0
|
||||
) {
|
||||
// BLog.LOGE("fmkorea_li >>> ${fmkorea_li}")
|
||||
val title = fmkorea_li.getElementsByClass("title").text()
|
||||
val tumb = "https://".plus(
|
||||
fmkorea_li.getElementsByClass("thumb").attr("data-original")
|
||||
)
|
||||
val pageUrl = "https://www.fmkorea.com".plus(
|
||||
fmkorea_li.getElementsByTag("a").get(0).attr("href")
|
||||
)
|
||||
val desc = fmkorea_li.getElementsByClass("category").text()
|
||||
val date = fmkorea_li.getElementsByClass("regdate").text()
|
||||
FmKorea(pageUrl, desc, date, title, tumb).apply {
|
||||
if (desc?.contains("유머") == true || desc?.contains("음악") == true || desc?.contains(
|
||||
"영화"
|
||||
) == true ||
|
||||
desc?.contains("TV") == true || desc?.contains("미스터리") == true || desc?.contains(
|
||||
"역사"
|
||||
) == true
|
||||
) {
|
||||
if (this.pubDate() > commicsDateTime) {
|
||||
temp.add(this.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.runWeatherGetter
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
import bums.lunatic.launcher.helpers.letTrue
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import bums.lunatic.launcher.workers.LocationUpdateService.Companion.pushLocation
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.gms.location.Priority
|
||||
import com.google.android.gms.tasks.CancellationTokenSource
|
||||
import kotlin.math.cos
|
||||
|
||||
class LocationGetter(context: Context, workerParams: WorkerParameters) : BaseGetter(context, workerParams) {
|
||||
companion object {
|
||||
val TAG = "LocationGetter"
|
||||
var longitude: Double? = null
|
||||
var latitude: Double? = null
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun realWork(): Result {
|
||||
BLog.LOGE("${OpenWeatherGetter.TAG} realWork()")
|
||||
|
||||
LocationServices.getFusedLocationProviderClient(this.applicationContext)
|
||||
.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, CancellationTokenSource().token)
|
||||
.addOnSuccessListener{ success: Location? ->
|
||||
success?.let {
|
||||
BLog.LOGE("Location >>> $it")
|
||||
BLog.LOGE("Location >>> (latitude)${it.longitude}/(longitude)${it.latitude}")
|
||||
longitude = it.longitude
|
||||
latitude = it.latitude
|
||||
runWeatherGetter()
|
||||
PrefHelper.isLocationOn().letTrue {
|
||||
pushLocation(this.applicationContext,it.latitude, it.longitude)
|
||||
}
|
||||
}
|
||||
}.addOnFailureListener{
|
||||
BLog.LOGE("Location error >>> $it")
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
val EARTH_RADIUS_METERS = 6371000
|
||||
val LATITUDE_DEGREE_PER_METER: Double = 1.0 / (2 * Math.PI * EARTH_RADIUS_METERS / 360)
|
||||
|
||||
fun latitudeRange(latitude: Double, radiusInMeters: Int): DoubleArray {
|
||||
val degreeRange = radiusInMeters * LATITUDE_DEGREE_PER_METER
|
||||
|
||||
val minLatitude = latitude - degreeRange
|
||||
val maxLatitude = latitude + degreeRange
|
||||
|
||||
return doubleArrayOf(minLatitude, maxLatitude)
|
||||
}
|
||||
|
||||
fun longitudeRange(latitude: Double, longitude: Double, radiusInMeters: Int): DoubleArray {
|
||||
val longitudeDegreePerMeter: Double = 360 / (2 * Math.PI * EARTH_RADIUS_METERS * cos(Math.toRadians(latitude)))
|
||||
val degreeRange = longitudeDegreePerMeter * radiusInMeters
|
||||
|
||||
val minLongitude = longitude - degreeRange
|
||||
val maxLongitude = longitude + degreeRange
|
||||
|
||||
return doubleArrayOf(minLongitude, maxLongitude)
|
||||
}
|
||||
|
||||
|
||||
//https://jinkpark.tistory.com/296
|
||||
//https://develoyummer.tistory.com/103
|
||||
//https://ghj1001020.tistory.com/300
|
||||
@@ -0,0 +1,173 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import bums.lunatic.launcher.helpers.PrefHelper
|
||||
import bums.lunatic.launcher.helpers.PrefString
|
||||
import bums.lunatic.launcher.helpers.letTrue
|
||||
import bums.lunatic.launcher.model.LocationLog
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.gson.Gson
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.query.Sort
|
||||
import okhttp3.ConnectionPool
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import java.io.IOException
|
||||
import java.util.Base64
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class LocationUpdateService : Service(), LocationListener {
|
||||
|
||||
companion object {
|
||||
private const val MIN_DISTANCE_CHANGE_FOR_UPDATES: Long = 200
|
||||
private const val MIN_TIME_BW_UPDATES: Long = 30
|
||||
|
||||
fun pushLocation(context: Context, lat :Double, long : Double) {
|
||||
try {
|
||||
val geocoder = Geocoder(context, Locale.getDefault())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
geocoder.getFromLocation(lat, long, 1) { addresses ->
|
||||
addresses.first()?.let {
|
||||
WorkersDb.getRealm()?.apply {
|
||||
LocationLog().let { loc ->
|
||||
loc.fillData(it)
|
||||
var list = query<LocationLog>().sort("time", Sort.DESCENDING).find()
|
||||
(list.size == 0 || (list.size > 0 && list.first().time < System.currentTimeMillis() - (1000L * 60L * 10L))).letTrue {
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
try {
|
||||
//////-1002450229641
|
||||
val url = PrefString.locationApi.get()
|
||||
if (url.length > 10) {
|
||||
val client = OkHttpClient.Builder()
|
||||
.connectionPool(ConnectionPool(5, 60, TimeUnit.SECONDS))
|
||||
.build()
|
||||
|
||||
// GET 요청 객체 생성
|
||||
val builder: Request.Builder = Request.Builder().url(url)
|
||||
.addHeader("Content-Type", "application/json").get()
|
||||
builder.method(
|
||||
"POST", RequestBody.create(
|
||||
MediaType.parse("application/text"),
|
||||
Base64.getEncoder().encode(
|
||||
Gson().toJson(this@apply).toByteArray()
|
||||
)
|
||||
)
|
||||
)
|
||||
val request: Request = builder.build()
|
||||
|
||||
BLog.LOGE("telegram before request ")
|
||||
// OkHttp 클라이언트로 GET 요청 객체 전송
|
||||
val response: Response = client.newCall(request).execute()
|
||||
if (response.isSuccessful()) {
|
||||
// 응답 받아서 처리
|
||||
val body: ResponseBody? = response.body()
|
||||
if (body != null) {
|
||||
|
||||
}
|
||||
} else BLog.LOGE("telegram Error Occurred")
|
||||
}
|
||||
} catch (e: java.lang.Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS)
|
||||
}
|
||||
writeBlocking {
|
||||
copyToRealm(loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
addresses.forEach { }
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
protected var locationManager: LocationManager? = null
|
||||
var checkGPS = false
|
||||
var checkNetwork = false
|
||||
|
||||
// boolean canGetLocation = false;
|
||||
var loc: Location? = null
|
||||
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun onLocationChanged(p0: Location) {
|
||||
BLog.LOGE("p0")
|
||||
PrefHelper.isLocationOn().letTrue {
|
||||
pushLocation(this.applicationContext, p0.latitude,p0.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
location
|
||||
}
|
||||
|
||||
|
||||
private val location: Location?
|
||||
private get() {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) !=
|
||||
PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
}
|
||||
locationManager = applicationContext
|
||||
.getSystemService(LOCATION_SERVICE) as LocationManager
|
||||
checkGPS = locationManager!!
|
||||
.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||
checkNetwork = locationManager!!
|
||||
.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
locationManager?.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES.toFloat(), this)
|
||||
if (locationManager != null) {
|
||||
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
|
||||
if (location != null) {
|
||||
Toast.makeText(
|
||||
applicationContext,
|
||||
java.lang.Double.toString(location.latitude) + location.longitude + "from method",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
pushLocation(this.applicationContext, location.latitude, location.longitude)
|
||||
}
|
||||
}
|
||||
}
|
||||
return loc
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.home.adapters.RssFeedsParser
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.utils.RssList
|
||||
|
||||
class NewsFeedsGetter : BaseGetter {
|
||||
companion object {
|
||||
val FEDDS_WORK_TAG = "NewsFeedsGetter"
|
||||
}
|
||||
var feddsUrls = arrayListOf<String>()
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
RssDataType.NEWSFEED.isOn {
|
||||
feddsUrls.clear()
|
||||
feddsUrls.addAll(RssList.newsFeeds)
|
||||
feddsUrls.addAll(RssList.getFeedUrls())
|
||||
|
||||
for (url in feddsUrls) {
|
||||
for (it in RssFeedsParser.getFeeds(url)) {
|
||||
if (it.pubDate() >= limitDateTime) {
|
||||
temp.add(it.getRssData())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
|
||||
// temp.forEach { synchronized(rssSet){
|
||||
// rssSet.put(it.originPage(), it)
|
||||
// } }.run {
|
||||
// rssSetTouchCount -= 1
|
||||
// Result.success() }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.model.WeatherForcast
|
||||
import bums.lunatic.launcher.model.WeatherInfoManager
|
||||
import bums.lunatic.launcher.utils.BLog
|
||||
import io.realm.kotlin.UpdatePolicy
|
||||
import retrofit2.Call
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import retrofit2.create
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
class OpenWeatherGetter(context: Context, workerParams: WorkerParameters) : BaseGetter(context, workerParams) {
|
||||
companion object {
|
||||
val TAG = "OpenWeatherGetter"
|
||||
var lon: Double? = null // 경도
|
||||
var lat: Double? = null // 위도
|
||||
}
|
||||
//////////////////////////////////////////
|
||||
// weatherapi
|
||||
val VER_WEATHERAPI = "v1"
|
||||
val URI_WEATHERAPI = "https://api.weatherapi.com"
|
||||
val KEY_WEATHERAPI = "8133d83d23ab4175a4160624241909"
|
||||
val DAYS = 3
|
||||
//////////////////////////////////////////
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
override fun realWork(): Result {
|
||||
BLog.LOGE("${TAG} realWork()")
|
||||
// 위치 값 가져오기
|
||||
lat = LocationGetter.latitude
|
||||
lon = LocationGetter.longitude
|
||||
if (lat != null && lon != null) {
|
||||
getWeather(lat!!, lon!!)
|
||||
} else {
|
||||
BLog.LOGE("lat or lon is null")
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
fun getWeather(latitude: Double, longitude: Double) {
|
||||
BLog.LOGE("into getWeather")
|
||||
///saved weatherForcast
|
||||
Retrofit.Builder()
|
||||
.baseUrl(URI_WEATHERAPI)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create<RestrofitService>()
|
||||
.getForecast( // weatherApi
|
||||
ver = VER_WEATHERAPI,
|
||||
key = KEY_WEATHERAPI,
|
||||
q = "$latitude,$longitude",
|
||||
days = (System.currentTimeMillis() % 5L).toInt().toString()
|
||||
)?.execute()?.let { response ->
|
||||
// BLog.LOGE("into getWeather after execute")
|
||||
// BLog.LOGE("weatherApi forecast response >>> $response")
|
||||
response.body()?.let { weatherInfo ->
|
||||
WeatherInfoManager.info = weatherInfo
|
||||
WeatherInfoManager.readyForSaving(lat ?: 0.0, lon ?: 0.0)
|
||||
// Realm에 저장
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
copyToRealm(weatherInfo, UpdatePolicy.ALL).also {
|
||||
// BLog.LOGE("saved weatherForcast >>> $it")
|
||||
}
|
||||
}
|
||||
// BLog.LOGE("saved weatherForcast forecastdayRealm.size >>> ${WorkersDb.getRealm().query<WeatherForcast>().first().find()?.forecast?.forecastdayRealm?.size}")
|
||||
// BLog.LOGE("saved weatherForcast hour.count >>> ${WorkersDb.getRealm().query<Hour>().count().find()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface RestrofitService {
|
||||
// weather_api
|
||||
@GET("/{ver}/forecast.json")
|
||||
fun getForecast(
|
||||
@Path("ver") ver: String,
|
||||
@Query("key") key: String,
|
||||
@Query("q") q: String,
|
||||
@Query("days") days: String
|
||||
): Call<WeatherForcast?>?
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user