,,,....
This commit is contained in:
parent
dc6b95108f
commit
62f1e646ab
@ -101,6 +101,11 @@
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|screenLayout|layoutDirection|navigation"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.HOME" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.WEB_SEARCH"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@ -117,6 +122,7 @@
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<!-- <activity-->
|
||||
|
||||
@ -14,6 +14,7 @@ import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.GestureDetector
|
||||
import android.view.KeyEvent
|
||||
import android.view.KeyEvent.ACTION_UP
|
||||
import android.view.KeyEvent.KEYCODE_BUTTON_A
|
||||
@ -38,6 +39,7 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.apps.AppDrawerBottomSheet
|
||||
import bums.lunatic.launcher.common.CommonActivity
|
||||
import bums.lunatic.launcher.databinding.LauncherActivityBinding
|
||||
import bums.lunatic.launcher.feeds.WidgetHost
|
||||
@ -79,6 +81,97 @@ import java.util.Date
|
||||
|
||||
|
||||
open class LauncherActivity : CommonActivity() {
|
||||
// LauncherActivity 내부 (inner class)
|
||||
|
||||
inner class HomeGestureListener : GestureDetector.SimpleOnGestureListener() {
|
||||
|
||||
// 1. 스와이프 감지 (상하좌우)
|
||||
override fun onFling(
|
||||
e1: MotionEvent?,
|
||||
e2: MotionEvent,
|
||||
velocityX: Float,
|
||||
velocityY: Float
|
||||
): Boolean {
|
||||
if (e1 == null) return false
|
||||
|
||||
val diffY = e2.y - e1.y
|
||||
val diffX = e2.x - e1.x
|
||||
val SWIPE_THRESHOLD = 100
|
||||
val SWIPE_VELOCITY_THRESHOLD = 100
|
||||
|
||||
if (Math.abs(diffX) > Math.abs(diffY)) {
|
||||
// 좌우 스와이프
|
||||
if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
|
||||
if (diffX > 0) {
|
||||
onSwipeRight()
|
||||
} else {
|
||||
onSwipeLeft()
|
||||
}
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// 상하 스와이프
|
||||
if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
|
||||
if (diffY > 0) {
|
||||
onSwipeDown()
|
||||
} else {
|
||||
onSwipeUp()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 2. 더블 클릭 감지 (빈 공간)
|
||||
override fun onDoubleTap(e: MotionEvent): Boolean {
|
||||
// 더블 클릭 액션
|
||||
showToast("더블 클릭: 설정 열기")
|
||||
// 예: startActivity(Intent(this@LauncherActivity, SettingsActivity::class.java))
|
||||
return true
|
||||
}
|
||||
|
||||
// 3. 싱글 클릭 감지 (빈 공간)
|
||||
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
|
||||
// 싱글 클릭 액션 (필요하다면)
|
||||
// showToast("빈 공간 클릭됨")
|
||||
return true
|
||||
}
|
||||
|
||||
// 4. 롱프레스 감지 (빈 공간)
|
||||
override fun onLongPress(e: MotionEvent) {
|
||||
// 위젯 추가 메뉴 등을 띄우려면 여기서 처리
|
||||
// 주의: 위젯 위에서 롱프레스하면 위젯 드래그가 먼저 작동하도록 설계해야 함
|
||||
showToast("바탕화면 롱프레스: 위젯 추가")
|
||||
selectWidget() // 기존에 만든 위젯 추가 함수 호출
|
||||
}
|
||||
|
||||
// onDown은 true를 반환해야 다른 제스처들이 시작됨
|
||||
override fun onDown(e: MotionEvent): Boolean {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// (편의용) 토스트 함수
|
||||
fun showToast(msg: String) {
|
||||
android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// 각 스와이프 동작 정의
|
||||
fun onSwipeUp() {
|
||||
showAppDrawer() // 지난번에 만든 바텀시트 앱서랍 열기
|
||||
}
|
||||
fun onSwipeDown() {
|
||||
// 알림창 내리기 등
|
||||
try {
|
||||
val service = getSystemService("statusbar")
|
||||
val statusbarManager = Class.forName("android.app.StatusBarManager")
|
||||
val expand = statusbarManager.getMethod("expandNotificationsPanel")
|
||||
expand.invoke(service)
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
fun onSwipeLeft() { /* 페이지 이동 등 */ }
|
||||
fun onSwipeRight() { /* 페이지 이동 등 */ }
|
||||
|
||||
private lateinit var binding: LauncherActivityBinding
|
||||
|
||||
@ -405,14 +498,20 @@ open class LauncherActivity : CommonActivity() {
|
||||
}
|
||||
|
||||
// 두 뷰가 겹치는지 확인하는 헬퍼 함수
|
||||
private fun isViewOverlapping(v1: View, v2: View): Boolean {
|
||||
val rect1 = android.graphics.Rect()
|
||||
v1.getGlobalVisibleRect(rect1)
|
||||
private fun isViewOverlapping(dragView: View, targetView: View): Boolean {
|
||||
val targetRect = android.graphics.Rect()
|
||||
targetView.getGlobalVisibleRect(targetRect)
|
||||
|
||||
val rect2 = android.graphics.Rect()
|
||||
v2.getGlobalVisibleRect(rect2)
|
||||
// 2. 드래그 중인 뷰의 화면상 절대 좌표 영역 구하기
|
||||
val dragRect = android.graphics.Rect()
|
||||
dragView.getGlobalVisibleRect(dragRect)
|
||||
|
||||
return android.graphics.Rect.intersects(rect1, rect2)
|
||||
// 3. 드래그 뷰의 중심점 계산
|
||||
val centerX = dragRect.centerX()
|
||||
val centerY = dragRect.centerY()
|
||||
|
||||
// 4. 중심점이 타겟 영역 안에 있는지 확인
|
||||
return targetRect.contains(centerX, centerY)
|
||||
}
|
||||
// 위젯 관련 변수
|
||||
private var appWidgetManager: AppWidgetManager? = null
|
||||
@ -421,6 +520,13 @@ open class LauncherActivity : CommonActivity() {
|
||||
private val REQUEST_PICK_APPWIDGET = 100
|
||||
private val REQUEST_CREATE_APPWIDGET = 101
|
||||
|
||||
|
||||
fun showAppDrawer() {
|
||||
val bottomSheet = AppDrawerBottomSheet.newInstance()
|
||||
bottomSheet.show(supportFragmentManager, AppDrawerBottomSheet.TAG)
|
||||
}
|
||||
private lateinit var homeGestureDetector: androidx.core.view.GestureDetectorCompat
|
||||
|
||||
@SuppressLint("NewApi", "MissingPermission", "ClickableViewAccessibility")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
@ -446,6 +552,18 @@ open class LauncherActivity : CommonActivity() {
|
||||
DynamicColors.applyToActivityIfAvailable(this)
|
||||
binding = LauncherActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
homeGestureDetector = androidx.core.view.GestureDetectorCompat(this, HomeGestureListener())
|
||||
binding.widgetContainer.setOnTouchListener { view, event ->
|
||||
// 위젯 드래그 중이 아닐 때만 제스처 처리
|
||||
if (!isDraggingWidget) {
|
||||
homeGestureDetector.onTouchEvent(event)
|
||||
// true를 반환해야 이벤트가 소비되어 onSingleTap 등이 정상 동작함
|
||||
// 하지만 자식 뷰(위젯)의 클릭을 막지 않으려면 주의 필요.
|
||||
// onTouchEvent가 true를 반환하면 이벤트 체인이 여기서 끝납니다.
|
||||
return@setOnTouchListener true
|
||||
}
|
||||
false
|
||||
}
|
||||
HeadsetActionButtonReceiver.register(this)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||
@ -789,6 +907,15 @@ open class LauncherActivity : CommonActivity() {
|
||||
R.id.setting ->{
|
||||
startActivity(Intent(this, SettingsActivity::class.java))
|
||||
}
|
||||
R.id.close ->{
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container)?.let {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.remove(it)
|
||||
.commit()
|
||||
binding.fragmentContainer.visibility = View.GONE
|
||||
}
|
||||
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
binding.floatingActionMenu.close(false)
|
||||
|
||||
@ -45,6 +45,7 @@ import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.helpers.PrefLong
|
||||
import bums.lunatic.launcher.helpers.PrefString
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
|
||||
@ -0,0 +1,338 @@
|
||||
package bums.lunatic.launcher.apps
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.databinding.BottomSheetAppDrawerBinding // XML 이름에 맞춰 바인딩 클래스 생성됨
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
import bums.lunatic.launcher.helpers.PrefLong
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.gms.common.wrappers.PackageManagerWrapper
|
||||
import com.google.android.gms.common.wrappers.Wrappers.packageManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
||||
|
||||
private var _binding: BottomSheetAppDrawerBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var appsAdapter: AppsAdapter? = null
|
||||
private var contactAdapter: ContactAdapter? = null
|
||||
private val packageList = mutableListOf<AppInfo>()
|
||||
private val contactList = arrayListOf<SimpleContact>() // SimpleContact 클래스가 import 되어야 함
|
||||
|
||||
companion object {
|
||||
const val TAG = "AppDrawerBottomSheet"
|
||||
|
||||
fun newInstance(): AppDrawerBottomSheet {
|
||||
return AppDrawerBottomSheet()
|
||||
}
|
||||
}
|
||||
|
||||
// 배경을 투명하게 하거나 스타일을 적용하기 위해
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// res/values/themes.xml 등에 BottomSheet 스타일 정의 필요 (모서리 둥글게 등)
|
||||
setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme)
|
||||
}
|
||||
|
||||
// 키보드가 올라올 때 바텀시트가 가려지지 않도록 설정
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||
|
||||
// 바텀시트가 완전히 펼쳐진 상태로 시작하게 설정
|
||||
dialog.setOnShowListener { dialogInterface ->
|
||||
val bottomSheetDialog = dialogInterface as BottomSheetDialog
|
||||
val bottomSheet = bottomSheetDialog.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)
|
||||
bottomSheet?.let {
|
||||
val behavior = BottomSheetBehavior.from(it)
|
||||
behavior.state = BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.skipCollapsed = true
|
||||
}
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = BottomSheetAppDrawerBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
setupAdapters()
|
||||
setupSearchButtons()
|
||||
setupListeners()
|
||||
|
||||
// 초기 데이터 로드
|
||||
fetchApps()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val dialog = dialog as? com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
dialog?.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)?.let { bottomSheet ->
|
||||
|
||||
// 1. 동작 설정 (완전히 펼치기)
|
||||
val behavior = com.google.android.material.bottomsheet.BottomSheetBehavior.from(bottomSheet)
|
||||
behavior.state = com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
|
||||
behavior.skipCollapsed = true
|
||||
|
||||
// 2. [핵심] 높이 강제 설정 (이게 없으면 리스트가 0dp일 때 안 보임)
|
||||
val layoutParams = bottomSheet.layoutParams
|
||||
layoutParams.height = android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
bottomSheet.layoutParams = layoutParams
|
||||
}
|
||||
}
|
||||
private var recAdapter: AppsAdapter? = null
|
||||
private fun setupAdapters() {
|
||||
// 기존 Activity의 packageManager 대신 requireContext().packageManager 사용
|
||||
val pm = requireContext().packageManager
|
||||
appsAdapter = AppsAdapter(pm, childFragmentManager, binding.appsCount)
|
||||
contactAdapter = ContactAdapter(pm, childFragmentManager)
|
||||
|
||||
// 가로 그리드 개수 4~5개 정도로 조정 (기존 2개였으면 그대로 유지)
|
||||
binding.appsList.layoutManager = GridLayoutManager(context, 3,GridLayoutManager.HORIZONTAL,false)
|
||||
binding.appsList.adapter = appsAdapter
|
||||
binding.appsList.setItemViewCacheSize(20)
|
||||
binding.appsList.setHasFixedSize(true) //
|
||||
|
||||
binding.contactList.layoutManager = GridLayoutManager(context, 3,GridLayoutManager.HORIZONTAL,false)
|
||||
binding.contactList.adapter = contactAdapter
|
||||
|
||||
recAdapter = AppsAdapter(pm, childFragmentManager,null)
|
||||
binding.recAppsList.layoutManager = GridLayoutManager(context, 1,GridLayoutManager.HORIZONTAL,false)
|
||||
binding.recAppsList.adapter = recAdapter
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
// 검색어 입력 리스너
|
||||
binding.searchInput.doOnTextChanged { inputText, _, _, _ ->
|
||||
filterAppsList(inputText.toString())
|
||||
}
|
||||
|
||||
// 엔터키 입력 시 검색 실행
|
||||
binding.searchInput.setOnKeyListener { _, keyCode, event ->
|
||||
if (PrefBoolean.useQuickLaunch.get(false) && keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
|
||||
checkResult(binding.searchInput.text.toString())
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// 새로고침 버튼
|
||||
binding.reset.setOnClickListener {
|
||||
binding.searchInput.text?.clear()
|
||||
fetchApps()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSearchButtons() {
|
||||
// 기존 AppDrawer의 버튼 연결
|
||||
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.searchGoogle.setOnClickListener {
|
||||
openSearchApps("https://www.google.com/search?q=${getInputText()}", "com.android.chrome")
|
||||
}
|
||||
binding.searchNaver.setOnClickListener {
|
||||
openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${getInputText()}", "com.nhn.android.search")
|
||||
}
|
||||
// ... 나머지 버튼들도 동일하게 추가 ...
|
||||
}
|
||||
|
||||
private fun getInputText() = binding.searchInput.text.toString()
|
||||
|
||||
private fun openSearchApps(schemeString: String, packageName: String? = null) {
|
||||
try {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(schemeString))
|
||||
packageName?.let {
|
||||
intent.setPackage(it)
|
||||
WorkersDb.updateAppUse(it)
|
||||
}
|
||||
startActivity(intent)
|
||||
dismiss() // 실행 후 닫기
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
// 앱이 없을 경우 웹으로 열기 등의 예외처리 권장
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkResult(keyword: String) {
|
||||
// 기존 Activity의 checkResult 로직 구현 (필요시 부모 액티비티 함수 호출)
|
||||
// (lActivity as? LauncherActivity)?.openSearchMenus(keyword) ...
|
||||
dismiss()
|
||||
}
|
||||
|
||||
private fun filterAppsList(searchString: String) {
|
||||
fetchApps(searchString)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 앱 목록을 불러오는 함수
|
||||
* - 검색어(keyword)가 있으면 필터링을 수행합니다.
|
||||
* - 검색어가 없으면 '문맥 기반 추천 앱'을 최상단에 배치합니다.
|
||||
* - IO 스레드에서 실행하여 UI 끊김을 방지합니다.
|
||||
*/
|
||||
private fun fetchApps(keyword: String? = null) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val realm = WorkersDb.getRealm()
|
||||
val pm = requireContext().packageManager
|
||||
|
||||
// 1. [추천 로직] 에러가 나도 앱 목록 로딩은 진행되도록 try-catch 분리
|
||||
val recommendedPkgNames = try {
|
||||
if (keyword.isNullOrEmpty()) {
|
||||
WorkersDb.getContextualRecommendations(limit = 5)
|
||||
} else {
|
||||
emptyList() // 검색 중에는 추천 안 함
|
||||
}
|
||||
} catch (e: Exception) { emptyList() }
|
||||
|
||||
try {
|
||||
// 2. [쿼리 구성]
|
||||
var appQuery = realm.query<AppInfo>()
|
||||
|
||||
if (!keyword.isNullOrEmpty()) {
|
||||
val firstChar = keyword.first().toString()
|
||||
if (JamoUtils.CHOSUNG.contains(firstChar)) {
|
||||
appQuery = appQuery.query("appNameChosung CONTAINS[c] $0 OR alphaCho CONTAINS[c] $0", keyword)
|
||||
} else if (java.util.regex.Pattern.matches("^[가-힣]*\$", keyword)) {
|
||||
appQuery = appQuery.query("appName CONTAINS[c] $0 OR koreanName CONTAINS[c] $0", keyword)
|
||||
} else {
|
||||
appQuery = appQuery.query("appName CONTAINS[c] $0 OR pkgName CONTAINS[c] $0 OR category CONTAINS[c] $0", keyword)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. [DB 조회]
|
||||
val results = appQuery
|
||||
.sort("clickCount", Sort.DESCENDING)
|
||||
.sort("lastUseDate", Sort.DESCENDING)
|
||||
.find()
|
||||
|
||||
// [중요] DB가 비어있다면(앱 설치 직후 등), 여기서 앱 스캔을 요청하거나 빈 상태 처리
|
||||
if (results.isEmpty() && keyword.isNullOrEmpty()) {
|
||||
// 필요 시 AppInfoGetter 워커를 즉시 실행하는 로직 추가 가능
|
||||
}
|
||||
|
||||
// 4. [데이터 가공]
|
||||
val allApps = results.map { realm.copyFromRealm(it) }
|
||||
.filter { appInfo ->
|
||||
try {
|
||||
pm.getLaunchIntentForPackage(appInfo.pkgName ?: "") != null
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
// C. [추천 앱 객체 추출]
|
||||
val recAppList = recommendedPkgNames.mapNotNull { pkg ->
|
||||
allApps.find { it.pkgName == pkg }
|
||||
}
|
||||
|
||||
val mainAppList = allApps
|
||||
|
||||
var contactQuery = realm.query<SimpleContact>()
|
||||
|
||||
if (!keyword.isNullOrEmpty()) {
|
||||
// 이름, 초성, 전화번호로 검색
|
||||
val firstChar = keyword.first().toString()
|
||||
if (JamoUtils.CHOSUNG.contains(firstChar)) {
|
||||
contactQuery = contactQuery.query("chosung CONTAINS[c] $0", keyword)
|
||||
} else if (java.util.regex.Pattern.matches("^[가-힣]*\$", keyword)) {
|
||||
contactQuery = contactQuery.query("name CONTAINS[c] $0", keyword)
|
||||
} else {
|
||||
contactQuery = contactQuery.query("name CONTAINS[c] $0 OR phoneNumber CONTAINS $0", keyword)
|
||||
}
|
||||
} else {
|
||||
// 검색어 없을 때: 자주 쓰는 연락처(터치 횟수 순) 또는 최근 연락처 상위 10개만 노출 (너무 많으면 스크롤 힘듦)
|
||||
|
||||
}
|
||||
contactQuery = contactQuery.sort("touchCount", Sort.DESCENDING).limit(10)
|
||||
|
||||
val contactsResult = contactQuery.find()
|
||||
val contactsList = contactsResult.map { realm.copyFromRealm(it) }
|
||||
|
||||
// 6. [UI 업데이트]
|
||||
withContext(Dispatchers.Main) {
|
||||
if (recAppList.isNotEmpty() && keyword.isNullOrEmpty()) {
|
||||
binding.titleRecommend.visibility = View.VISIBLE
|
||||
binding.recAppsList.visibility = View.VISIBLE
|
||||
recAdapter?.updateData(recAppList)
|
||||
} else {
|
||||
// 검색 중이거나 데이터 없으면 숨김
|
||||
binding.titleRecommend.visibility = View.GONE
|
||||
binding.recAppsList.visibility = View.GONE
|
||||
}
|
||||
|
||||
// 2. 전체 앱 리스트 업데이트
|
||||
packageList.clear()
|
||||
packageList.addAll(mainAppList)
|
||||
appsAdapter?.updateData(packageList)
|
||||
binding.appsCount.text = "${packageList.size} Apps"
|
||||
|
||||
if (contactsList.isNotEmpty()) {
|
||||
binding.titleContact.visibility = View.VISIBLE
|
||||
binding.contactList.visibility = View.VISIBLE
|
||||
contactAdapter?.updateData(contactsList)
|
||||
} else {
|
||||
// 검색 결과가 없으면 숨김
|
||||
binding.titleContact.visibility = View.GONE
|
||||
binding.contactList.visibility = View.GONE
|
||||
contactAdapter?.updateData(emptyList()) // 빈 리스트로 갱신하여 잔상 제거
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace() // 여기서 에러가 나면 앱 목록이 안 뜹니다. 로그캣(Logcat)을 확인해보세요.
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun isPackageInstalled(packageName: String, packageManager: PackageManagerWrapper): Boolean {
|
||||
return try {
|
||||
packageManager.getPackageInfo(packageName, 0)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,7 @@ import bums.lunatic.launcher.workers.WorkersDb
|
||||
import io.realm.kotlin.ext.query
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
internal class AppsAdapter(
|
||||
@ -64,11 +65,7 @@ internal class AppsAdapter(
|
||||
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) }
|
||||
} }
|
||||
loadIconAsync(appIconTwo, item.pkgName)
|
||||
childTextview.apply {
|
||||
gravity = Gravity.CENTER
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twelve))
|
||||
@ -90,6 +87,7 @@ internal class AppsAdapter(
|
||||
}
|
||||
}
|
||||
}
|
||||
item.pkgName?.let { WorkersDb.logAppUsage(it,"APP") }
|
||||
context.startActivity(packageManager.getLaunchIntentForPackage(item.pkgName!!))
|
||||
}
|
||||
|
||||
@ -112,6 +110,28 @@ internal class AppsAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadIconAsync(imageView: android.widget.ImageView, pkgName: String?) {
|
||||
if (pkgName == null) return
|
||||
|
||||
// 1. 이미지가 로딩되기 전 초기화 (재사용 뷰 깜빡임 방지)
|
||||
imageView.setImageDrawable(null)
|
||||
|
||||
// Tag를 사용하여 뷰홀더가 재사용되었을 때 이전 작업 취소 식별
|
||||
imageView.tag = pkgName
|
||||
|
||||
// CoroutineScope (lifecycleScope나 adapter 내부 scope 사용)
|
||||
kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Main).launch {
|
||||
val icon = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
IconPackManager.getDrawableIconForPackage(imageView.context, pkgName)
|
||||
}
|
||||
|
||||
// 로딩이 끝났는데 뷰가 여전히 같은 앱을 가리키고 있는지 확인
|
||||
if (imageView.tag == pkgName) {
|
||||
imageView.setImageDrawable(icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = oldList.size
|
||||
|
||||
inner class AppsViewHolder(var view: AppsChildBinding) : RecyclerView.ViewHolder(view.root)
|
||||
@ -119,10 +139,17 @@ internal class AppsAdapter(
|
||||
/* update app list */
|
||||
fun updateData(newList: List<AppInfo>) {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(AppsDiffUtil(oldList, newList))
|
||||
//
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
|
||||
// [수정 전] dispatchUpdatesTo가 먼저 있어서 에러 발생함
|
||||
// diffUtilResult.dispatchUpdatesTo(this)
|
||||
// oldList.clear()
|
||||
// oldList.addAll(newList)
|
||||
|
||||
// [수정 후] 반드시 리스트 데이터를 먼저 갱신하고 나서 알림을 보내야 합니다!
|
||||
oldList.clear()
|
||||
oldList.addAll(newList)
|
||||
diffUtilResult.dispatchUpdatesTo(this) // <-- 순서 변경 (맨 뒤로)
|
||||
|
||||
newList.size.let {
|
||||
appsCount?.text = it.toString()
|
||||
appsSize = it
|
||||
|
||||
@ -27,6 +27,7 @@ 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.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
@ -80,9 +81,9 @@ internal class ContactAdapter (
|
||||
synchronized(oldList) {
|
||||
try {
|
||||
val diffUtilResult = DiffUtil.calculateDiff(ContactDiffUtil(oldList, newList))
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
oldList.clear()
|
||||
oldList.addAll(newList)
|
||||
diffUtilResult.dispatchUpdatesTo(this)
|
||||
newList.size.let {
|
||||
appsSize = it
|
||||
}
|
||||
@ -107,25 +108,7 @@ internal class ContactAdapter (
|
||||
|
||||
}
|
||||
}
|
||||
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>
|
||||
|
||||
@ -27,6 +27,7 @@ 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.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
||||
@ -1,243 +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.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.content.Context
|
||||
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
|
||||
import android.util.LruCache // 이거 임포트
|
||||
|
||||
|
||||
internal class IconPackManager {
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
class IconPackManager {
|
||||
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 }
|
||||
}
|
||||
// [추가] 메모리 캐시 (최대 4MB 또는 아이콘 100개 분량 등 설정)
|
||||
private val iconCache = object : LruCache<String, Drawable>(100) {
|
||||
// 사이즈 측정 로직이 필요하면 sizeOf 오버라이드 (여기선 갯수 기준 100개)
|
||||
}
|
||||
|
||||
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
|
||||
fun getDrawableIconForPackage(context: Context, packageName: String): Drawable? {
|
||||
// 1. 캐시에 있는지 확인
|
||||
val cachedIcon = iconCache.get(packageName)
|
||||
if (cachedIcon != null) {
|
||||
return cachedIcon
|
||||
}
|
||||
|
||||
private fun loadDrawable(drawableName: String): Drawable? {
|
||||
iconPackRes!!.getIdentifier(drawableName, "drawable", packageName).let {
|
||||
return if (it > 0) ResourcesCompat.getDrawable(iconPackRes!!, it, null)
|
||||
else null
|
||||
}
|
||||
// 2. 없으면 로딩 (기존 로직 수행)
|
||||
// ... (기존의 아이콘팩 로딩 또는 pm.getApplicationIcon 코드) ...
|
||||
val loadedIcon = try {
|
||||
context.packageManager.getApplicationIcon(packageName)
|
||||
// 만약 아이콘팩 적용 로직이 있다면 여기서 변환 수행
|
||||
} catch (e: Exception) {
|
||||
context.resources.getDrawable(android.R.drawable.sym_def_app_icon, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 3. 로딩된 아이콘 캐시에 저장
|
||||
if (loadedIcon != null) {
|
||||
iconCache.put(packageName, loadedIcon)
|
||||
}
|
||||
|
||||
return loadedIcon
|
||||
}
|
||||
|
||||
// 아이콘팩 변경 시 호출해줄 함수
|
||||
fun clearCache() {
|
||||
iconCache.evictAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package bums.lunatic.launcher.apps
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import bums.lunatic.launcher.databinding.AppsChildBinding // 기존 레이아웃 재사용 (또는 별도 레이아웃 생성)
|
||||
import bums.lunatic.launcher.databinding.AppsChildRecBinding
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class RecommendedAppsAdapter(private val pm: PackageManager) : RecyclerView.Adapter<RecommendedAppsAdapter.ViewHolder>() {
|
||||
|
||||
private val items = ArrayList<AppInfo>()
|
||||
|
||||
fun submitList(newItems: List<AppInfo>) {
|
||||
items.clear()
|
||||
items.addAll(newItems)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
// 기존 apps_child.xml을 재사용하거나, 아이콘만 보여주는 새로운 xml을 만들어도 됨
|
||||
val binding = AppsChildRecBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||
return ViewHolder(binding)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item = items[position]
|
||||
|
||||
// 앱 이름 (추천 영역에서는 숨기거나 작게 표시 가능)
|
||||
holder.binding.childTextview.text = item.appName
|
||||
// holder.binding.appName.visibility = View.GONE // 이름 숨기고 싶으면 주석 해제
|
||||
|
||||
// 아이콘 비동기 로딩 (이전 성능 최적화 코드 적용)
|
||||
holder.binding.appIconTwo.setImageDrawable(null)
|
||||
holder.binding.appIconTwo.tag = item.pkgName
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
val icon = withContext(Dispatchers.IO) {
|
||||
IconPackManager.getDrawableIconForPackage(holder.binding.root.context, item.pkgName ?: "")
|
||||
}
|
||||
if (holder.binding.appIconTwo.tag == item.pkgName) {
|
||||
holder.binding.appIconTwo.setImageDrawable(icon)
|
||||
}
|
||||
}
|
||||
|
||||
// 클릭 이벤트
|
||||
holder.itemView.setOnClickListener {
|
||||
try {
|
||||
val intent = pm.getLaunchIntentForPackage(item.pkgName ?: "")
|
||||
if (intent != null) {
|
||||
holder.itemView.context.startActivity(intent)
|
||||
// [중요] 사용 로그 저장 -> 추천 정확도 상승
|
||||
WorkersDb.logAppUsage(item.pkgName ?: "", "APP")
|
||||
}
|
||||
} catch (e: Exception) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = items.size
|
||||
|
||||
class ViewHolder(val binding: AppsChildRecBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
}
|
||||
@ -20,29 +20,19 @@ import android.os.IBinder
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.OutOfQuotaPolicy
|
||||
import androidx.work.PeriodicWorkRequestBuilder
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.LauncherActivity
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.ArcaGetter
|
||||
import bums.lunatic.launcher.workers.ClienGetter
|
||||
import bums.lunatic.launcher.workers.DCGetter
|
||||
import bums.lunatic.launcher.workers.DotaxGetter
|
||||
import bums.lunatic.launcher.workers.DotaxGetter.Companion.COMIC2_WORK_TAG
|
||||
import bums.lunatic.launcher.workers.FmKoreaGetter
|
||||
import bums.lunatic.launcher.workers.FmKoreaGetter.Companion.FM_WORK_TAG
|
||||
import bums.lunatic.launcher.workers.LocationGetter
|
||||
import bums.lunatic.launcher.workers.NewsFeedsGetter
|
||||
import bums.lunatic.launcher.workers.NewsFeedsGetter.Companion.FEDDS_WORK_TAG
|
||||
import bums.lunatic.launcher.workers.RedditGetter
|
||||
import bums.lunatic.launcher.workers.RedditGetter.Companion.REDDIT_WORK_TAG
|
||||
import bums.lunatic.launcher.workers.RuliWebGetter
|
||||
import bums.lunatic.launcher.workers.TheQooGetter
|
||||
import bums.lunatic.launcher.workers.YoutubeGetter
|
||||
import bums.lunatic.launcher.workers.YoutubeGetter.Companion.YT_WORK_TAG
|
||||
import bums.lunatic.launcher.workers.TaskAggregator
|
||||
import com.yausername.youtubedl_android.YoutubeDL
|
||||
import com.yausername.youtubedl_android.YoutubeDLRequest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@ -58,6 +48,22 @@ import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
class AggregatedSystemWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
override suspend fun doWork(): Result {
|
||||
// 통합 시스템 작업 실행
|
||||
TaskAggregator.refreshSystemInfo(context)
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
class AggregatedNewsWorker(private val context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
|
||||
override suspend fun doWork(): Result {
|
||||
// 통합 뉴스 작업 실행
|
||||
TaskAggregator.refreshNewsFeeds(context = context)
|
||||
return Result.success()
|
||||
}
|
||||
}
|
||||
|
||||
class ForeGroundService : Service() {
|
||||
companion object {
|
||||
val ACTION_SENDMSG = "ACTION_SEND_TO_LOVE"
|
||||
@ -225,85 +231,26 @@ class ForeGroundService : Service() {
|
||||
}
|
||||
|
||||
fun refreshFeeds() {
|
||||
mWorkManager?.cancelAllWork()
|
||||
mWorkManager?.cancelAllWorkByTag(RuliWebGetter.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
RuliWebGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<RuliWebGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(RuliWebGetter.TAG)
|
||||
.build())
|
||||
val workManager = WorkManager.getInstance(applicationContext)
|
||||
|
||||
mWorkManager?.cancelAllWorkByTag(FEDDS_WORK_TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
FEDDS_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<NewsFeedsGetter>(PrefLong.shortTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(FEDDS_WORK_TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(YT_WORK_TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
YT_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<YoutubeGetter>(PrefLong.longTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(YT_WORK_TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(REDDIT_WORK_TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
REDDIT_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<RedditGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(REDDIT_WORK_TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(FM_WORK_TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
FM_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<FmKoreaGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(FM_WORK_TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(COMIC2_WORK_TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
COMIC2_WORK_TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<DotaxGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(COMIC2_WORK_TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(ClienGetter.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
ClienGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<ClienGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(ClienGetter.TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(DCGetter.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
DCGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<DCGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(DCGetter.TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(TheQooGetter.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
TheQooGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<TheQooGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(TheQooGetter.TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(ArcaGetter.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
ArcaGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<ArcaGetter>(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(ArcaGetter.TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(LocationGetter.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
LocationGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE,
|
||||
PeriodicWorkRequestBuilder<LocationGetter>(PrefLong.locationTimePeriod.get(), TimeUnit.MINUTES)
|
||||
.addTag(LocationGetter.TAG)
|
||||
.build())
|
||||
mWorkManager?.cancelAllWorkByTag(ServiceWatchdogWorker.TAG)
|
||||
mWorkManager?.enqueueUniquePeriodicWork(
|
||||
ServiceWatchdogWorker.TAG,
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
PeriodicWorkRequestBuilder<ServiceWatchdogWorker>(15, TimeUnit.MINUTES)
|
||||
.addTag(ServiceWatchdogWorker.TAG)
|
||||
.build())
|
||||
|
||||
TaskAggregator.refreshSystemInfo(applicationContext)
|
||||
|
||||
// 1. 시스템 정보: 자주 변경되지 않으므로 1~3시간 간격
|
||||
val systemRequest = PeriodicWorkRequestBuilder<AggregatedSystemWorker>(PrefLong.longTimePeriod.get(120L), TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
// 2. 뉴스 피드: 사용자가 설정한 간격 (예: 1시간)
|
||||
val newsRequest = PeriodicWorkRequestBuilder<AggregatedNewsWorker>(PrefLong.shortTimePeriod.get(20L), TimeUnit.MINUTES)
|
||||
.build()
|
||||
|
||||
// 기존의 수많은 enqueue 코드를 이 두 개로 대체
|
||||
workManager.enqueueUniquePeriodicWork("AggregatedSystemWork", ExistingPeriodicWorkPolicy.KEEP, systemRequest)
|
||||
workManager.enqueueUniquePeriodicWork("AggregatedNewsWork", ExistingPeriodicWorkPolicy.KEEP, newsRequest)
|
||||
}
|
||||
|
||||
|
||||
|
||||
fun workmanager() : WorkManager? {
|
||||
if (mWorkManager == null && lActivity != null) {
|
||||
mWorkManager = WorkManager.getInstance(lActivity!!)
|
||||
|
||||
@ -18,4 +18,9 @@ class AppInfo : RealmObject {
|
||||
var category : String? = null
|
||||
var currentInstalled : Boolean = false
|
||||
var isInstalled : Boolean = false
|
||||
// [신규] 0: 항상 보이기(기본), 1: 검색 시만 보이기 (숨김)
|
||||
var visibilityMode: Int = 0
|
||||
|
||||
// [신규] true면 추천 리스트에 절대 안 뜸
|
||||
var blockRecommend: Boolean = false
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
import org.mongodb.kbson.ObjectId
|
||||
|
||||
class AppUsageLog : RealmObject {
|
||||
@PrimaryKey
|
||||
var _id: ObjectId = ObjectId()
|
||||
|
||||
var itemKey: String = "" // 패키지명(앱) 또는 연락처 URI(연락처)
|
||||
var itemType: String = "APP" // "APP" 또는 "CONTACT"
|
||||
|
||||
var timestamp: Long = 0L // 사용 시간 (밀리초)
|
||||
var month: Int = 0 // 0~11 (Calendar.MONTH)
|
||||
var dayOfMonth: Int = 0 // 1~31
|
||||
var dayOfWeek: Int = 0 // 1(일)~7(토)
|
||||
var hour: Int = 0 // 0~23 (24시간제)
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package bums.lunatic.launcher.model
|
||||
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import io.realm.kotlin.types.annotations.PrimaryKey
|
||||
|
||||
class SimpleContact : RealmObject {
|
||||
@PrimaryKey
|
||||
var id : String? = ""
|
||||
var name : String? = ""
|
||||
var chosung : String? = ""
|
||||
var phoneNumber : String? = ""
|
||||
var touchCount = 0
|
||||
var lastedTouchDateTime = 0L
|
||||
// [신규] 0: 항상 보이기(기본), 1: 검색 시만 보이기 (숨김)
|
||||
var visibilityMode: Int = 0
|
||||
|
||||
// [신규] true면 추천 리스트에 절대 안 뜸
|
||||
var blockRecommend: Boolean = false
|
||||
constructor(id: String, name: String, phoneNumber: String) {
|
||||
this.id = id
|
||||
this.name = name
|
||||
this.phoneNumber = phoneNumber
|
||||
chosung = JamoUtils.split(name).joinToString("")
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
}
|
||||
@ -45,7 +45,6 @@ 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.apps.SimpleContact
|
||||
import bums.lunatic.launcher.databinding.QuickAccessBinding
|
||||
import bums.lunatic.launcher.databinding.ShortcutMakerBinding
|
||||
import bums.lunatic.launcher.helpers.ColorPicker
|
||||
@ -60,6 +59,7 @@ 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 bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.workers.WorkersDb
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
|
||||
@ -43,9 +43,9 @@ class PackageEventReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
fun startAppInfoGetter(context: Context) {
|
||||
var mWorkManager = WorkManager.getInstance(context)
|
||||
Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
mWorkManager.enqueue(OneTimeWorkRequest.from(AppInfoGetter::class.java))
|
||||
}, 5, TimeUnit.SECONDS)
|
||||
// var mWorkManager = WorkManager.getInstance(context)
|
||||
// Executors.newSingleThreadScheduledExecutor().schedule({
|
||||
// mWorkManager.enqueue(OneTimeWorkRequest.from(AppInfoGetter::class.java))
|
||||
// }, 5, TimeUnit.SECONDS)
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,7 +25,6 @@ import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.R
|
||||
import bums.lunatic.launcher.apps.SimpleContact
|
||||
import bums.lunatic.launcher.common.CommonActivity
|
||||
import bums.lunatic.launcher.databinding.AboutBinding
|
||||
import bums.lunatic.launcher.databinding.SettingsActivityBinding
|
||||
@ -34,6 +33,7 @@ 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.model.AppInfo
|
||||
import bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.settings.childs.Apps
|
||||
import bums.lunatic.launcher.settings.childs.HomeSettings
|
||||
import bums.lunatic.launcher.settings.childs.Misc
|
||||
|
||||
@ -12,8 +12,10 @@ import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.apps.AppDrawer.Companion.appNamesPrefs
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.utils.AlphabetToChosungMap
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.utils.JamoUtils
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import java.text.Normalizer
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@ -21,48 +23,49 @@ class AppInfoGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "AppInfoGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
constructor(context: Context) : super(context) {
|
||||
|
||||
}
|
||||
override fun realWork(): Result {
|
||||
|
||||
|
||||
override fun realWork(): List<RealmObject> {
|
||||
var result = mutableListOf<RealmObject>()
|
||||
try {
|
||||
val packageManager = lActivity?.packageManager ?: return result
|
||||
|
||||
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)
|
||||
)
|
||||
// 1. 설치된 앱 목록 가져오기 (시스템 호출 1회)
|
||||
val intent = Intent(Intent.ACTION_MAIN, null).addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
val resolveInfos = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageManager.queryIntentActivities(intent, 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)
|
||||
packageManager.queryIntentActivities(intent, 0)
|
||||
}
|
||||
}
|
||||
}!!
|
||||
|
||||
} catch (e : Exception) {e.printStackTrace()}
|
||||
return Result.success()
|
||||
resolveInfos.forEach { ri ->
|
||||
val pkgName = ri.activityInfo.packageName
|
||||
if (pkgName == BuildConfig.APPLICATION_ID) return@forEach
|
||||
|
||||
// 이미 DB에 있는지 Map에서 확인 (고속 검색)
|
||||
// 신규 앱 발견 -> 추가
|
||||
val appName = normalize(appName(ri))
|
||||
|
||||
result.add(AppInfo().apply {
|
||||
this.appName = appName
|
||||
this.pkgName = pkgName
|
||||
this.category = getCategory(ri.activityInfo.applicationInfo.category)
|
||||
this.alphaCho = AlphabetToChosungMap.getCho(appName)
|
||||
this.appNameChosung = JamoUtils.split(appName).joinToString("")
|
||||
})
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
return result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
fun appName(resolver: ResolveInfo): String {
|
||||
return resolver.loadLabel(lActivity?.packageManager!!).toString().apply {
|
||||
appNamesPrefs?.edit()?.putString(resolver.activityInfo.packageName, this)?.apply()
|
||||
|
||||
@ -1,108 +1,108 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
//import bums.lunatic.launcher.workers.WorkersDb.blockKeyword
|
||||
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.getT
|
||||
import bums.lunatic.launcher.utils.beforeOneDay
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class ArcaGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "ArcaGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
override fun realWork(): Result {
|
||||
// RssDataType.ARCA.isOn {
|
||||
// try {
|
||||
// Blog.LOGE("realWork() ${this::class.simpleName}")
|
||||
// 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 {
|
||||
//package bums.lunatic.launcher.workers
|
||||
//
|
||||
////import bums.lunatic.launcher.workers.WorkersDb.blockKeyword
|
||||
//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.getT
|
||||
//import bums.lunatic.launcher.utils.beforeOneDay
|
||||
//import org.jsoup.nodes.Element
|
||||
//
|
||||
//class ArcaGetter : BaseGetter {
|
||||
// companion object {
|
||||
// val TAG = "ArcaGetter"
|
||||
// }
|
||||
// constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// override fun realWork(): Result {
|
||||
//// RssDataType.ARCA.isOn {
|
||||
//// try {
|
||||
//// Jsoup.connect(it)
|
||||
//// .userAgent(USAGT)
|
||||
//// .header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
|
||||
//// .header("accept-language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
|
||||
//// .header("cache-control", "no-cache")
|
||||
//// .header("pragma", "no-cache")
|
||||
//// .ignoreContentType(true)
|
||||
//// .timeout(5000)
|
||||
//// .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())
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// Blog.LOGE("realWork() ${this::class.simpleName}")
|
||||
//// 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 {
|
||||
////// try {
|
||||
////// Jsoup.connect(it)
|
||||
////// .userAgent(USAGT)
|
||||
////// .header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
|
||||
////// .header("accept-language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
|
||||
////// .header("cache-control", "no-cache")
|
||||
////// .header("pragma", "no-cache")
|
||||
////// .ignoreContentType(true)
|
||||
////// .timeout(5000)
|
||||
////// .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())
|
||||
////// }
|
||||
////// }
|
||||
////// }
|
||||
////// }
|
||||
////// }
|
||||
////// }
|
||||
////// } catch (e : Exception) {
|
||||
////// e.printStackTrace()
|
||||
////// }
|
||||
////
|
||||
//// }
|
||||
////// Jsoup.connect("https://projrctjav.com").userAgent(USAGT).get().let { projectj ->
|
||||
////// BLog.LOGE("projectj >>>>> ${projectj}")
|
||||
////// }
|
||||
//// } catch (e: Exception) {
|
||||
//// e.printStackTrace()
|
||||
//// }
|
||||
//
|
||||
// }
|
||||
//// 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)
|
||||
// }
|
||||
// }
|
||||
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) {
|
||||
// if(blockKeyword.filter { desc.contains(it) }.size == 0) {
|
||||
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() > beforeOneDay()) {
|
||||
tempArray.add(this)
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
//// if(blockKeyword.filter { desc.contains(it) }.size == 0) {
|
||||
// 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() > beforeOneDay()) {
|
||||
// tempArray.add(this)
|
||||
// }
|
||||
// }
|
||||
//// }
|
||||
// }
|
||||
// return tempArray
|
||||
// }
|
||||
//
|
||||
//
|
||||
//}
|
||||
}
|
||||
return tempArray
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -8,10 +8,11 @@ import bums.lunatic.launcher.LunaticLauncher
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.utils.beforeDay
|
||||
import bums.lunatic.launcher.utils.beforeOneDay
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
open abstract class BaseGetter : Worker {
|
||||
abstract class BaseGetter(internal val context: Context) {
|
||||
protected companion object {
|
||||
var lastedUpdateTime = 0L
|
||||
val defaultDay = 3
|
||||
@ -29,21 +30,8 @@ open abstract class BaseGetter : Worker {
|
||||
val commicsDateTime = beforeDay(1)
|
||||
val temp = arrayListOf<RssData>()
|
||||
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
override fun doWork(): Result {
|
||||
LunaticLauncher.mHourlyLogWriter?.writeLog("${this::class.java.simpleName} doWork()")
|
||||
val currentTime = before10Min()
|
||||
if (lastedUpdateTime > 0L && currentTime > lastedUpdateTime) {
|
||||
return Result.success().apply {
|
||||
|
||||
abstract fun realWork() : List<RealmObject>
|
||||
open suspend fun fetchData(): List<RealmObject> {
|
||||
return realWork()
|
||||
}
|
||||
}
|
||||
return realWork().apply {
|
||||
LunaticLauncher.mHourlyLogWriter?.writeLog("${this@BaseGetter::class.java.simpleName} return realWork() ")
|
||||
}
|
||||
}
|
||||
abstract fun realWork() : Result
|
||||
}
|
||||
@ -1,149 +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()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//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
|
||||
//import io.realm.kotlin.types.RealmObject
|
||||
//
|
||||
//
|
||||
//class CalendarGetter : BaseGetter {
|
||||
// companion object {
|
||||
// val TAG = "DCGetter"
|
||||
// }
|
||||
// constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
// override fun realWork(): List<RealmObject> {
|
||||
// return setCalendar()
|
||||
// }
|
||||
//
|
||||
// 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()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//}
|
||||
@ -9,14 +9,14 @@ import bums.lunatic.launcher.model.getHref
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.model.getT
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class ClienGetter : BaseGetter {
|
||||
class ClienGetter(context: Context) : BaseGetter(context = context) {
|
||||
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}")
|
||||
@ -44,33 +44,12 @@ class ClienGetter : BaseGetter {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
RssDataType.CLIEN.isOn {
|
||||
Blog.LOGE("realWork() ${this::class.simpleName}")
|
||||
try {
|
||||
@ -90,8 +69,6 @@ class ClienGetter : BaseGetter {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -4,17 +4,18 @@ 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 bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.ext.query
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
|
||||
class ContactInfoGetter : BaseGetter {
|
||||
class ContactInfoGetter(context: Context) : BaseGetter(context) {
|
||||
companion object {
|
||||
val TAG = "ContactInfoGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
var temp = mutableListOf<SimpleContact>()
|
||||
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
|
||||
val projection = arrayOf(
|
||||
ContactsContract.CommonDataKinds.Phone.CONTACT_ID,
|
||||
@ -23,28 +24,31 @@ class ContactInfoGetter : BaseGetter {
|
||||
)
|
||||
|
||||
try {
|
||||
val cursor = lActivity?.contentResolver?.query(phoneUri, projection, null, null, null)
|
||||
if (cursor != null) {
|
||||
|
||||
lActivity?.contentResolver?.query(phoneUri, projection, null, null, null)?.use { cursor ->
|
||||
val idIdx = cursor.getColumnIndex(projection[0])
|
||||
val nameIdx = cursor.getColumnIndex(projection[1])
|
||||
val numberIdx = cursor.getColumnIndex(projection[2])
|
||||
|
||||
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))
|
||||
val contactId = cursor.getString(idIdx)
|
||||
|
||||
val name = cursor.getString(nameIdx)
|
||||
// 루프 내 불필요한 객체 생성 방지 및 null 처리
|
||||
val rawNumber = cursor.getString(numberIdx)
|
||||
val number = rawNumber?.replace("-", "") ?: ""
|
||||
|
||||
if (!name.isNullOrEmpty() && number.isNotEmpty()) {
|
||||
temp.add(SimpleContact(contactId, name, number))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 데이터 계열은 반드시 닫아줘야 한다.
|
||||
cursor?.close()
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return Result.success()
|
||||
Blog.LOGE("ContactInfoGetter >>> ${temp.size}")
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -9,15 +9,15 @@ import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import org.jsoup.Jsoup
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
class DCGetter : BaseGetter {
|
||||
class DCGetter(context: Context) : BaseGetter(context) {
|
||||
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>()
|
||||
@ -71,7 +71,7 @@ class DCGetter : BaseGetter {
|
||||
}
|
||||
val df = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
// Blog.LOGE("${TAG} RssDataType.DCINSIDE.isOn >>>> ${PrefHelper.getBoolean(RssDataType.DCINSIDE.name,false)}")
|
||||
RssDataType.DCINSIDE.isOn {
|
||||
Blog.LOGE("realWork() ${this::class.simpleName}")
|
||||
@ -161,12 +161,6 @@ class DCGetter : BaseGetter {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
try {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
} catch (e : Exception) {
|
||||
|
||||
}
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -6,30 +6,24 @@ import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.LauncherActivity
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DotaxGetter : BaseGetter {
|
||||
class DotaxGetter(context: Context) : BaseGetter(context) {
|
||||
companion object {
|
||||
val COMIC2_WORK_TAG = "ComicGetter2"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
RssDataType.DOTAX.isOn {
|
||||
try {
|
||||
Blog.LOGE("realWork() ${this::class.simpleName}")
|
||||
temp.clear()
|
||||
// val dotaxUrls = arrayListOf("https://cafe.daum.net/dotax",
|
||||
// "https://m.cafe.daum.net/dotax/Elgq"
|
||||
//// "https://m.cafe.daum.net/dotax/_rec?page=3"
|
||||
// )
|
||||
// dotaxUrls?.forEach { url ->
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
withContext(Dispatchers.Main) {
|
||||
LauncherActivity.lActivity?.let {
|
||||
@ -37,32 +31,8 @@ class DotaxGetter : BaseGetter {
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
// dotaxUrls?.forEach {
|
||||
// Jsoup.connect(it).timeout(3000).userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36").get()?.let { dotax ->
|
||||
// Blog.LOGE("dotax.html() >>> ${dotax.html()}")
|
||||
// dotax.getElementsByTag("tr").forEach { dotax_li ->
|
||||
// Blog.LOGE("dotax_li >>> ${dotax_li.text()}")
|
||||
// if (dotax_li.getElementsByTag("a").size > 0) {
|
||||
// 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)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -7,16 +7,14 @@ import bums.lunatic.launcher.model.FmKorea
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.Date
|
||||
|
||||
class FmKoreaGetter : BaseGetter {
|
||||
class FmKoreaGetter (context: Context): BaseGetter(context) {
|
||||
companion object {
|
||||
val FM_WORK_TAG = "FmKoreaGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
fun extractDocumentSrl(url: String): String? {
|
||||
val uri = java.net.URI(url)
|
||||
@ -33,7 +31,8 @@ class FmKoreaGetter : BaseGetter {
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
temp.clear()
|
||||
RssDataType.FMKORAE.isOn {
|
||||
val now = Date()
|
||||
try {
|
||||
@ -97,8 +96,6 @@ class FmKoreaGetter : BaseGetter {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -1,54 +1,55 @@
|
||||
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.common.letTrue
|
||||
import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
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 android.annotation.SuppressLint
|
||||
//import android.content.Context
|
||||
//import android.location.Location
|
||||
//import androidx.work.WorkerParameters
|
||||
//import bums.lunatic.launcher.common.letTrue
|
||||
//import bums.lunatic.launcher.helpers.PrefBoolean
|
||||
//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 io.realm.kotlin.types.RealmObject
|
||||
import kotlin.math.cos
|
||||
|
||||
class LocationGetter(context: Context, workerParams: WorkerParameters) : BaseGetter(context, workerParams) {
|
||||
companion object {
|
||||
val TAG = "LocationGetter"
|
||||
var longitude: Double = 0.0
|
||||
var latitude: Double = 0.0
|
||||
}
|
||||
|
||||
@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()
|
||||
PrefBoolean.location.get().letTrue {
|
||||
pushLocation(this.applicationContext,it.latitude, it.longitude)
|
||||
}
|
||||
}
|
||||
}.addOnFailureListener{
|
||||
Blog.LOGE("Location error >>> $it")
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//class LocationGetter(context: Context) : BaseGetter(context) {
|
||||
// companion object {
|
||||
// val TAG = "LocationGetter"
|
||||
// var longitude: Double = 0.0
|
||||
// var latitude: Double = 0.0
|
||||
// }
|
||||
//
|
||||
// @SuppressLint("MissingPermission")
|
||||
// override fun realWork(): List<RealmObject> {
|
||||
//// Blog.LOGE("${OpenWeatherGetter.TAG} realWork()")
|
||||
//
|
||||
// LocationServices.getFusedLocationProviderClient(context)
|
||||
// .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()
|
||||
// PrefBoolean.location.get().letTrue {
|
||||
// pushLocation(context,it.latitude, it.longitude)
|
||||
// }
|
||||
// }
|
||||
// }.addOnFailureListener{
|
||||
// Blog.LOGE("Location error >>> $it")
|
||||
// }
|
||||
//
|
||||
// return temp
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
//}
|
||||
//
|
||||
//
|
||||
val EARTH_RADIUS_METERS = 6371000
|
||||
val LATITUDE_DEGREE_PER_METER: Double = 1.0 / (2 * Math.PI * EARTH_RADIUS_METERS / 360)
|
||||
|
||||
@ -70,8 +71,8 @@ fun longitudeRange(latitude: Double, longitude: Double, radiusInMeters: Int): Do
|
||||
|
||||
return doubleArrayOf(minLongitude, maxLongitude)
|
||||
}
|
||||
|
||||
|
||||
//https://jinkpark.tistory.com/296
|
||||
//https://develoyummer.tistory.com/103
|
||||
//https://ghj1001020.tistory.com/300
|
||||
//
|
||||
//
|
||||
////https://jinkpark.tistory.com/296
|
||||
////https://develoyummer.tistory.com/103
|
||||
////https://ghj1001020.tistory.com/300
|
||||
@ -18,8 +18,6 @@ import bums.lunatic.launcher.helpers.PrefString
|
||||
import bums.lunatic.launcher.model.LocationLog
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import bums.lunatic.launcher.utils.inRange
|
||||
import bums.lunatic.launcher.workers.LocationGetter.Companion.latitude
|
||||
import bums.lunatic.launcher.workers.LocationGetter.Companion.longitude
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.gson.Gson
|
||||
import io.realm.kotlin.ext.query
|
||||
@ -42,6 +40,8 @@ import java.util.concurrent.TimeUnit
|
||||
class LocationUpdateService : Service(), LocationListener {
|
||||
|
||||
companion object {
|
||||
var longitude: Double = 0.0
|
||||
var latitude: Double = 0.0
|
||||
fun pushLocation(context: Context, lat :Double, long : Double) {
|
||||
try {
|
||||
Blog.LOGE("Location >>> ${lat},${long}")
|
||||
@ -179,7 +179,6 @@ class LocationUpdateService : Service(), LocationListener {
|
||||
).show()
|
||||
longitude = location.longitude
|
||||
latitude = location.latitude
|
||||
// runWeatherGetter()
|
||||
pushLocation(this.applicationContext, location.latitude, location.longitude)
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,17 +7,17 @@ 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
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
|
||||
class NewsFeedsGetter : BaseGetter {
|
||||
class NewsFeedsGetter(context: Context) : BaseGetter(context) {
|
||||
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 {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
RssDataType.NEWSFEED.isOn {
|
||||
feddsUrls.clear()
|
||||
feddsUrls.addAll(RssList.newsFeeds)
|
||||
@ -32,15 +32,7 @@ class NewsFeedsGetter : BaseGetter {
|
||||
}
|
||||
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
|
||||
// temp.forEach { synchronized(rssSet){
|
||||
// rssSet.put(it.originPage(), it)
|
||||
// } }.run {
|
||||
// rssSetTouchCount -= 1
|
||||
// Result.success() }
|
||||
return temp
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -1,89 +1,90 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.helpers.PrefString
|
||||
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 ${PrefString.weatherApiKey.get()}")
|
||||
///saved weatherForcast
|
||||
Retrofit.Builder()
|
||||
.baseUrl(URI_WEATHERAPI)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
.create<RestrofitService>()
|
||||
.getForecast( // weatherApi
|
||||
ver = VER_WEATHERAPI,
|
||||
key = PrefString.weatherApiKey.get(),
|
||||
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?>?
|
||||
}
|
||||
|
||||
//package bums.lunatic.launcher.workers
|
||||
//
|
||||
//import android.content.Context
|
||||
//import androidx.work.WorkerParameters
|
||||
//import bums.lunatic.launcher.helpers.PrefString
|
||||
//import bums.lunatic.launcher.model.WeatherForcast
|
||||
//import bums.lunatic.launcher.model.WeatherInfoManager
|
||||
//import bums.lunatic.launcher.utils.Blog
|
||||
//import io.realm.kotlin.UpdatePolicy
|
||||
//import io.realm.kotlin.types.RealmObject
|
||||
//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) : BaseGetter(context) {
|
||||
// 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(): List<RealmObject> {
|
||||
// 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 ${PrefString.weatherApiKey.get()}")
|
||||
// ///saved weatherForcast
|
||||
// Retrofit.Builder()
|
||||
// .baseUrl(URI_WEATHERAPI)
|
||||
// .addConverterFactory(GsonConverterFactory.create())
|
||||
// .build()
|
||||
// .create<RestrofitService>()
|
||||
// .getForecast( // weatherApi
|
||||
// ver = VER_WEATHERAPI,
|
||||
// key = PrefString.weatherApiKey.get(),
|
||||
// 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?>?
|
||||
//}
|
||||
//
|
||||
|
||||
@ -5,7 +5,6 @@ import android.content.Context
|
||||
import android.provider.CallLog
|
||||
import androidx.work.WorkerParameters
|
||||
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
|
||||
import bums.lunatic.launcher.apps.SimpleContact
|
||||
import bums.lunatic.launcher.utils.beforeDay
|
||||
import bums.lunatic.launcher.utils.getContactId
|
||||
import com.google.gson.Gson
|
||||
@ -48,18 +47,16 @@ class RecentCall : RealmObject {
|
||||
|
||||
}
|
||||
|
||||
class RecentCallGetter : BaseGetter {
|
||||
class RecentCallGetter(context: Context) : BaseGetter(context) {
|
||||
companion object{
|
||||
var dayRange = BaseGetter.defaultDay
|
||||
val TAG = "RecentCallGetter"
|
||||
val dateFormat = SimpleDateFormat("yyy/MM/dd-HH:mm:ss")
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
|
||||
override fun realWork(): List<RealmObject> {
|
||||
var temp = mutableListOf<RecentCall>()
|
||||
var dateParam = beforeDay(dayRange).toString()
|
||||
var managedCursor = lActivity?.contentResolver?.query(
|
||||
CallLog.Calls.CONTENT_URI, arrayOf(
|
||||
@ -69,8 +66,7 @@ class RecentCallGetter : BaseGetter {
|
||||
CallLog.Calls.DURATION,
|
||||
CallLog.Calls.CACHED_NAME,
|
||||
), CallLog.Calls.DATE + " >= ? " , arrayOf<String>(dateParam), CallLog.Calls.DATE + " desc")
|
||||
//+ CallLog.Calls.TYPE + " > ?"
|
||||
//, "2"
|
||||
|
||||
if(managedCursor != null && managedCursor.isClosed == false) {
|
||||
try {
|
||||
val number = managedCursor.getColumnIndex(CallLog.Calls.NUMBER)
|
||||
@ -97,8 +93,7 @@ class RecentCallGetter : BaseGetter {
|
||||
CallLog.Calls.BLOCKED_TYPE -> { dir = "BLOCKED_TYPE" }
|
||||
CallLog.Calls.ANSWERED_EXTERNALLY_TYPE -> { dir = "ANSWERED_EXTERNALLY_TYPE" }
|
||||
}
|
||||
|
||||
val call = RecentCall(
|
||||
temp.add(RecentCall(
|
||||
1,
|
||||
callerName,
|
||||
phNumber,
|
||||
@ -107,27 +102,8 @@ class RecentCallGetter : BaseGetter {
|
||||
dateFormat.format(Date(callDayTime)),
|
||||
callDayTime,
|
||||
callDuration.toLong()
|
||||
)
|
||||
call.uniqK?.let {
|
||||
// BLog.LOGE("${TAG} call.uniqK? >>> ${call.uniqK}")
|
||||
WorkersDb.getRealm().writeBlocking {
|
||||
if(query<RecentCall>().query("uniqK == $0", it).find().count() < 1) {
|
||||
this.copyToRealm(call, UpdatePolicy.ALL)
|
||||
var cId = getContactId(lActivity!!.contentResolver, call.number)
|
||||
if (cId!=null && cId.length > 0) {
|
||||
val result = query<SimpleContact>().query("id == $0", cId).find()
|
||||
if(result.size > 0){
|
||||
var contact = result.first()
|
||||
contact.touchCount = contact.touchCount + 1
|
||||
contact.lastedTouchDateTime = Math.max(contact.lastedTouchDateTime, callDayTime)
|
||||
// BLog.LOGE("${TAG} updatetouch info >>>> ${contact.touchCount}")
|
||||
}
|
||||
}
|
||||
}
|
||||
// BLog.LOGE("${TAG} updatetouch info >>>> BE ")
|
||||
}
|
||||
// BLog.LOGE("${TAG} updatetouch info >>>> E")
|
||||
}
|
||||
))
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
@ -135,9 +111,7 @@ class RecentCallGetter : BaseGetter {
|
||||
managedCursor.close()
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
dayRange = BaseGetter.defaultDay
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
}
|
||||
@ -23,20 +23,16 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
|
||||
class RecentSmsGetter : BaseGetter {
|
||||
class RecentSmsGetter(context: Context) : BaseGetter(context) {
|
||||
companion object {
|
||||
var dayRange = BaseGetter.defaultDay
|
||||
val SMS_WORK_TAG = "RecentSmsGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
var temp = mutableListOf<RecentSms>()
|
||||
var dateParam = beforeDay(dayRange).toString()
|
||||
val managedCursor = lActivity?.contentResolver?.query(
|
||||
Telephony.Sms.CONTENT_URI, arrayOf(
|
||||
@ -88,15 +84,8 @@ class RecentSmsGetter : BaseGetter {
|
||||
)
|
||||
log.isMms = false
|
||||
// BLog.LOGE("RecentSmsGetter resultData put ${phNumber +"_"+ reciveDate} >>> ${log.toJson()}")
|
||||
log.sender = getContactName(applicationContext.contentResolver,phNumber) ?: ""
|
||||
WorkersDb.getRealm().apply {
|
||||
if (query<RecentSms>("uniqKey == $0", log.uniqKey).find().size == 0) {
|
||||
writeBlocking {
|
||||
copyToRealm(log)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.sender = getContactName(context.applicationContext.contentResolver,phNumber) ?: ""
|
||||
temp.add(log)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@ -107,9 +96,7 @@ class RecentSmsGetter : BaseGetter {
|
||||
if (lActivity?.contentResolver != null) {
|
||||
MmsQueryHelper(lActivity?.contentResolver!!).query()
|
||||
}
|
||||
return Result.success().apply {
|
||||
dayRange = BaseGetter.defaultDay
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -9,18 +9,17 @@ import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.utils.RssList.feedJsons
|
||||
import bums.lunatic.launcher.utils.RssList.feedJsons_nsfw
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
|
||||
class RedditGetter : BaseGetter {
|
||||
class RedditGetter(context: Context) : BaseGetter(context) {
|
||||
companion object{
|
||||
val REDDIT_WORK_TAG = "RedditGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
val temp = arrayListOf<RssData>()
|
||||
override fun realWork(): List<RealmObject> {
|
||||
temp.clear()
|
||||
RssDataType.REDDIT.isOn { for (url in feedJsons) {
|
||||
for (it in RssFeedsParser.getReddit(url,false)) {
|
||||
if (it.pubDate() >= limitDateTime) {
|
||||
@ -37,8 +36,6 @@ class RedditGetter : BaseGetter {
|
||||
}
|
||||
} }
|
||||
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -8,14 +8,13 @@ import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.RuliWeb
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class RuliWebGetter : BaseGetter {
|
||||
class RuliWebGetter(context: Context) : BaseGetter(context) {
|
||||
companion object {
|
||||
val TAG = "RuliWebGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
fun parseRuli(ruli_tr : org.jsoup.nodes.Element) {
|
||||
Blog.LOGE("ruli_tr >>> ${ruli_tr.text()}")
|
||||
@ -61,7 +60,8 @@ class RuliWebGetter : BaseGetter {
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
temp.clear()
|
||||
RssDataType.RULIWEB.isOn {
|
||||
try {
|
||||
Blog.LOGE("realWork() ${this::class.simpleName}")
|
||||
@ -82,9 +82,6 @@ class RuliWebGetter : BaseGetter {
|
||||
}
|
||||
}
|
||||
} catch (e:Exception){e.printStackTrace()}}
|
||||
return Result.success().apply {
|
||||
// BLog.LOGE("Ruli temp >>>> ${temp.size}")
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.content.Context
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.utils.Blog
|
||||
import io.realm.kotlin.UpdatePolicy
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
object TaskAggregator {
|
||||
|
||||
// 1. 시스템 정보 통합 업데이트 (App, Contact, SMS 등)
|
||||
// 로컬 작업이므로 Dispatchers.Default 사용
|
||||
fun refreshSystemInfo(context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
// 1. [병렬 수집] 시스템에서 최신 데이터 가져오기 (이때 카운트는 0인 상태)
|
||||
val appsJob = async { AppInfoGetter(context).fetchData() }
|
||||
val contactsJob = async { ContactInfoGetter(context).fetchData() }
|
||||
|
||||
val scannedApps: List<AppInfo> = appsJob.await().filterIsInstance<AppInfo>()
|
||||
val scannedContacts: List<SimpleContact> = contactsJob.await().filterIsInstance<SimpleContact>()
|
||||
|
||||
|
||||
val realm = WorkersDb.getRealm()
|
||||
|
||||
// 2. [일괄 저장 및 병합] 트랜잭션 시작
|
||||
realm.write {
|
||||
|
||||
// --- [A] 앱 정보 병합 (AppInfo) ---
|
||||
// DB에 있는 기존 앱들을 패키지명(Key) 기준으로 Map 생성
|
||||
val existingAppsMap = query<AppInfo>(AppInfo::class).find().associateBy { it.pkgName }
|
||||
val activeAppPkgNames = HashSet<String>()
|
||||
|
||||
scannedApps.forEach { newApp ->
|
||||
val pkgName = newApp.pkgName ?: return@forEach
|
||||
activeAppPkgNames.add(pkgName)
|
||||
|
||||
// 기존 데이터가 있으면 카운트 정보 이식
|
||||
val oldApp = existingAppsMap[pkgName]
|
||||
if (oldApp != null) {
|
||||
newApp.clickCount = oldApp.clickCount
|
||||
newApp.lastUseDate = oldApp.lastUseDate
|
||||
// 즐겨찾기 여부 등 보존해야 할 다른 필드가 있다면 여기서 복사
|
||||
// newApp.isFavorite = oldApp.isFavorite
|
||||
}
|
||||
|
||||
// 병합된 데이터 저장 (덮어쓰기)
|
||||
copyToRealm(newApp, UpdatePolicy.ALL)
|
||||
}
|
||||
|
||||
// 삭제된 앱 처리 (시스템 스캔 목록에 없는 앱은 DB에서 삭제)
|
||||
val appsToDelete = query<AppInfo>(AppInfo::class).find().filter {
|
||||
!activeAppPkgNames.contains(it.pkgName)
|
||||
}
|
||||
appsToDelete.forEach { delete(it) }
|
||||
|
||||
|
||||
// --- [B] 연락처 정보 병합 (SimpleContact) ---
|
||||
// DB에 있는 기존 연락처들을 ID(Key) 기준으로 Map 생성
|
||||
// (SimpleContact의 PrimaryKey가 id라고 가정)
|
||||
val existingContactsMap = query<SimpleContact>(SimpleContact::class).find().associateBy { it.id }
|
||||
val activeContactIds = HashSet<String>()
|
||||
|
||||
scannedContacts.forEach { newContact ->
|
||||
// SimpleContact 타입 캐스팅 (fetchData 반환형이 List<RealmObject>이므로)
|
||||
val contact = newContact as? SimpleContact ?: return@forEach
|
||||
val contactId = contact.id ?: return@forEach
|
||||
|
||||
activeContactIds.add(contactId)
|
||||
|
||||
val oldContact = existingContactsMap[contactId]
|
||||
if (oldContact != null) {
|
||||
contact.touchCount = oldContact.touchCount
|
||||
contact.lastedTouchDateTime = oldContact.lastedTouchDateTime
|
||||
}
|
||||
|
||||
copyToRealm(contact, UpdatePolicy.ALL)
|
||||
}
|
||||
|
||||
// 삭제된 연락처 처리
|
||||
val contactsToDelete = query<SimpleContact>(SimpleContact::class).find().filter {
|
||||
!activeContactIds.contains(it.id)
|
||||
}
|
||||
contactsToDelete.forEach { delete(it) }
|
||||
|
||||
|
||||
}
|
||||
|
||||
Blog.LOGE("SystemInfo Aggregation finished in ${System.currentTimeMillis() - startTime}ms.")
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 뉴스 피드 통합 업데이트 (Ruliweb, Youtube, FMKorea 등)
|
||||
// 네트워크 작업이므로 Dispatchers.IO 사용
|
||||
fun refreshNewsFeeds(context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
// 병렬로 네트워크 요청 쏘기 (시간 획기적으로 단축됨)
|
||||
val jobs = listOf(
|
||||
async { RuliWebGetter(context).fetchData() },
|
||||
async { YoutubeGetter(context).fetchData() },
|
||||
async { DCGetter(context).fetchData() },
|
||||
async { FmKoreaGetter(context).fetchData() },
|
||||
async { NewsFeedsGetter(context).fetchData() },
|
||||
async { ClienGetter(context).fetchData() },
|
||||
async { DotaxGetter(context).fetchData() },
|
||||
async { RedditGetter(context).fetchData() },
|
||||
)
|
||||
|
||||
// 결과 수집
|
||||
val allFeeds = jobs.awaitAll().flatten()
|
||||
|
||||
// [일괄 저장]
|
||||
if (allFeeds.isNotEmpty()) {
|
||||
WorkersDb.getRealm().write {
|
||||
// 1. 새 데이터 저장
|
||||
allFeeds.forEach { feed ->
|
||||
copyToRealm(feed, UpdatePolicy.ALL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Blog.LOGE("NewsFeed Aggregation finished in ${System.currentTimeMillis() - startTime}ms. Items: ${allFeeds.size}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,141 +1,141 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.work.WorkerParameters
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
|
||||
|
||||
class TelegramBotGetter : BaseGetter {
|
||||
companion object {
|
||||
val TAG = "TelegramBotGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
|
||||
try {
|
||||
|
||||
//package bums.lunatic.launcher.workers
|
||||
//
|
||||
//import android.annotation.SuppressLint
|
||||
//import android.content.Context
|
||||
//import androidx.work.WorkerParameters
|
||||
//import com.google.android.gms.location.FusedLocationProviderClient
|
||||
//
|
||||
//
|
||||
//class TelegramBotGetter : BaseGetter {
|
||||
// companion object {
|
||||
// val TAG = "TelegramBotGetter"
|
||||
// }
|
||||
// constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
// }
|
||||
//
|
||||
//
|
||||
// @SuppressLint("RestrictedApi")
|
||||
// override fun realWork(): Result {
|
||||
//
|
||||
// try {
|
||||
// val url = "https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/getUpdates"
|
||||
// //"https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/sendMessage?chat_id=71476436&text=안녕하세요."
|
||||
// //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) {
|
||||
// val bodyString = body.string()
|
||||
// BLog.LOGE("bodyString >>>>\n${bodyString}")
|
||||
// Gson().fromJson<TelegramBotUpdate>(bodyString,TelegramBotUpdate::class.java)?.let { telegramUpdates ->
|
||||
// telegramUpdates.fill()
|
||||
// telegramUpdates.list.forEach {
|
||||
//// if (it.message?.text?.startsWith("/") == true) {
|
||||
// if((it.message?.text?.contains("where") == true) || (it.message?.text?.contains("어디") == true)) {
|
||||
// BLog.LOGE("it.message?.text?.contains(\"where\") == true) >>> ${it.message?.text?.contains("where") == true}")
|
||||
// BLog.LOGE("it.message?.text?.contains(\"어디\") == true) >>> ${it.message?.text?.contains("어디") == true}")
|
||||
// WorkersDb.getRealm().apply {
|
||||
// writeBlocking {
|
||||
// if (query<TelegramData>("update_id == $0",it.update_id).find().size == 0) {
|
||||
// copyToRealm(it)
|
||||
// getLastLocation(context = applicationContext)
|
||||
// BLog.LOGE("telegram telegramUpdates >>>> ${Gson().toJson(it)}")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//// try {
|
||||
//// val url = "https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/getUpdates"
|
||||
//// //"https://api.telegram.org/bot7934509464:AAE_xUbICxMdywLGnxo7BkeIqA1nVza4P9w/sendMessage?chat_id=71476436&text=안녕하세요."
|
||||
//// //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) {
|
||||
//// val bodyString = body.string()
|
||||
//// BLog.LOGE("bodyString >>>>\n${bodyString}")
|
||||
//// Gson().fromJson<TelegramBotUpdate>(bodyString,TelegramBotUpdate::class.java)?.let { telegramUpdates ->
|
||||
//// telegramUpdates.fill()
|
||||
//// telegramUpdates.list.forEach {
|
||||
////// if (it.message?.text?.startsWith("/") == true) {
|
||||
//// if((it.message?.text?.contains("where") == true) || (it.message?.text?.contains("어디") == true)) {
|
||||
//// BLog.LOGE("it.message?.text?.contains(\"where\") == true) >>> ${it.message?.text?.contains("where") == true}")
|
||||
//// BLog.LOGE("it.message?.text?.contains(\"어디\") == true) >>> ${it.message?.text?.contains("어디") == true}")
|
||||
//// WorkersDb.getRealm().apply {
|
||||
//// writeBlocking {
|
||||
//// if (query<TelegramData>("update_id == $0",it.update_id).find().size == 0) {
|
||||
//// copyToRealm(it)
|
||||
//// getLastLocation(context = applicationContext)
|
||||
//// BLog.LOGE("telegram telegramUpdates >>>> ${Gson().toJson(it)}")
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
////
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// } else BLog.LOGE("telegram Error Occurred")
|
||||
////
|
||||
//// } catch (e: java.lang.Exception) {
|
||||
//// e.printStackTrace()
|
||||
//// }
|
||||
// } catch (e:Exception){e.printStackTrace()}
|
||||
// return Result.success().apply {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// 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()
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
//// })
|
||||
// }
|
||||
//}
|
||||
// } else BLog.LOGE("telegram Error Occurred")
|
||||
//
|
||||
// } catch (e: java.lang.Exception) {
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
} catch (e:Exception){e.printStackTrace()}
|
||||
return Result.success().apply {
|
||||
|
||||
}
|
||||
}
|
||||
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()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
}
|
||||
}
|
||||
@ -8,14 +8,14 @@ import bums.lunatic.launcher.model.TheQoo
|
||||
import bums.lunatic.launcher.model.getHref
|
||||
import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.model.getT
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class TheQooGetter : BaseGetter {
|
||||
class TheQooGetter(context: Context) : BaseGetter(context) {
|
||||
companion object {
|
||||
val TAG = "TheQooGetter"
|
||||
}
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
}
|
||||
|
||||
|
||||
fun parseTQoo(tq_tr : org.jsoup.nodes.Element) {
|
||||
// BLog.LOGE("tq_tr >>>> ${tq_tr}")
|
||||
@ -50,7 +50,7 @@ class TheQooGetter : BaseGetter {
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
RssDataType.THEQOO.isOn {
|
||||
try {
|
||||
val testUrl2 = arrayListOf("https://theqoo.net/hot","https://theqoo.net/hot/category/512000937","https://theqoo.net/hot/category/24784","https://theqoo.net/hot/category/24788")
|
||||
@ -65,9 +65,6 @@ class TheQooGetter : BaseGetter {
|
||||
}
|
||||
}
|
||||
} catch (e:Exception){e.printStackTrace()}}
|
||||
return Result.success().apply {
|
||||
// BLog.LOGE("theqoo temp >>>> ${temp.size}")
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
package bums.lunatic.launcher.workers
|
||||
|
||||
import bums.lunatic.launcher.BuildConfig
|
||||
import bums.lunatic.launcher.apps.SimpleContact
|
||||
import bums.lunatic.launcher.common.letTrue
|
||||
import bums.lunatic.launcher.model.AppInfo
|
||||
import bums.lunatic.launcher.model.AppUsageLog
|
||||
import bums.lunatic.launcher.model.Astro
|
||||
import bums.lunatic.launcher.model.BotCommandEentitie
|
||||
import bums.lunatic.launcher.model.Condition
|
||||
@ -19,6 +19,7 @@ import bums.lunatic.launcher.model.NotificationItem
|
||||
import bums.lunatic.launcher.model.RssData
|
||||
import bums.lunatic.launcher.model.RssDataInterface
|
||||
import bums.lunatic.launcher.model.RssDataType
|
||||
import bums.lunatic.launcher.model.SimpleContact
|
||||
import bums.lunatic.launcher.model.TelegramBotUpdate
|
||||
import bums.lunatic.launcher.model.TelegramChat
|
||||
import bums.lunatic.launcher.model.TelegramData
|
||||
@ -45,6 +46,7 @@ import io.realm.kotlin.query.RealmQuery
|
||||
import io.realm.kotlin.query.Sort
|
||||
import io.realm.kotlin.types.BaseRealmObject
|
||||
import io.realm.kotlin.types.TypedRealmObject
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.reflect.KClass
|
||||
@ -57,16 +59,90 @@ class CustMigration : AutomaticSchemaMigration {
|
||||
}
|
||||
object WorkersDb {
|
||||
|
||||
|
||||
//RecentCall::class, RecentSms::class,
|
||||
val clazz : Set<KClass<out BaseRealmObject>> = setOf(
|
||||
RssData::class, NotificationItem::class, AppInfo::class,SimpleContact::class, RecentCall::class, RecentSms::class, CurrentPlayItem::class,
|
||||
RssData::class, NotificationItem::class, AppInfo::class,SimpleContact::class, CurrentPlayItem::class,
|
||||
TelegramBotUpdate::class, TelegramData::class, TelegramMessage::class, TelegramChat::class, BotCommandEentitie::class, TelegramFrom::class,
|
||||
WeatherForcast::class, Location::class, Current::class, Forecast::class, Condition::class, Forecastday::class, Day::class, Astro::class, Hour::class,
|
||||
LocationLog::class,
|
||||
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,WidgetData::class
|
||||
LastInfo::class, HistoryItem::class, ReaderConfig::class, ContentsCollection::class, ContentsPageInfo::class,
|
||||
WidgetData::class,AppUsageLog::class
|
||||
)
|
||||
//,UserActionModel::class
|
||||
|
||||
// [추가] 앱/연락처 사용 시 로그 저장 (기존 updateAppUse 대신 이거 호출)
|
||||
fun logAppUsage(key: String, type: String = "APP") {
|
||||
val realm = getRealm()
|
||||
val calendar = Calendar.getInstance()
|
||||
|
||||
realm.writeBlocking { // 비동기로 하려면 write { } 사용
|
||||
// 1. 기존 카운트 증가 (기존 로직 유지)
|
||||
// ... (AppInfo 조회 후 clickCount++ 하는 코드) ...
|
||||
|
||||
// 2. 상세 로그 저장 (추가된 부분)
|
||||
copyToRealm(AppUsageLog().apply {
|
||||
itemKey = key
|
||||
itemType = type
|
||||
timestamp = System.currentTimeMillis()
|
||||
month = calendar.get(Calendar.MONTH)
|
||||
dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
|
||||
hour = calendar.get(Calendar.HOUR_OF_DAY)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// [핵심] 현재 시간 상황에 맞는 추천 리스트 가져오기
|
||||
fun getContextualRecommendations(limit: Int = 5): List<String> {
|
||||
val realm = getRealm()
|
||||
val calendar = Calendar.getInstance()
|
||||
|
||||
val curMonth = calendar.get(Calendar.MONTH)
|
||||
val curDay = calendar.get(Calendar.DAY_OF_MONTH)
|
||||
val curDow = calendar.get(Calendar.DAY_OF_WEEK)
|
||||
val curHour = calendar.get(Calendar.HOUR_OF_DAY)
|
||||
|
||||
// 최근 3개월 데이터만 조회 (너무 오래된 데이터는 노이즈가 됨)
|
||||
val threeMonthsAgo = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000)
|
||||
|
||||
// 쿼리: 최근 데이터만 가져와서 메모리에서 계산 (복잡한 가중치는 메모리 연산이 빠름)
|
||||
val logs = realm.query<AppUsageLog>("timestamp > $0", threeMonthsAgo).find()
|
||||
|
||||
// 점수 계산
|
||||
val scores = HashMap<String, Double>()
|
||||
|
||||
for (log in logs) {
|
||||
var score = 1.0 // 기본 점수 (최근에 썼다는 것 자체로 의미 있음)
|
||||
|
||||
// 1. 시간대 매칭 (가장 중요: 아침에 쓰던 앱은 아침에 쓴다)
|
||||
if (log.hour == curHour) score += 3.0
|
||||
else if (log.hour == curHour - 1 || log.hour == curHour + 1) score += 1.0
|
||||
|
||||
// 2. 요일 매칭 (주말/평일 패턴)
|
||||
if (log.dayOfWeek == curDow) score += 2.0
|
||||
|
||||
// 3. "매월 1일" 같은 날짜 패턴 (가중치 매우 높음)
|
||||
if (log.dayOfMonth == curDay) score += 5.0
|
||||
|
||||
// 4. (옵션) 매년 같은 달 같은 날? (생일 등)
|
||||
if (log.month == curMonth && log.dayOfMonth == curDay) score += 10.0
|
||||
|
||||
// 5. 최신성 가중치 (최근 기록일수록 점수 높게)
|
||||
val daysAgo = (System.currentTimeMillis() - log.timestamp) / (24 * 60 * 60 * 1000)
|
||||
val decay = 1.0 / (daysAgo + 1) // 오늘이면 1, 9일전이면 0.1
|
||||
|
||||
// 최종 점수 누적
|
||||
val finalScore = score * decay
|
||||
scores[log.itemKey] = (scores[log.itemKey] ?: 0.0) + finalScore
|
||||
}
|
||||
|
||||
// 점수 높은 순으로 정렬하여 상위 N개 반환
|
||||
return scores.entries
|
||||
.sortedByDescending { it.value }
|
||||
.take(limit)
|
||||
.map { it.key }
|
||||
}
|
||||
|
||||
val schemaVersion : Long = BuildConfig.BuildDateTime
|
||||
|
||||
private var pRealm : Realm? = null
|
||||
|
||||
@ -10,21 +10,20 @@ import bums.lunatic.launcher.model.getRssData
|
||||
import bums.lunatic.launcher.model.others.Youtube
|
||||
import bums.lunatic.launcher.utils.RssList
|
||||
import com.google.gson.Gson
|
||||
import io.realm.kotlin.types.RealmObject
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
|
||||
class YoutubeGetter : BaseGetter {
|
||||
class YoutubeGetter(context: Context) : BaseGetter(context) {
|
||||
companion object {
|
||||
val YT_WORK_TAG = "YoutubeGetter"
|
||||
}
|
||||
var rssUrls = arrayListOf<String>()
|
||||
constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun realWork(): Result {
|
||||
override fun realWork(): List<RealmObject> {
|
||||
rssUrls.clear()
|
||||
rssUrls.addAll(RssList.youtubeUrls)
|
||||
RssDataType.YOUTUBE.isOn {
|
||||
@ -32,9 +31,7 @@ class YoutubeGetter : BaseGetter {
|
||||
temp.addAll(ytChannel(Jsoup.connect(url).userAgent(USAGT).get()))
|
||||
}
|
||||
}
|
||||
return Result.success().apply {
|
||||
WorkersDb.insertBulkData(temp)
|
||||
}
|
||||
return temp
|
||||
}
|
||||
|
||||
fun ytChannel(doc: Document) : ArrayList<RssData> {
|
||||
|
||||
@ -22,6 +22,7 @@
|
||||
android:id="@+id/childTextview"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintLeft_toRightOf="@id/appIconTwo"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:lines="2"
|
||||
|
||||
34
app/src/main/res/layout/apps_child_rec.xml
Normal file
34
app/src/main/res/layout/apps_child_rec.xml
Normal file
@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="45dp"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@drawable/apps_bg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/appIconTwo"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="@dimen/forty"
|
||||
android:layout_height="@dimen/forty"
|
||||
android:layout_gravity="center_horizontal"
|
||||
/>
|
||||
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/childTextview"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintLeft_toRightOf="@id/appIconTwo"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
android:lines="2"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_marginBottom="@dimen/four"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
180
app/src/main/res/layout/bottom_sheet_app_drawer.xml
Normal file
180
app/src/main/res/layout/bottom_sheet_app_drawer.xml
Normal file
@ -0,0 +1,180 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/rounded_bg_top"
|
||||
android:paddingTop="10dp">
|
||||
|
||||
<View
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="4dp"
|
||||
android:background="#E0E0E0"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/searchInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_margin="@dimen/default_layout_margin"
|
||||
android:layout_marginTop="20dp"
|
||||
android:background="@drawable/base_bg"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:hint="앱 검색"
|
||||
android:imeOptions="actionSearch"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintTop_toBottomOf="@id/drag_handle"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"/>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/reset"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/rounded_bg"
|
||||
android:padding="@dimen/eight"
|
||||
android:tint="@color/white"
|
||||
android:layout_marginEnd="5dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/searchInput"
|
||||
app:layout_constraintRight_toRightOf="@id/searchInput"
|
||||
app:layout_constraintTop_toTopOf="@id/searchInput"
|
||||
app:srcCompat="@drawable/ic_refresh" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/nestedScrollView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="10dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintTop_toBottomOf="@id/searchInput"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appsCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:text="0 Apps"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"/>
|
||||
<TextView
|
||||
android:id="@+id/title_recommend"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:text="앱 목록"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recAppsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="100dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:overScrollMode="never"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_recommend"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_apps"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:text="앱 목록"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintTop_toBottomOf="@+id/recAppsList"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/appsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="100dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:overScrollMode="never"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_apps"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_webs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:text="빠른 검색"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintTop_toBottomOf="@id/appsList"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/quickSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="10dp"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_webs">
|
||||
|
||||
<bums.lunatic.launcher.view.CircleImageView
|
||||
android:id="@+id/search_nmap"
|
||||
style="@style/SearchIcons"
|
||||
android:src="@drawable/navermap"/>
|
||||
<bums.lunatic.launcher.view.CircleImageView
|
||||
android:id="@+id/search_google"
|
||||
style="@style/SearchIcons"
|
||||
android:src="@drawable/google"/>
|
||||
<bums.lunatic.launcher.view.CircleImageView
|
||||
android:id="@+id/search_naver"
|
||||
style="@style/SearchIcons"
|
||||
android:src="@drawable/naver"/>
|
||||
<bums.lunatic.launcher.view.CircleImageView
|
||||
android:id="@+id/search_youtube"
|
||||
style="@style/SearchIcons"
|
||||
android:src="@drawable/youtube"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_contact"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp"
|
||||
android:textColor="@color/white"
|
||||
android:text="연락처"
|
||||
app:layout_constraintTop_toBottomOf="@id/quickSearch"
|
||||
app:layout_constraintLeft_toLeftOf="parent"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/contactList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="90dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:layout_constraintTop_toBottomOf="@id/title_contact"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -10,6 +10,7 @@
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/name"
|
||||
android:visibility="visible"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
@ -21,6 +22,7 @@
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/number"
|
||||
android:textColor="@color/white"
|
||||
app:layout_constraintTop_toBottomOf="@id/name"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/delete_zone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="삭제하려면 여기에 놓으세요"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:text="삭제"
|
||||
android:textColor="#FFFFFF"
|
||||
android:background="#99FF0000"
|
||||
android:padding="20dp"
|
||||
@ -32,7 +32,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="50dp"/>
|
||||
android:layout_marginTop="10dp"/>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
@ -49,6 +49,7 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/fragment_container"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
android:id="@+id/back"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/back_vector"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom" />
|
||||
@ -57,6 +58,7 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/fragment_container"
|
||||
app:layout_constraintLeft_toRightOf="@id/back"
|
||||
android:id="@+id/reload"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/ic_refresh"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom"/>
|
||||
@ -68,6 +70,7 @@
|
||||
app:layout_constraintLeft_toRightOf="@id/reload"
|
||||
android:textColor="@color/white"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:textSize="@dimen/_12sp"
|
||||
android:ellipsize="middle"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
@ -79,6 +82,7 @@
|
||||
app:layout_constraintRight_toLeftOf="@id/share"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:id="@+id/dl_video"
|
||||
android:visibility="gone"
|
||||
android:src="@drawable/dl_vid"
|
||||
tools:ignore="ContentDescription"
|
||||
style="@style/CommonBottom"/>
|
||||
@ -88,6 +92,7 @@
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_marginRight="60dp"
|
||||
android:id="@+id/share"
|
||||
android:visibility="gone"
|
||||
android:foregroundTint="@color/white"
|
||||
android:src="@drawable/ic_share"
|
||||
tools:ignore="ContentDescription"
|
||||
@ -169,5 +174,15 @@
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
|
||||
<bums.lunatic.launcher.view.FloatingActionButton
|
||||
app:fab_label="close"
|
||||
android:id="@+id/close"
|
||||
app:fab_showShadow="true"
|
||||
app:fab_size="mini"
|
||||
android:onClick="floatClick"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"/>
|
||||
|
||||
</bums.lunatic.launcher.view.FloatingActionMenu>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@ -21,4 +21,13 @@
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">10dp</item>
|
||||
</style>
|
||||
|
||||
<style name="CustomBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
|
||||
<item name="bottomSheetStyle">@style/CustomBottomSheetStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="CustomBottomSheetStyle" parent="Widget.Design.BottomSheet.Modal">
|
||||
<item name="android:background">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
Loading…
x
Reference in New Issue
Block a user