From 62f1e646ab65dcff28afb96c9ebd54358e1eb3a9 Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Thu, 8 Jan 2026 18:20:30 +0900 Subject: [PATCH] ,,,.... --- app/src/main/AndroidManifest.xml | 6 + .../bums/lunatic/launcher/LauncherActivity.kt | 139 ++++++- .../bums/lunatic/launcher/apps/AppDrawer.kt | 3 +- .../launcher/apps/AppDrawerBottomSheet.kt | 338 ++++++++++++++++++ .../bums/lunatic/launcher/apps/AppsAdapter.kt | 41 ++- .../lunatic/launcher/apps/ContactAdapter.kt | 21 +- .../bums/lunatic/launcher/apps/ContactMenu.kt | 1 + .../lunatic/launcher/apps/IconPackManager.kt | 260 ++------------ .../launcher/apps/RecommendedAppsAdapter.kt | 68 ++++ .../launcher/helpers/ForeGroundService.kt | 127 ++----- .../bums/lunatic/launcher/model/AppInfo.kt | 5 + .../lunatic/launcher/model/AppUsageLog.kt | 19 + .../lunatic/launcher/model/SimpleContact.kt | 29 ++ .../lunatic/launcher/qaccess/QuickAccess.kt | 2 +- .../launcher/receiver/PackageEventReceiver.kt | 8 +- .../launcher/settings/SettingsActivity.kt | 2 +- .../lunatic/launcher/workers/AppInfoGetter.kt | 73 ++-- .../lunatic/launcher/workers/ArcaGetter.kt | 208 +++++------ .../lunatic/launcher/workers/BaseGetter.kt | 22 +- .../launcher/workers/CalendarGetter.kt | 298 +++++++-------- .../lunatic/launcher/workers/ClienGetter.kt | 37 +- .../launcher/workers/ContactInfoGetter.kt | 48 +-- .../bums/lunatic/launcher/workers/DCGetter.kt | 16 +- .../lunatic/launcher/workers/DotaxGetter.kt | 48 +-- .../lunatic/launcher/workers/FmKoreaGetter.kt | 13 +- .../launcher/workers/LocationGetter.kt | 109 +++--- .../launcher/workers/LocationUpdateService.kt | 5 +- .../launcher/workers/NewsFeedsGetter.kt | 18 +- .../launcher/workers/OpenWeatherGetter.kt | 179 +++++----- .../launcher/workers/RecentCallGetter.kt | 42 +-- .../launcher/workers/RecentSmsGetter.kt | 25 +- .../lunatic/launcher/workers/RedditGetter.kt | 13 +- .../lunatic/launcher/workers/RuliWebGetter.kt | 47 ++- .../launcher/workers/TaskAggregator.kt | 130 +++++++ .../launcher/workers/TelegramBotGetter.kt | 266 +++++++------- .../lunatic/launcher/workers/TheQooGetter.kt | 13 +- .../lunatic/launcher/workers/WorkersDb.kt | 84 ++++- .../lunatic/launcher/workers/YoutubeGetter.kt | 11 +- app/src/main/res/layout/apps_child.xml | 1 + app/src/main/res/layout/apps_child_rec.xml | 34 ++ .../res/layout/bottom_sheet_app_drawer.xml | 180 ++++++++++ app/src/main/res/layout/contact_item.xml | 2 + app/src/main/res/layout/launcher_activity.xml | 23 +- app/src/main/res/values/themes.xml | 9 + 44 files changed, 1848 insertions(+), 1175 deletions(-) create mode 100644 app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawerBottomSheet.kt create mode 100644 app/src/main/kotlin/bums/lunatic/launcher/apps/RecommendedAppsAdapter.kt create mode 100644 app/src/main/kotlin/bums/lunatic/launcher/model/AppUsageLog.kt create mode 100644 app/src/main/kotlin/bums/lunatic/launcher/model/SimpleContact.kt create mode 100644 app/src/main/kotlin/bums/lunatic/launcher/workers/TaskAggregator.kt create mode 100644 app/src/main/res/layout/apps_child_rec.xml create mode 100644 app/src/main/res/layout/bottom_sheet_app_drawer.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bfe70823..b44adb58 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -101,6 +101,11 @@ android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|screenLayout|layoutDirection|navigation" android:windowSoftInputMode="adjustResize" android:exported="true"> + + + + + @@ -117,6 +122,7 @@ + diff --git a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt index 83efebd5..30c1a8b7 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt @@ -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) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawer.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawer.kt index 388afcd5..4c52e22c 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawer.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawer.kt @@ -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 @@ -87,7 +88,7 @@ class AppDrawer : CommonActivity() { // super.onAttach(context) //// } // -// +// // // override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawerBottomSheet.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawerBottomSheet.kt new file mode 100644 index 00000000..3b1410b1 --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/AppDrawerBottomSheet.kt @@ -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() + private val contactList = arrayListOf() // 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(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(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() + + 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() + + 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 + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/AppsAdapter.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/AppsAdapter.kt index 2a4a8dff..bbc8c0f3 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/apps/AppsAdapter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/AppsAdapter.kt @@ -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) { 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 diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactAdapter.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactAdapter.kt index fe549031..8b34ca28 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactAdapter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactAdapter.kt @@ -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, private val newList: List diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactMenu.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactMenu.kt index 39fe6187..63c8b049 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactMenu.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/ContactMenu.kt @@ -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 diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/IconPackManager.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/IconPackManager.kt index ee4566d9..8c5d578e 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/apps/IconPackManager.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/IconPackManager.kt @@ -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 . - */ - 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() - private val packagesConponentNames = HashMap() - private val backImages: MutableList = 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 = 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(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(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 + fun getDrawableIconForPackage(context: Context, packageName: String): Drawable? { + // 1. 캐시에 있는지 확인 + val cachedIcon = iconCache.get(packageName) + if (cachedIcon != null) { + return cachedIcon } - 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 - } - } - } + + // 2. 없으면 로딩 (기존 로직 수행) + // ... (기존의 아이콘팩 로딩 또는 pm.getApplicationIcon 코드) ... + val loadedIcon = try { + context.packageManager.getApplicationIcon(packageName) + // 만약 아이콘팩 적용 로직이 있다면 여기서 변환 수행 + } catch (e: Exception) { + context.resources.getDrawable(android.R.drawable.sym_def_app_icon, null) } - return null + + // 3. 로딩된 아이콘 캐시에 저장 + if (loadedIcon != null) { + iconCache.put(packageName, loadedIcon) + } + + return loadedIcon } - private fun loadDrawable(drawableName: String): Drawable? { - iconPackRes!!.getIdentifier(drawableName, "drawable", packageName).let { - return if (it > 0) ResourcesCompat.getDrawable(iconPackRes!!, it, null) - else null - } + // 아이콘팩 변경 시 호출해줄 함수 + fun clearCache() { + iconCache.evictAll() } - - 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) - } - } - } - } - } - - } - } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/apps/RecommendedAppsAdapter.kt b/app/src/main/kotlin/bums/lunatic/launcher/apps/RecommendedAppsAdapter.kt new file mode 100644 index 00000000..9e3fe1e3 --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/apps/RecommendedAppsAdapter.kt @@ -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() { + + private val items = ArrayList() + + fun submitList(newItems: List) { + 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) +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt b/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt index 43f38b9f..2d1b529d 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/helpers/ForeGroundService.kt @@ -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(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(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(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(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(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(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(COMIC2_WORK_TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(ClienGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - ClienGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(ClienGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(DCGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - DCGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(DCGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(TheQooGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - TheQooGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(TheQooGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(ArcaGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - ArcaGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.midTimePeriod.get(), TimeUnit.MINUTES) - .addTag(ArcaGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(LocationGetter.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - LocationGetter.TAG, ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, - PeriodicWorkRequestBuilder(PrefLong.locationTimePeriod.get(), TimeUnit.MINUTES) - .addTag(LocationGetter.TAG) - .build()) - mWorkManager?.cancelAllWorkByTag(ServiceWatchdogWorker.TAG) - mWorkManager?.enqueueUniquePeriodicWork( - ServiceWatchdogWorker.TAG, - ExistingPeriodicWorkPolicy.REPLACE, - PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES) - .addTag(ServiceWatchdogWorker.TAG) - .build()) + TaskAggregator.refreshSystemInfo(applicationContext) + + // 1. 시스템 정보: 자주 변경되지 않으므로 1~3시간 간격 + val systemRequest = PeriodicWorkRequestBuilder(PrefLong.longTimePeriod.get(120L), TimeUnit.MINUTES) + .build() + + // 2. 뉴스 피드: 사용자가 설정한 간격 (예: 1시간) + val newsRequest = PeriodicWorkRequestBuilder(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!!) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/model/AppInfo.kt b/app/src/main/kotlin/bums/lunatic/launcher/model/AppInfo.kt index 8edb077c..50f9abc7 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/model/AppInfo.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/model/AppInfo.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/model/AppUsageLog.kt b/app/src/main/kotlin/bums/lunatic/launcher/model/AppUsageLog.kt new file mode 100644 index 00000000..385ad52d --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/model/AppUsageLog.kt @@ -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시간제) +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/model/SimpleContact.kt b/app/src/main/kotlin/bums/lunatic/launcher/model/SimpleContact.kt new file mode 100644 index 00000000..710d74ca --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/model/SimpleContact.kt @@ -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() + +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/qaccess/QuickAccess.kt b/app/src/main/kotlin/bums/lunatic/launcher/qaccess/QuickAccess.kt index 319e14ae..fe6346f9 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/qaccess/QuickAccess.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/qaccess/QuickAccess.kt @@ -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 diff --git a/app/src/main/kotlin/bums/lunatic/launcher/receiver/PackageEventReceiver.kt b/app/src/main/kotlin/bums/lunatic/launcher/receiver/PackageEventReceiver.kt index 96c72f94..ca5e9278 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/receiver/PackageEventReceiver.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/receiver/PackageEventReceiver.kt @@ -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) } } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/settings/SettingsActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/settings/SettingsActivity.kt index 33ef4d3c..414c11a9 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/settings/SettingsActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/settings/SettingsActivity.kt @@ -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 diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/AppInfoGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/AppInfoGetter.kt index b607ef92..b56345dd 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/AppInfoGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/AppInfoGetter.kt @@ -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 { + var result = mutableListOf() try { + val packageManager = lActivity?.packageManager ?: return result - var packageManager = lActivity?.packageManager - var packageInfoList: MutableList = 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().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() @@ -90,4 +93,4 @@ class AppInfoGetter : BaseGetter { val pattern = Pattern.compile("\\p{InCombiningDiacriticalMarks}+") return pattern.matcher(normalizedString).replaceAll("").toLowerCase() } -} +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt index 115c5958..de318dfd 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt @@ -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 { -//// 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() -//// } +//package bums.lunatic.launcher.workers // -// } -//// Jsoup.connect("https://projrctjav.com").userAgent(USAGT).get().let { projectj -> -//// BLog.LOGE("projectj >>>>> ${projectj}") +////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 { +////// 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() //// } -// } 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 { - var tempArray = arrayListOf() -// 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 - } - - -} \ No newline at end of file +// private fun parseArcaLi(aracaLi: Element) : ArrayList { +// var tempArray = arrayListOf() +//// 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 +// } +// +// +//} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/BaseGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/BaseGetter.kt index 4a0bbe13..58c1d893 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/BaseGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/BaseGetter.kt @@ -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() - constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) { + abstract fun realWork() : List + open suspend fun fetchData(): List { + return realWork() } - - @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 { - - } - } - return realWork().apply { - LunaticLauncher.mHourlyLogWriter?.writeLog("${this@BaseGetter::class.java.simpleName} return realWork() ") - } - } - abstract fun realWork() : Result } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/CalendarGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/CalendarGetter.kt index ac2f29d7..52c6c643 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/CalendarGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/CalendarGetter.kt @@ -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(managedCursor.count) - val title = arrayOfNulls(managedCursor.count) -// val eventLocation = arrayOfNulls(managedCursor.count) - val description = arrayOfNulls(managedCursor.count) -// val eventStatus = IntArray(managedCursor.count) -// val selfAttendeeStatus = IntArray(managedCursor.count) -// val commentsUri = arrayOfNulls(managedCursor.count) - val dtstart = arrayOfNulls(managedCursor.count) - val dtend = arrayOfNulls(managedCursor.count) -// val eventTimezone = arrayOfNulls(managedCursor.count) -// val duration = arrayOfNulls(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(managedCursor.count) - val rdate = arrayOfNulls(managedCursor.count) -// val exrule = arrayOfNulls(managedCursor.count) -// val exdate = arrayOfNulls(managedCursor.count) -// val originalEvent = arrayOfNulls(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(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() - } - } - -} \ No newline at end of file +//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 { +// 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(managedCursor.count) +// val title = arrayOfNulls(managedCursor.count) +//// val eventLocation = arrayOfNulls(managedCursor.count) +// val description = arrayOfNulls(managedCursor.count) +//// val eventStatus = IntArray(managedCursor.count) +//// val selfAttendeeStatus = IntArray(managedCursor.count) +//// val commentsUri = arrayOfNulls(managedCursor.count) +// val dtstart = arrayOfNulls(managedCursor.count) +// val dtend = arrayOfNulls(managedCursor.count) +//// val eventTimezone = arrayOfNulls(managedCursor.count) +//// val duration = arrayOfNulls(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(managedCursor.count) +// val rdate = arrayOfNulls(managedCursor.count) +//// val exrule = arrayOfNulls(managedCursor.count) +//// val exdate = arrayOfNulls(managedCursor.count) +//// val originalEvent = arrayOfNulls(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(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() +// } +// } +// +//} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt index fee02a0d..9640bb83 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt @@ -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 { 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 } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/ContactInfoGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/ContactInfoGetter.kt index d92785bc..40e7b6be 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/ContactInfoGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/ContactInfoGetter.kt @@ -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 { + var temp = mutableListOf() 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("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) { + + + } catch (e: Exception) { e.printStackTrace() } - return Result.success() + Blog.LOGE("ContactInfoGetter >>> ${temp.size}") + return temp } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt index b9ca070d..4d9eab9b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/DCGetter.kt @@ -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{ var temp = arrayListOf() @@ -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 { // 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 } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt index 52267624..2a5ca005 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt @@ -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 { 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 -> + try { + Blog.LOGE("realWork() ${this::class.simpleName}") + temp.clear() 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) - } + + } catch (e : Exception) {e.printStackTrace()}} + return temp } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/FmKoreaGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/FmKoreaGetter.kt index cf50fa41..c19a45b5 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/FmKoreaGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/FmKoreaGetter.kt @@ -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 { + 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 } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationGetter.kt index 7302d0d1..60a3c3e1 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationGetter.kt @@ -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 { +//// 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 \ No newline at end of file +// +// +////https://jinkpark.tistory.com/296 +////https://develoyummer.tistory.com/103 +////https://ghj1001020.tistory.com/300 \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationUpdateService.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationUpdateService.kt index ed4862f5..9f21ff2c 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationUpdateService.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/LocationUpdateService.kt @@ -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) } } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/NewsFeedsGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/NewsFeedsGetter.kt index 1458dc97..fddee994 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/NewsFeedsGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/NewsFeedsGetter.kt @@ -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() - constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) { - } + @SuppressLint("RestrictedApi") - override fun realWork(): Result { + override fun realWork(): List { 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 } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/OpenWeatherGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/OpenWeatherGetter.kt index 6dc3ab64..f766e79b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/OpenWeatherGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/OpenWeatherGetter.kt @@ -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() - .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().first().find()?.forecast?.forecastdayRealm?.size}") -// BLog.LOGE("saved weatherForcast hour.count >>> ${WorkersDb.getRealm().query().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? -} - +//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 { +// 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() +// .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().first().find()?.forecast?.forecastdayRealm?.size}") +//// BLog.LOGE("saved weatherForcast hour.count >>> ${WorkersDb.getRealm().query().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? +//} +// diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentCallGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentCallGetter.kt index 79b981df..9f8bdb79 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentCallGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentCallGetter.kt @@ -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 { + var temp = mutableListOf() 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(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().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().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 } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentSmsGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentSmsGetter.kt index db8ee436..4d9546ad 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentSmsGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/RecentSmsGetter.kt @@ -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 { + var temp = mutableListOf() 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("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 } } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/RedditGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/RedditGetter.kt index 8343c2c8..d3f09b47 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/RedditGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/RedditGetter.kt @@ -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() + override fun realWork(): List { + 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 } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/RuliWebGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/RuliWebGetter.kt index c12612a8..db6e89b0 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/RuliWebGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/RuliWebGetter.kt @@ -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,30 +60,28 @@ class RuliWebGetter : BaseGetter { } @SuppressLint("RestrictedApi") - override fun realWork(): Result { + override fun realWork(): List { + temp.clear() RssDataType.RULIWEB.isOn { - try { - Blog.LOGE("realWork() ${this::class.simpleName}") - val testUrl2 = arrayListOf("https://bbs.ruliweb.com/best/humor_only","https://bbs.ruliweb.com/best/humor_only/now?m=humor_only&t=default&page=2") - testUrl2.forEach { url -> - Jsoup.connect(url).timeout(5000).ignoreHttpErrors(true) - .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) - .get().let { ruli -> + try { + Blog.LOGE("realWork() ${this::class.simpleName}") + val testUrl2 = arrayListOf("https://bbs.ruliweb.com/best/humor_only","https://bbs.ruliweb.com/best/humor_only/now?m=humor_only&t=default&page=2") + testUrl2.forEach { url -> + Jsoup.connect(url).timeout(5000).ignoreHttpErrors(true) + .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) + .get().let { ruli -> // Blog.LOGE(TAG.plus("test ${testUrl2} >> ${ruli.title()}")) - ruli.getElementsByClass("table_body blocktarget").forEach { ruli_tr -> - parseRuli(ruli_tr) + ruli.getElementsByClass("table_body blocktarget").forEach { ruli_tr -> + parseRuli(ruli_tr) + } } - } - } - } catch (e:Exception){e.printStackTrace()}} - return Result.success().apply { -// BLog.LOGE("Ruli temp >>>> ${temp.size}") - WorkersDb.insertBulkData(temp) - } + } + } catch (e:Exception){e.printStackTrace()}} + return temp } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/TaskAggregator.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/TaskAggregator.kt new file mode 100644 index 00000000..c86a2c12 --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/TaskAggregator.kt @@ -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 = appsJob.await().filterIsInstance() + val scannedContacts: List = contactsJob.await().filterIsInstance() + + + val realm = WorkersDb.getRealm() + + // 2. [일괄 저장 및 병합] 트랜잭션 시작 + realm.write { + + // --- [A] 앱 정보 병합 (AppInfo) --- + // DB에 있는 기존 앱들을 패키지명(Key) 기준으로 Map 생성 + val existingAppsMap = query(AppInfo::class).find().associateBy { it.pkgName } + val activeAppPkgNames = HashSet() + + 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::class).find().filter { + !activeAppPkgNames.contains(it.pkgName) + } + appsToDelete.forEach { delete(it) } + + + // --- [B] 연락처 정보 병합 (SimpleContact) --- + // DB에 있는 기존 연락처들을 ID(Key) 기준으로 Map 생성 + // (SimpleContact의 PrimaryKey가 id라고 가정) + val existingContactsMap = query(SimpleContact::class).find().associateBy { it.id } + val activeContactIds = HashSet() + + scannedContacts.forEach { newContact -> + // SimpleContact 타입 캐스팅 (fetchData 반환형이 List이므로) + 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::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}") + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/TelegramBotGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/TelegramBotGetter.kt index 94e8fca7..abed570b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/TelegramBotGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/TelegramBotGetter.kt @@ -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 { - -// 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() +//package bums.lunatic.launcher.workers // -// // GET 요청 객체 생성 -// val builder: Request.Builder = Request.Builder().url(url).addHeader("Content-Type", "application/json").get() +//import android.annotation.SuppressLint +//import android.content.Context +//import androidx.work.WorkerParameters +//import com.google.android.gms.location.FusedLocationProviderClient // -// 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(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("update_id == $0",it.update_id).find().size == 0) { -// copyToRealm(it) -// getLastLocation(context = applicationContext) -// BLog.LOGE("telegram telegramUpdates >>>> ${Gson().toJson(it)}") -// } -// } -// } +//class TelegramBotGetter : BaseGetter { +// companion object { +// val TAG = "TelegramBotGetter" +// } +// constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) { +// } // -// } -// } -// } -// } -// } 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 { -// 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()) +// @SuppressLint("RestrictedApi") +// override fun realWork(): Result { // -// // Use Geocoder to get detailed location information -// try { -// val geocoder = Geocoder(context, Locale.getDefault()) -// val addresses: List
? = geocoder.getFromLocation( -// location.getLatitude(), -// location.getLongitude(), -// 1 -// ) +// try { // -// 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() +//// 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(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("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 { // -// // 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() -// } -// } -// } -// }) - } -} \ No newline at end of file +// } +// } +// var fusedLocationProviderClient: FusedLocationProviderClient? = null +// @SuppressLint("MissingPermission") +// private fun getLastLocation(context: Context) { +//// fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context); +//// BLog.LOGE("Location getLastLocation") +//// fusedLocationProviderClient?.getLastLocation()?.addOnSuccessListener(object : OnSuccessListener { +//// 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
? = 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() +//// } +//// } +//// } +//// }) +// } +//} \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt index ee26c0c6..57603ab6 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt @@ -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 { 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 } } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt index 99e7337c..52d60283 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt @@ -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> = 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 { + 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("timestamp > $0", threeMonthsAgo).find() + + // 점수 계산 + val scores = HashMap() + + 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 diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/YoutubeGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/YoutubeGetter.kt index 5fcce0f1..a3c99429 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/YoutubeGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/YoutubeGetter.kt @@ -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() - constructor(context: Context, workerParams: WorkerParameters) : super(context, workerParams) { - } @SuppressLint("RestrictedApi") - override fun realWork(): Result { + override fun realWork(): List { 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 { diff --git a/app/src/main/res/layout/apps_child.xml b/app/src/main/res/layout/apps_child.xml index 173f7f0b..91b4aefe 100644 --- a/app/src/main/res/layout/apps_child.xml +++ b/app/src/main/res/layout/apps_child.xml @@ -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" diff --git a/app/src/main/res/layout/apps_child_rec.xml b/app/src/main/res/layout/apps_child_rec.xml new file mode 100644 index 00000000..91b4aefe --- /dev/null +++ b/app/src/main/res/layout/apps_child_rec.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_app_drawer.xml b/app/src/main/res/layout/bottom_sheet_app_drawer.xml new file mode 100644 index 00000000..f507f530 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_app_drawer.xml @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_item.xml b/app/src/main/res/layout/contact_item.xml index ed649386..72961a5a 100644 --- a/app/src/main/res/layout/contact_item.xml +++ b/app/src/main/res/layout/contact_item.xml @@ -10,6 +10,7 @@ + android:layout_marginTop="10dp"/> @@ -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"/> + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 3748d81c..936030ce 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -21,4 +21,13 @@ rounded 10dp + + + + + \ No newline at end of file