...
This commit is contained in:
parent
614e244be7
commit
83546e5e10
@ -3,6 +3,7 @@ package bums.lunatic.launcher.apps
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.app.SearchManager
|
import android.app.SearchManager
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -28,6 +29,7 @@ import bums.lunatic.launcher.model.AppInfo
|
|||||||
import bums.lunatic.launcher.model.SimpleContact
|
import bums.lunatic.launcher.model.SimpleContact
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
import bums.lunatic.launcher.utils.CategoryGrouper
|
import bums.lunatic.launcher.utils.CategoryGrouper
|
||||||
|
import bums.lunatic.launcher.utils.CategoryManualMapper
|
||||||
import bums.lunatic.launcher.utils.EnToKo
|
import bums.lunatic.launcher.utils.EnToKo
|
||||||
import bums.lunatic.launcher.utils.JamoUtils
|
import bums.lunatic.launcher.utils.JamoUtils
|
||||||
import bums.lunatic.launcher.utils.SimpleTransliterater
|
import bums.lunatic.launcher.utils.SimpleTransliterater
|
||||||
@ -97,18 +99,7 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
|||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
|
||||||
private val categoryMap = mapOf(
|
|
||||||
"전체" to "ALL",
|
|
||||||
"게임" to "GAME",
|
|
||||||
"생산성" to "PRODUCTIVITY",
|
|
||||||
"소셜" to "SOCIAL",
|
|
||||||
"오디오" to "AUDIO",
|
|
||||||
"비디오" to "VIDEO",
|
|
||||||
"이미지" to "IMAGE",
|
|
||||||
"지도" to "MAPS",
|
|
||||||
"뉴스" to "NEWS",
|
|
||||||
"기타" to "UNDEFINED" // 혹은 UNKNOWN
|
|
||||||
)
|
|
||||||
|
|
||||||
private var currentScope: String = CategoryGrouper.SCOPE_ALL
|
private var currentScope: String = CategoryGrouper.SCOPE_ALL
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@ -127,7 +118,7 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
|||||||
private var currentCategoryKey: String = "ALL"
|
private var currentCategoryKey: String = "ALL"
|
||||||
private fun setupCategorySpinner() {
|
private fun setupCategorySpinner() {
|
||||||
// 1. 스피너에 들어갈 데이터(표시 이름들) 준비
|
// 1. 스피너에 들어갈 데이터(표시 이름들) 준비
|
||||||
val displayList = categoryMap.keys.toList()
|
val displayList = CategoryManualMapper.CATEGORY_MAP.keys.toList()
|
||||||
|
|
||||||
// 2. 어댑터 설정 (기본 안드로이드 레이아웃 사용)
|
// 2. 어댑터 설정 (기본 안드로이드 레이아웃 사용)
|
||||||
val adapter = object : ArrayAdapter<String>(
|
val adapter = object : ArrayAdapter<String>(
|
||||||
@ -154,7 +145,7 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
|||||||
binding.categorySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
binding.categorySpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
val selectedDisplayName = displayList[position]
|
val selectedDisplayName = displayList[position]
|
||||||
currentCategoryKey = categoryMap[selectedDisplayName] ?: "ALL"
|
currentCategoryKey = CategoryManualMapper.CATEGORY_MAP[selectedDisplayName] ?: "ALL"
|
||||||
|
|
||||||
// 선택 변경 시 목록 갱신 (검색어 유지)
|
// 선택 변경 시 목록 갱신 (검색어 유지)
|
||||||
fetchApps(binding.searchInput.text.toString())
|
fetchApps(binding.searchInput.text.toString())
|
||||||
@ -421,6 +412,8 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 앱 목록을 불러오는 함수
|
* 앱 목록을 불러오는 함수
|
||||||
* - 검색어(keyword)가 있으면 필터링을 수행합니다.
|
* - 검색어(keyword)가 있으면 필터링을 수행합니다.
|
||||||
@ -467,7 +460,15 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
|||||||
.sort("lastUseDate", Sort.DESCENDING)
|
.sort("lastUseDate", Sort.DESCENDING)
|
||||||
.find()
|
.find()
|
||||||
|
|
||||||
val scopedAppsList = baseAppQuery.map { realm.copyFromRealm(it) }
|
val scopedAppsList = baseAppQuery.map {
|
||||||
|
val appCopy = realm.copyFromRealm(it)
|
||||||
|
|
||||||
|
// 💡 수동 매퍼를 사용하여 카테고리 강제 재할당
|
||||||
|
// it.systemCategoryInt는 AppInfo 모델에 시스템 카테고리 값이 저장되어 있다고 가정함
|
||||||
|
val fixedCategory = CategoryManualMapper.getFixedCategory(it.pkgName ?:"", it.appName, it.systemCategoryInt)
|
||||||
|
appCopy.category = fixedCategory
|
||||||
|
appCopy
|
||||||
|
}
|
||||||
.filter { appInfo ->
|
.filter { appInfo ->
|
||||||
val isLaunchable = try {
|
val isLaunchable = try {
|
||||||
pm.getLaunchIntentForPackage(appInfo.pkgName ?: "") != null
|
pm.getLaunchIntentForPackage(appInfo.pkgName ?: "") != null
|
||||||
@ -479,7 +480,6 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
|
|||||||
val isCategoryMatch = if (currentCategoryKey == "ALL") {
|
val isCategoryMatch = if (currentCategoryKey == "ALL") {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// AppInfoGetter에서 저장한 값("GAME", "AUDIO" 등)과 비교
|
|
||||||
appInfo.category == currentCategoryKey
|
appInfo.category == currentCategoryKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ class AppInfo : RealmObject {
|
|||||||
var clickCount : Int = 0
|
var clickCount : Int = 0
|
||||||
var lastUseDate : Long = 0L
|
var lastUseDate : Long = 0L
|
||||||
var category : String? = null
|
var category : String? = null
|
||||||
|
var systemCategoryInt : Int = 0
|
||||||
var currentInstalled : Boolean = false
|
var currentInstalled : Boolean = false
|
||||||
var isInstalled : Boolean = false
|
var isInstalled : Boolean = false
|
||||||
// [신규] 0: 항상 보이기(기본), 1: 검색 시만 보이기 (숨김)
|
// [신규] 0: 항상 보이기(기본), 1: 검색 시만 보이기 (숨김)
|
||||||
|
|||||||
@ -0,0 +1,166 @@
|
|||||||
|
package bums.lunatic.launcher.utils
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 로그 분석 및 키워드 기반 카테고리 자동 분류 유틸리티
|
||||||
|
*/
|
||||||
|
object CategoryManualMapper {
|
||||||
|
val CATEGORY_MAP = mapOf(
|
||||||
|
"전체" to "ALL",
|
||||||
|
"게임" to "GAME",
|
||||||
|
"생산성" to "PRODUCTIVITY",
|
||||||
|
"소셜" to "SOCIAL",
|
||||||
|
"오디오" to "MUSIC",
|
||||||
|
"쇼핑" to "SHOPPING",
|
||||||
|
"금융" to "FINANCE",
|
||||||
|
"비디오" to "VIDEO",
|
||||||
|
"이미지" to "IMAGE",
|
||||||
|
"지도" to "MAPS",
|
||||||
|
"뉴스" to "NEWS",
|
||||||
|
"도구" to "TOOLS",
|
||||||
|
"기타" to "UNDEFINED"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1. 고정 패키지명 매핑 (가장 높은 우선순위)
|
||||||
|
private val manualMapping = mapOf(
|
||||||
|
"com.sec.android.app.myfiles" to "TOOLS", // 삼성 내 파일
|
||||||
|
"com.google.android.apps.nbu.files" to "TOOLS", // 구글 Files
|
||||||
|
// VIDEO 카테고리
|
||||||
|
"com.netflix.mediaclient" to "VIDEO",
|
||||||
|
"net.cj.cjhv.gs.tving" to "VIDEO",
|
||||||
|
"com.google.android.youtube" to "VIDEO",
|
||||||
|
"com.disney.disneyplus" to "VIDEO",
|
||||||
|
"com.coupang.mobile.play" to "VIDEO",
|
||||||
|
"kr.co.captv.pooqV2" to "VIDEO", // wavve
|
||||||
|
"com.synology.dsvideo" to "VIDEO",
|
||||||
|
"org.videolan.vlc" to "VIDEO",
|
||||||
|
"com.mxtech.videoplayer.ad" to "VIDEO",
|
||||||
|
"com.gretech.gomplayerko" to "VIDEO",
|
||||||
|
"com.google.android.videos" to "VIDEO", // Google TV
|
||||||
|
|
||||||
|
// MAPS / TRAVEL 카테고리
|
||||||
|
"com.nhn.android.nmap" to "MAPS",
|
||||||
|
"net.daum.android.map" to "MAPS",
|
||||||
|
"com.google.android.apps.maps" to "MAPS",
|
||||||
|
"com.skt.tmap.ku" to "MAPS",
|
||||||
|
"com.locnall.KimGiSa" to "MAPS", // 카카오내비
|
||||||
|
"com.agoda.mobile.consumer" to "MAPS",
|
||||||
|
"com.airbnb.android" to "MAPS",
|
||||||
|
"com.mrt.ducati" to "MAPS", // 마이리얼트립
|
||||||
|
|
||||||
|
// FINANCE 카테고리
|
||||||
|
"viva.republica.toss" to "FINANCE",
|
||||||
|
"com.kakaobank.channel" to "FINANCE",
|
||||||
|
"com.btckorea.bithumb" to "FINANCE",
|
||||||
|
"com.dunamu.exchange" to "FINANCE", // 업비트
|
||||||
|
"com.truefriend.ministock" to "FINANCE",
|
||||||
|
"com.truefriend.neosmartarenewal" to "FINANCE",
|
||||||
|
"com.hyundaicard.appcard" to "FINANCE",
|
||||||
|
"kr.co.samsungcard.mpocket" to "FINANCE",
|
||||||
|
"com.kbcard.cxh.appcard" to "FINANCE",
|
||||||
|
"com.kbstar.kbbank" to "FINANCE",
|
||||||
|
"nh.smart.banking" to "FINANCE",
|
||||||
|
"com.kbankwith.smartbank" to "FINANCE",
|
||||||
|
|
||||||
|
// SHOPPING 카테고리
|
||||||
|
"com.coupang.mobile" to "SHOPPING",
|
||||||
|
"com.coupang.mobile.eats" to "SHOPPING",
|
||||||
|
"com.ebay.kr.gmarket" to "SHOPPING",
|
||||||
|
"com.ebay.kr.auction" to "SHOPPING",
|
||||||
|
"com.elevenst" to "SHOPPING",
|
||||||
|
"net.giosis.shopping.sg" to "SHOPPING", // Qoo10
|
||||||
|
"com.alibaba.aliexpresshd" to "SHOPPING",
|
||||||
|
"com.einnovation.temu" to "SHOPPING",
|
||||||
|
|
||||||
|
// GAME 카테고리
|
||||||
|
"com.sundaytoz.mobile.anisachun.google.service" to "GAME",
|
||||||
|
"com.gof.global" to "GAME", // 화이트아웃서바이벌
|
||||||
|
"com.tap4fun.odin.kingdomguard" to "GAME",
|
||||||
|
"com.dreamgames.royalmatch" to "GAME",
|
||||||
|
"com.ragequitgames.tomorrow" to "GAME"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. 키워드 기반 카테고리 매핑 (두 번째 우선순위)
|
||||||
|
private val keywordMapping = mapOf(
|
||||||
|
"VIDEO" to listOf(
|
||||||
|
"video", "player", "movie", "tv", "cinema", "streaming", "media", "netflix",
|
||||||
|
"비디오", "플레이어", "영화", "티브이", "시네마", "스트리밍", "넷플릭스", "티빙", "유튜브"
|
||||||
|
),
|
||||||
|
"GAME" to listOf(
|
||||||
|
"game", "vulkan", "unity", "unreal", "nexon", "netmarble", "lineage",
|
||||||
|
"게임", "넥슨", "넷마블", "카카오게임", "애니팡", "전투", "전략", "퍼즐", "RPG"
|
||||||
|
),
|
||||||
|
"FINANCE" to listOf(
|
||||||
|
"bank", "card", "finance", "pay", "stock", "invest", "kb", "shinhan", "woori", "hana",
|
||||||
|
"은행", "카드", "금융", "페이", "증권", "주식", "투자", "뱅크", "뱅킹", "보험", "자산"
|
||||||
|
),
|
||||||
|
"MUSIC" to listOf(
|
||||||
|
"music", "audio", "sound", "radio", "melody", "streaming", "melon", "bugs",
|
||||||
|
"뮤직", "음악", "오디오", "사운드", "라디오", "멜로디", "멜론", "벅스", "지니"
|
||||||
|
),
|
||||||
|
"MAPS" to listOf(
|
||||||
|
"map", "navi", "taxi", "transport", "bus", "subway", "metro", "navigation",
|
||||||
|
"지도", "내비", "택시", "교통", "버스", "지하철", "네비", "길찾기"
|
||||||
|
),
|
||||||
|
"IMAGE" to listOf(
|
||||||
|
"photo", "gallery", "image", "camera", "editor", "snap", "pic",
|
||||||
|
"사진", "갤러리", "이미지", "카메라", "편집", "스냅", "뷰어"
|
||||||
|
),
|
||||||
|
"SOCIAL" to listOf(
|
||||||
|
"talk", "messenger", "chat", "social", "sns", "community", "kakao", "telegram",
|
||||||
|
"톡", "메신저", "채팅", "소셜", "커뮤니티", "카카오", "텔레그램", "밴드", "카페"
|
||||||
|
),
|
||||||
|
"SHOPPING" to listOf(
|
||||||
|
"shop", "mall", "market", "delivery", "eats", "order", "commerce", "coupang",
|
||||||
|
"쇼핑", "몰", "마켓", "배달", "이츠", "주문", "커머스", "쿠팡", "마트", "백화점"
|
||||||
|
),
|
||||||
|
"TOOLS" to listOf(
|
||||||
|
"file", "manager", "explorer", "cleaner", "storage", "calculator", "clock", "calendar",
|
||||||
|
"파일", "관리자", "탐색기", "클리너", "저장소", "계산기", "시계", "달력", "메모"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 패키지명, 앱 이름, 시스템 카테고리를 종합하여 최적의 카테고리를 반환합니다.
|
||||||
|
*/
|
||||||
|
fun getFixedCategory(packageName: String?, appName: String?, systemCategory: Int): String {
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// [단계 1] 이전의 수동 맵(manualMapping) 확인
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
manualMapping[packageName]?.let { return it }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// [단계 2] 시스템 분류(systemCategory)가 명확한지 확인
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
val systemType = when (systemCategory) {
|
||||||
|
ApplicationInfo.CATEGORY_GAME -> "GAME"
|
||||||
|
ApplicationInfo.CATEGORY_VIDEO -> "VIDEO"
|
||||||
|
ApplicationInfo.CATEGORY_MAPS -> "MAPS"
|
||||||
|
ApplicationInfo.CATEGORY_AUDIO -> "MUSIC"
|
||||||
|
ApplicationInfo.CATEGORY_PRODUCTIVITY -> "PRODUCTIVITY"
|
||||||
|
ApplicationInfo.CATEGORY_SOCIAL -> "SOCIAL"
|
||||||
|
ApplicationInfo.CATEGORY_NEWS -> "NEWS"
|
||||||
|
ApplicationInfo.CATEGORY_IMAGE -> "IMAGE"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (systemType != null) return systemType
|
||||||
|
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
// [단계 3] 시스템 분류가 없는 경우 키워드 매핑 수행
|
||||||
|
// ---------------------------------------------------------
|
||||||
|
val pkg = packageName?.lowercase(Locale.ROOT) ?: ""
|
||||||
|
val name = appName?.lowercase(Locale.ROOT)?.replace(" ", "") ?: ""
|
||||||
|
|
||||||
|
for ((category, keywords) in keywordMapping) {
|
||||||
|
for (keyword in keywords) {
|
||||||
|
if (pkg.contains(keyword) || name.contains(keyword)) {
|
||||||
|
return category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "UNDEFINED"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -47,6 +47,7 @@ class AppInfoGetter : BaseGetter {
|
|||||||
this.searchIndex = SimpleTransliterater.makeSearchIndex(appName)
|
this.searchIndex = SimpleTransliterater.makeSearchIndex(appName)
|
||||||
this.pkgName = pkgName
|
this.pkgName = pkgName
|
||||||
this.category = getCategory(ri.activityInfo.applicationInfo.category)
|
this.category = getCategory(ri.activityInfo.applicationInfo.category)
|
||||||
|
this.systemCategoryInt = ri.activityInfo.applicationInfo.category
|
||||||
this.alphaCho = AlphabetToChosungMap.getCho(appName)
|
this.alphaCho = AlphabetToChosungMap.getCho(appName)
|
||||||
this.appNameChosung = JamoUtils.split(appName).joinToString("")
|
this.appNameChosung = JamoUtils.split(appName).joinToString("")
|
||||||
})
|
})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user