This commit is contained in:
lunaticbum
2024-10-18 16:16:41 +09:00
parent edca12a137
commit 94000c4b67
134 changed files with 1663 additions and 2373 deletions
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("&amp;","&")
}
}
}
}.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("&amp;","&").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("&amp;","&")
thumbnail = thumbnail.replace("&amp;","&")
// 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