This commit is contained in:
lunaticbum 2026-01-09 16:36:43 +09:00
parent 62f1e646ab
commit 98fd382ed4
20 changed files with 648 additions and 372 deletions

View File

@ -55,7 +55,9 @@
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<!-- <queries>--> <!-- <queries>-->
<!-- <intent>--> <!-- <intent>-->

View File

@ -2,6 +2,7 @@ package bums.lunatic.launcher
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AppOpsManager
import android.app.SearchManager import android.app.SearchManager
import android.appwidget.AppWidgetHost import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetHostView import android.appwidget.AppWidgetHostView
@ -9,11 +10,13 @@ import android.appwidget.AppWidgetManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.provider.Settings
import android.view.GestureDetector import android.view.GestureDetector
import android.view.KeyEvent import android.view.KeyEvent
import android.view.KeyEvent.ACTION_UP import android.view.KeyEvent.ACTION_UP
@ -35,6 +38,7 @@ import androidx.annotation.RequiresApi
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
@ -62,6 +66,7 @@ import bums.lunatic.launcher.tokiz.Webtoons
import bums.lunatic.launcher.tokiz.YouTube import bums.lunatic.launcher.tokiz.YouTube
import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import bums.lunatic.launcher.workers.WorkersDb.syncSystemUsageStats
import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColors
import com.yausername.ffmpeg.FFmpeg import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
@ -126,8 +131,8 @@ open class LauncherActivity : CommonActivity() {
// 2. 더블 클릭 감지 (빈 공간) // 2. 더블 클릭 감지 (빈 공간)
override fun onDoubleTap(e: MotionEvent): Boolean { override fun onDoubleTap(e: MotionEvent): Boolean {
// 더블 클릭 액션 // 더블 클릭 액션
showToast("더블 클릭: 설정 열기") // showToast("더블 클릭: 설정 열기")
// 예: startActivity(Intent(this@LauncherActivity, SettingsActivity::class.java)) openApp("org.telegram.messenger")
return true return true
} }
@ -142,7 +147,7 @@ open class LauncherActivity : CommonActivity() {
override fun onLongPress(e: MotionEvent) { override fun onLongPress(e: MotionEvent) {
// 위젯 추가 메뉴 등을 띄우려면 여기서 처리 // 위젯 추가 메뉴 등을 띄우려면 여기서 처리
// 주의: 위젯 위에서 롱프레스하면 위젯 드래그가 먼저 작동하도록 설계해야 함 // 주의: 위젯 위에서 롱프레스하면 위젯 드래그가 먼저 작동하도록 설계해야 함
showToast("바탕화면 롱프레스: 위젯 추가") // showToast("바탕화면 롱프레스: 위젯 추가")
selectWidget() // 기존에 만든 위젯 추가 함수 호출 selectWidget() // 기존에 만든 위젯 추가 함수 호출
} }
@ -156,13 +161,27 @@ open class LauncherActivity : CommonActivity() {
fun showToast(msg: String) { fun showToast(msg: String) {
android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show() android.widget.Toast.makeText(this, msg, android.widget.Toast.LENGTH_SHORT).show()
} }
fun openApp(packageName: String) {
val intent = packageManager.getLaunchIntentForPackage(packageName)
if (intent != null) {
// 앱이 설치되어 있으면 실행
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} else {
// 앱이 없으면 플레이스토어로 이동
val playStoreIntent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("market://details?id=$packageName")
}
startActivity(playStoreIntent)
}
}
// 각 스와이프 동작 정의 // 각 스와이프 동작 정의
fun onSwipeUp() { fun onSwipeUp() {
showAppDrawer() // 지난번에 만든 바텀시트 앱서랍 열기 openApp("com.google.android.apps.bard")
} }
fun onSwipeDown() { fun onSwipeDown() {
// 알림창 내리기 등
try { try {
val service = getSystemService("statusbar") val service = getSystemService("statusbar")
val statusbarManager = Class.forName("android.app.StatusBarManager") val statusbarManager = Class.forName("android.app.StatusBarManager")
@ -170,8 +189,14 @@ open class LauncherActivity : CommonActivity() {
expand.invoke(service) expand.invoke(service)
} catch (e: Exception) { e.printStackTrace() } } catch (e: Exception) { e.printStackTrace() }
} }
fun onSwipeLeft() { /* 페이지 이동 등 */ }
fun onSwipeRight() { /* 페이지 이동 등 */ } fun onSwipeLeft() {
showAppDrawer()
}
fun onSwipeRight() {
showContents(R.id.feeds)
}
private lateinit var binding: LauncherActivityBinding private lateinit var binding: LauncherActivityBinding
@ -543,7 +568,14 @@ open class LauncherActivity : CommonActivity() {
val intent = Intent(this, ForeGroundService::class.java) val intent = Intent(this, ForeGroundService::class.java)
this.startForegroundService(intent) this.startForegroundService(intent)
// 1. 시스템 바 공간을 앱이 차지하도록 설정 (상태바 뒤로 레이아웃 확장)
WindowCompat.setDecorFitsSystemWindows(window, false)
// 2. 상태바 색상을 투명하게 변경 (필요한 경우)
window.statusBarColor = Color.TRANSPARENT
// (선택 사항) 내비게이션 바도 투명하게 하고 싶다면
window.navigationBarColor = Color.TRANSPARENT
val nlService = Intent(this, NLService::class.java) val nlService = Intent(this, NLService::class.java)
this.startService(nlService) this.startService(nlService)
@ -783,11 +815,11 @@ open class LauncherActivity : CommonActivity() {
// 손가락이 하나일 때만 이동 허용 // 손가락이 하나일 때만 이동 허용
// [추가] 삭제 영역과 겹치는지 확인하여 시각적 피드백 (예: 빨갛게 변함) // [추가] 삭제 영역과 겹치는지 확인하여 시각적 피드백 (예: 빨갛게 변함)
if (isViewOverlapping(currentDragView!!, binding.deleteZone)) { if (isViewOverlapping(currentDragView!!, binding.deleteZone)) {
binding.deleteZone.setBackgroundColor(android.graphics.Color.RED) binding.deleteZone.setBackgroundResource(R.drawable.bg_circle_emoji_red)
binding.deleteZone.text = "손을 떼면 삭제됩니다" // binding.deleteZone.text = "\uD83D\uDDD1\uFE0F"
} else { } else {
binding.deleteZone.setBackgroundColor(0x99FF0000.toInt()) // 반투명 빨강 (원래 색) binding.deleteZone.setBackgroundResource(R.drawable.bg_circle_emoji)
binding.deleteZone.text = "삭제하려면 여기에 놓으세요" // binding.deleteZone.text = "\uD83D\uDDD1\uFE0F"
} }
if (ev.pointerCount == 1 && lastTouchX != 0f && lastTouchY != 0f) { if (ev.pointerCount == 1 && lastTouchX != 0f && lastTouchY != 0f) {
@ -866,7 +898,10 @@ open class LauncherActivity : CommonActivity() {
} }
fun showContents(id : Int) { fun showContents(id : Int) {
binding.fragmentLayer.visibility = View.VISIBLE
binding.fragmentContainer.visibility = View.VISIBLE binding.fragmentContainer.visibility = View.VISIBLE
binding.controllPanel.visibility = View.VISIBLE
binding.floatingActionMenu.visibility = View.VISIBLE
when(id) { when(id) {
R.id.feeds -> { R.id.feeds -> {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
@ -912,7 +947,10 @@ open class LauncherActivity : CommonActivity() {
supportFragmentManager.beginTransaction() supportFragmentManager.beginTransaction()
.remove(it) .remove(it)
.commit() .commit()
binding.fragmentLayer.visibility = View.GONE
binding.fragmentContainer.visibility = View.GONE binding.fragmentContainer.visibility = View.GONE
binding.controllPanel.visibility = View.GONE
binding.floatingActionMenu.visibility = View.GONE
} }
} }
@ -962,10 +1000,31 @@ open class LauncherActivity : CommonActivity() {
super.onDestroy() super.onDestroy()
} }
private fun checkUsageStatsPermission(): Boolean {
val appOps = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
val mode = appOps.checkOpNoThrow(
AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(),
packageName
)
return mode == AppOpsManager.MODE_ALLOWED
}
private fun requestPermission() {
if (!checkUsageStatsPermission()) {
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
}
}
@RequiresApi(Build.VERSION_CODES.O_MR1) @RequiresApi(Build.VERSION_CODES.O_MR1)
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
Blog.LOGE("LauncherActivity onResume") Blog.LOGE("LauncherActivity onResume")
if (checkUsageStatsPermission()) {
WorkersDb.syncSystemUsageStats(applicationContext)
} else {
requestPermission()
}
} }
private fun openSearch() { private fun openSearch() {
@ -988,6 +1047,7 @@ open class LauncherActivity : CommonActivity() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container) val currentFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
if (currentFragment == null) showContents(R.id.close)
when(currentFragment) { when(currentFragment) {
is RssHome ->{ is RssHome ->{
if (currentFragment.binding.layoutRssSummary.root.isVisible) { if (currentFragment.binding.layoutRssSummary.root.isVisible) {
@ -1002,6 +1062,9 @@ open class LauncherActivity : CommonActivity() {
is Novels -> { is Novels -> {
currentFragment.actionNextEvent(false) currentFragment.actionNextEvent(false)
} }
else -> {
showContents(R.id.close)
}
} }
} }
}) })

View File

@ -48,6 +48,8 @@ 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.JamoUtils import bums.lunatic.launcher.utils.JamoUtils
import bums.lunatic.launcher.workers.UsageLogType
import bums.lunatic.launcher.workers.UsageUpdateType
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import io.realm.kotlin.ext.query import io.realm.kotlin.ext.query
import io.realm.kotlin.query.RealmResults import io.realm.kotlin.query.RealmResults
@ -99,7 +101,7 @@ class AppDrawer : CommonActivity() {
layoutType = settingsPrefs!!.getInt(KEY_APPS_LAYOUT, 0) layoutType = settingsPrefs!!.getInt(KEY_APPS_LAYOUT, 0)
appsAdapter = AppsAdapter(packageManager!!, supportFragmentManager, binding.appsCount) appsAdapter = AppsAdapter(packageManager!!, supportFragmentManager, binding.appsCount)
contactAdapter = ContactAdapter(packageManager!!, supportFragmentManager) contactAdapter = ContactAdapter(applicationContext, supportFragmentManager)
binding.appsCount.visibility = if (settingsPrefs!!.getBoolean(KEY_APPS_COUNT, true)) VISIBLE else GONE binding.appsCount.visibility = if (settingsPrefs!!.getBoolean(KEY_APPS_COUNT, true)) VISIBLE else GONE
binding.searchNmap.setOnClickListener { binding.searchNmap.setOnClickListener {
@ -244,7 +246,7 @@ class AppDrawer : CommonActivity() {
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
pakage?.let { pakage?.let {
mapIntent.setPackage(pakage) mapIntent.setPackage(pakage)
WorkersDb.updateAppUse(pakage) WorkersDb.logAppUsage(pakage, UsageLogType.APP, UsageUpdateType.DATETIME)
} }
startActivity(mapIntent) startActivity(mapIntent)
} }

View File

@ -1,6 +1,7 @@
package bums.lunatic.launcher.apps package bums.lunatic.launcher.apps
import android.app.Dialog import android.app.Dialog
import android.app.SearchManager
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
@ -21,6 +22,8 @@ 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.JamoUtils import bums.lunatic.launcher.utils.JamoUtils
import bums.lunatic.launcher.workers.UsageLogType
import bums.lunatic.launcher.workers.UsageUpdateType
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.google.android.gms.common.wrappers.PackageManagerWrapper import com.google.android.gms.common.wrappers.PackageManagerWrapper
import com.google.android.gms.common.wrappers.Wrappers.packageManager import com.google.android.gms.common.wrappers.Wrappers.packageManager
@ -110,12 +113,12 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
bottomSheet.layoutParams = layoutParams bottomSheet.layoutParams = layoutParams
} }
} }
private var recAdapter: AppsAdapter? = null private var recAdapter: RecommendedAppsAdapter? = null
private fun setupAdapters() { private fun setupAdapters() {
// 기존 Activity의 packageManager 대신 requireContext().packageManager 사용 // 기존 Activity의 packageManager 대신 requireContext().packageManager 사용
val pm = requireContext().packageManager val pm = requireContext().packageManager
appsAdapter = AppsAdapter(pm, childFragmentManager, binding.appsCount) appsAdapter = AppsAdapter(pm, childFragmentManager, binding.appsCount)
contactAdapter = ContactAdapter(pm, childFragmentManager) contactAdapter = ContactAdapter(requireContext(), childFragmentManager)
// 가로 그리드 개수 4~5개 정도로 조정 (기존 2개였으면 그대로 유지) // 가로 그리드 개수 4~5개 정도로 조정 (기존 2개였으면 그대로 유지)
binding.appsList.layoutManager = GridLayoutManager(context, 3,GridLayoutManager.HORIZONTAL,false) binding.appsList.layoutManager = GridLayoutManager(context, 3,GridLayoutManager.HORIZONTAL,false)
@ -126,7 +129,7 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
binding.contactList.layoutManager = GridLayoutManager(context, 3,GridLayoutManager.HORIZONTAL,false) binding.contactList.layoutManager = GridLayoutManager(context, 3,GridLayoutManager.HORIZONTAL,false)
binding.contactList.adapter = contactAdapter binding.contactList.adapter = contactAdapter
recAdapter = AppsAdapter(pm, childFragmentManager,null) recAdapter = RecommendedAppsAdapter(requireContext(),pm,childFragmentManager)
binding.recAppsList.layoutManager = GridLayoutManager(context, 1,GridLayoutManager.HORIZONTAL,false) binding.recAppsList.layoutManager = GridLayoutManager(context, 1,GridLayoutManager.HORIZONTAL,false)
binding.recAppsList.adapter = recAdapter binding.recAppsList.adapter = recAdapter
} }
@ -163,7 +166,12 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
openSearchApps("https://www.youtube.com/results?search_query=${getInputText()}", "com.google.android.youtube") openSearchApps("https://www.youtube.com/results?search_query=${getInputText()}", "com.google.android.youtube")
} }
binding.searchGoogle.setOnClickListener { binding.searchGoogle.setOnClickListener {
openSearchApps("https://www.google.com/search?q=${getInputText()}", "com.android.chrome") val intent = Intent(Intent.ACTION_WEB_SEARCH).apply {
putExtra(SearchManager.QUERY, getInputText()) // 질문 전달
}
if (intent.resolveActivity(requireContext().packageManager) != null) {
startActivity(intent)
}
} }
binding.searchNaver.setOnClickListener { binding.searchNaver.setOnClickListener {
openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${getInputText()}", "com.nhn.android.search") openSearchApps("https://search.naver.com/search.naver?where=nexearch&query=${getInputText()}", "com.nhn.android.search")
@ -178,7 +186,7 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(schemeString)) val intent = Intent(Intent.ACTION_VIEW, Uri.parse(schemeString))
packageName?.let { packageName?.let {
intent.setPackage(it) intent.setPackage(it)
WorkersDb.updateAppUse(it) WorkersDb.logAppUsage(packageName, UsageLogType.APP, UsageUpdateType.DATETIME)
} }
startActivity(intent) startActivity(intent)
dismiss() // 실행 후 닫기 dismiss() // 실행 후 닫기
@ -211,13 +219,24 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
val pm = requireContext().packageManager val pm = requireContext().packageManager
// 1. [추천 로직] 에러가 나도 앱 목록 로딩은 진행되도록 try-catch 분리 // 1. [추천 로직] 에러가 나도 앱 목록 로딩은 진행되도록 try-catch 분리
val recommendedPkgNames = try { val scoredItems = WorkersDb.getContextualRecommendations(limit = 8)
if (keyword.isNullOrEmpty()) {
WorkersDb.getContextualRecommendations(limit = 5) val unifiedList = mutableListOf<RecommendationItem>()
} else {
emptyList() // 검색 중에는 추천 안 함 for (item in scoredItems) {
if (item.type == "APP") {
val app = realm.query<AppInfo>("pkgName == $0", item.key).first().find()
if (app != null && !app.blockRecommend) {
unifiedList.add(RecommendationItem.AppItem(realm.copyFromRealm(app)))
}
} else if (item.type == "CONTACT") {
// 연락처 ID나 전화번호로 조회 (Log 저장 시 key가 무엇인지에 따라 다름)
val contact = realm.query<SimpleContact>("id == $0", item.key).first().find()
if (contact != null) {
unifiedList.add(RecommendationItem.ContactItem(realm.copyFromRealm(contact)))
}
} }
} catch (e: Exception) { emptyList() } }
try { try {
// 2. [쿼리 구성] // 2. [쿼리 구성]
@ -254,12 +273,9 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
false false
} }
} }
// C. [추천 앱 객체 추출]
val recAppList = recommendedPkgNames.mapNotNull { pkg ->
allApps.find { it.pkgName == pkg }
}
val mainAppList = allApps
val mainAppList = allApps.filter { it.visibilityMode == 0}
var contactQuery = realm.query<SimpleContact>() var contactQuery = realm.query<SimpleContact>()
@ -282,16 +298,15 @@ class AppDrawerBottomSheet : BottomSheetDialogFragment() {
val contactsResult = contactQuery.find() val contactsResult = contactQuery.find()
val contactsList = contactsResult.map { realm.copyFromRealm(it) } val contactsList = contactsResult.map { realm.copyFromRealm(it) }
Blog.LOGE("unifiedList >>> ${unifiedList.size}")
// 6. [UI 업데이트] // 6. [UI 업데이트]
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (recAppList.isNotEmpty() && keyword.isNullOrEmpty()) {
if (unifiedList.isNotEmpty()) {
binding.titleRecommend.visibility = View.VISIBLE binding.titleRecommend.visibility = View.VISIBLE
binding.recAppsList.visibility = View.VISIBLE binding.recAppsList.visibility = View.VISIBLE
recAdapter?.updateData(recAppList) recAdapter?.submitList(unifiedList)
} else {
// 검색 중이거나 데이터 없으면 숨김
binding.titleRecommend.visibility = View.GONE
binding.recAppsList.visibility = View.GONE
} }
// 2. 전체 앱 리스트 업데이트 // 2. 전체 앱 리스트 업데이트

View File

@ -54,6 +54,8 @@ import bums.lunatic.launcher.helpers.UniUtils.Companion.screenHeight
import bums.lunatic.launcher.helpers.UniUtils.Companion.screenWidth import bums.lunatic.launcher.helpers.UniUtils.Companion.screenWidth
import bums.lunatic.launcher.model.AppInfo import bums.lunatic.launcher.model.AppInfo
import bums.lunatic.launcher.utils.JamoUtils import bums.lunatic.launcher.utils.JamoUtils
import bums.lunatic.launcher.workers.UsageLogType
import bums.lunatic.launcher.workers.UsageUpdateType
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -126,29 +128,20 @@ internal class AppMenu : BottomSheetDialogFragment() {
"최종 실행 일시 : ".plus(SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date(app.lastUseDate))) "최종 실행 일시 : ".plus(SimpleDateFormat("yyyy-MM-dd HH:mm").format(Date(app.lastUseDate)))
app.currentInstalled = true app.currentInstalled = true
binding.alterName.setText(app.koreanName) binding.alterName.setText(app.koreanName)
// app.clickCount = app.clickCount + 15 binding.recommend.isChecked = app.blockRecommend
binding.listVisible.isChecked = app.visibilityMode == 1
// app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
// app.clickCount = app.clickCount + 15
// app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
} }
} }
} }
} }
fun update() {
WorkersDb.getRealm().apply { binding.totalTouch.setOnClickListener {
writeBlocking { WorkersDb.logAppUsage(packageName, UsageLogType.APP,UsageUpdateType.COUNT)
var result = query<AppInfo>("pkgName == $0",packageName).find() }
if(result.size > 0) { binding.lastTouchDate.setOnClickListener {
val app = result.first() WorkersDb.logAppUsage(packageName, UsageLogType.APP,UsageUpdateType.DATETIME)
app.clickCount = app.clickCount + 15
}
}
}
} }
binding.totalTouch.setOnClickListener { update() }
binding.lastTouchDate.setOnClickListener { update() }
binding.alterName.doOnTextChanged { text, start, before, count -> binding.alterName.doOnTextChanged { text, start, before, count ->
WorkersDb.getRealm().apply { WorkersDb.getRealm().apply {
@ -168,6 +161,7 @@ internal class AppMenu : BottomSheetDialogFragment() {
hint = defAppName hint = defAppName
} }
binding.appPackage.text = packageName binding.appPackage.text = packageName
return binding.root return binding.root
} }
@ -189,6 +183,25 @@ internal class AppMenu : BottomSheetDialogFragment() {
binding.appInfo.setOnClickListener { appInfo() } binding.appInfo.setOnClickListener { appInfo() }
binding.appShare.setOnClickListener { share() } binding.appShare.setOnClickListener { share() }
binding.appUninstall.setOnClickListener { uninstall() } binding.appUninstall.setOnClickListener { uninstall() }
binding.listVisible.setOnClickListener {
WorkersDb.getRealm().writeBlocking {
var result = query<AppInfo>("pkgName == $0",packageName).find()
if (result.size > 0) {
var appInfo = result.first()
appInfo.visibilityMode = if (binding.listVisible.isChecked) 1 else 0
}
}
}
binding.recommend.setOnClickListener {
WorkersDb.getRealm().writeBlocking {
var result = query<AppInfo>("pkgName == $0",packageName).find()
if(result.size > 0){
var appInfo = result.first()
appInfo.blockRecommend = binding.recommend.isChecked
}
}
}
} }

View File

@ -30,21 +30,52 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bums.lunatic.launcher.LauncherActivity.Companion.lActivity import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
import bums.lunatic.launcher.R import bums.lunatic.launcher.R
import bums.lunatic.launcher.apps.IconPackManager.Companion.getDrawableIconForPackage
import bums.lunatic.launcher.databinding.AppsChildBinding import bums.lunatic.launcher.databinding.AppsChildBinding
import bums.lunatic.launcher.model.AppInfo import bums.lunatic.launcher.model.AppInfo
import bums.lunatic.launcher.utils.Blog import bums.lunatic.launcher.utils.Blog
import bums.lunatic.launcher.workers.UsageLogType
import bums.lunatic.launcher.workers.UsageUpdateType
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import io.realm.kotlin.ext.query import io.realm.kotlin.ext.query
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class AppsViewHolder(var view: AppsChildBinding) : RecyclerView.ViewHolder(view.root) {
fun bind(
packageManager: PackageManager,
fragmentManager: FragmentManager,
appinfo: AppInfo) {
view.apply {
childTextview.text = appinfo.appName
appIconTwo.visibility = View.VISIBLE
loadIconAsync(appIconTwo, appinfo.pkgName)
childTextview.apply {
gravity = Gravity.CENTER
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twelve))
}
}
view.root.apply {
/* on click - open app */
setOnClickListener {
appinfo.pkgName?.let { WorkersDb.logAppUsage(it, UsageLogType.APP,datetime = UsageUpdateType.DATETIME) }
context.startActivity(packageManager.getLaunchIntentForPackage(appinfo.pkgName!!))
}
/* on long click - open app menu */
setOnLongClickListener {
appinfo.pkgName?.let { WorkersDb.logAppUsage(it, UsageLogType.APP,datetime = UsageUpdateType.JC) }
AppMenu().apply {
}.show(fragmentManager, appinfo.pkgName)
true
}
}
}
}
internal class AppsAdapter( internal class AppsAdapter(
private val packageManager: PackageManager, private val packageManager: PackageManager,
private val fragmentManager: FragmentManager, private val fragmentManager: FragmentManager,
private val appsCount: TextView?) : RecyclerView.Adapter<AppsAdapter.AppsViewHolder>() { private val appsCount: TextView?) : RecyclerView.Adapter<AppsViewHolder>() {
private var oldList = mutableListOf<AppInfo>() private var oldList = mutableListOf<AppInfo>()
// private var appGravity: Int = Gravity.CENTER // private var appGravity: Int = Gravity.CENTER
@ -58,94 +89,18 @@ internal class AppsAdapter(
override fun onBindViewHolder(holder: AppsViewHolder, i: Int) { override fun onBindViewHolder(holder: AppsViewHolder, i: Int) {
val item = oldList[i] val item = oldList[i]
holder.bind(packageManager,fragmentManager,item)
holder.view.apply {
childTextview.text = item.appName
appIconTwo.visibility = View.VISIBLE
loadIconAsync(appIconTwo, item.pkgName)
childTextview.apply {
gravity = Gravity.CENTER
setTextSize(TypedValue.COMPLEX_UNIT_PX, lActivity!!.resources.getDimension(R.dimen.twelve))
}
}
holder.view.root.apply {
/* on click - open app */
setOnClickListener {
WorkersDb.getRealm().apply {
writeBlocking {
Blog.LOGE("item.pkgName >>>> ${item.pkgName
}")
var result = query<AppInfo>("pkgName == $0",item.pkgName).find()
if(result.size > 0) {
val app = result.first()
app.clickCount = app.clickCount + 1
app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
}
}
}
item.pkgName?.let { WorkersDb.logAppUsage(it,"APP") }
context.startActivity(packageManager.getLaunchIntentForPackage(item.pkgName!!))
}
/* on long click - open app menu */
setOnLongClickListener {
WorkersDb.getRealm().apply {
writeBlocking {
var result = query<AppInfo>("pkgName == $0",item.pkgName).find()
if(result.size > 0) {
val app = result.first()
app.clickCount = app.clickCount + 15
// app.lastUseDate = Math.max(app.lastUseDate, System.currentTimeMillis())
}
}
}
AppMenu().apply {
}.show(fragmentManager, item.pkgName)
true
}
}
} }
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 override fun getItemCount(): Int = oldList.size
inner class AppsViewHolder(var view: AppsChildBinding) : RecyclerView.ViewHolder(view.root)
/* update app list */ /* update app list */
fun updateData(newList: List<AppInfo>) { fun updateData(newList: List<AppInfo>) {
val diffUtilResult = DiffUtil.calculateDiff(AppsDiffUtil(oldList, newList)) val diffUtilResult = DiffUtil.calculateDiff(AppsDiffUtil(oldList, newList))
// [수정 전] dispatchUpdatesTo가 먼저 있어서 에러 발생함
// diffUtilResult.dispatchUpdatesTo(this)
// oldList.clear()
// oldList.addAll(newList)
// [수정 후] 반드시 리스트 데이터를 먼저 갱신하고 나서 알림을 보내야 합니다!
oldList.clear() oldList.clear()
oldList.addAll(newList) oldList.addAll(newList)
diffUtilResult.dispatchUpdatesTo(this) // <-- 순서 변경 (맨 뒤로) diffUtilResult.dispatchUpdatesTo(this) // <-- 순서 변경 (맨 뒤로)
@ -171,3 +126,25 @@ internal class AppsDiffUtil(
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean = override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition] oldList[oldItemPosition] == newList[newItemPosition]
} }
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)
}
}
}

View File

@ -19,7 +19,9 @@
package bums.lunatic.launcher.apps package bums.lunatic.launcher.apps
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.pm.PackageManager import android.content.Context
import android.content.Intent
import android.net.Uri
import android.view.Gravity import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -28,14 +30,42 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bums.lunatic.launcher.databinding.ContactItemBinding import bums.lunatic.launcher.databinding.ContactItemBinding
import bums.lunatic.launcher.model.SimpleContact import bums.lunatic.launcher.model.SimpleContact
import bums.lunatic.launcher.utils.JamoUtils import bums.lunatic.launcher.workers.UsageLogType
import io.realm.kotlin.types.RealmObject import bums.lunatic.launcher.workers.UsageUpdateType
import io.realm.kotlin.types.annotations.PrimaryKey import bums.lunatic.launcher.workers.WorkersDb
class ContactViewHolder(var view: ContactItemBinding) : RecyclerView.ViewHolder(view.root) {
fun bind(context: Context, fragmentManager: FragmentManager , simpleContact: SimpleContact) {
view.apply {
name.text = simpleContact.name
number.text= simpleContact.phoneNumber
}
view.root.apply {
/* on click - open app */
setOnClickListener {
var intent = Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:" + simpleContact.phoneNumber));
context.startActivity(intent);
simpleContact.id?.let {
WorkersDb.logAppUsage(it, UsageLogType.CONTACT, UsageUpdateType.DATETIME)
}
}
/* on long click - open app menu */
setOnLongClickListener {
simpleContact.id?.let {
WorkersDb.logAppUsage(it, UsageLogType.CONTACT, UsageUpdateType.JC)
}
ContactMenu().show(fragmentManager, simpleContact.id.toString())
true
}
}
}
}
internal class ContactAdapter ( internal class ContactAdapter (
private val packageManager: PackageManager, private val context: Context,
private val fragmentManager: FragmentManager) : RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() { private val fragmentManager: FragmentManager) : RecyclerView.Adapter<ContactViewHolder>() {
private var oldList = mutableListOf<SimpleContact>() private var oldList = mutableListOf<SimpleContact>()
private var appGravity: Int = Gravity.CENTER private var appGravity: Int = Gravity.CENTER
@ -49,32 +79,12 @@ internal class ContactAdapter (
override fun onBindViewHolder(holder: ContactViewHolder, i: Int) { override fun onBindViewHolder(holder: ContactViewHolder, i: Int) {
val item = oldList[i] val item = oldList[i]
// BLog.LOGE("name >>> ${item.name} :: ${item.touchCount} :: ${RecentCallGetter.dateFormat.format( holder.bind(context, fragmentManager,item)
// Date(item.lastedTouchDateTime)
// )}")
holder.view.apply {
name.text = item.name
number.text= item.phoneNumber
}
holder.view.root.apply {
/* on click - open app */
setOnClickListener {
ContactMenu().show(fragmentManager, item.id.toString())
}
/* on long click - open app menu */
setOnLongClickListener {
// BLog.LOGE("item.id.toString() >> ${item.id.toString()}")
ContactMenu().show(fragmentManager, item.id.toString())
true
}
}
} }
override fun getItemCount(): Int = oldList.size override fun getItemCount(): Int = oldList.size
inner class ContactViewHolder(var view: ContactItemBinding) : RecyclerView.ViewHolder(view.root)
/* update app list */ /* update app list */
fun updateData(newList: List<SimpleContact>) { fun updateData(newList: List<SimpleContact>) {

View File

@ -29,6 +29,8 @@ import bums.lunatic.launcher.LauncherActivity.Companion.lActivity
import bums.lunatic.launcher.databinding.ContactMenuBinding import bums.lunatic.launcher.databinding.ContactMenuBinding
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.workers.UsageLogType
import bums.lunatic.launcher.workers.UsageUpdateType
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.workers.WorkersDb
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -62,19 +64,14 @@ internal class ContactMenu : BottomSheetDialogFragment() {
} }
} }
fun update() {
WorkersDb.getRealm().writeBlocking {
if (contactId != null && contactId.length ?: 0 > 0) { binding.totalTouch.setOnClickListener {
val result = query<SimpleContact>().query("id == $0", contactId).find() WorkersDb.logAppUsage(contactId, UsageLogType.CONTACT,UsageUpdateType.COUNT)
if(result.size > 0){
var contact = result.first()
contact.touchCount = contact.touchCount + 15
}
}
} }
binding.lastTouchDate.setOnClickListener {
WorkersDb.logAppUsage(contactId, UsageLogType.CONTACT,UsageUpdateType.DATETIME)
} }
binding.totalTouch.setOnClickListener { update() }
binding.lastTouchDate.setOnClickListener { update() }
val resolver = lActivity!!.contentResolver val resolver = lActivity!!.contentResolver
val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI val phoneUri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI
@ -84,23 +81,23 @@ internal class ContactMenu : BottomSheetDialogFragment() {
) )
Blog.LOGE("GetContact", "packageName ${contactId}") Blog.LOGE("GetContact", "packageName ${contactId}")
try { try {
val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null , null) val cursor = resolver.query(phoneUri, projection, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = " + contactId, null , null)
if (cursor != null) { if (cursor != null) {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val nameIndex = cursor.getColumnIndex(projection[0]) val nameIndex = cursor.getColumnIndex(projection[0])
val numberIndex = cursor.getColumnIndex(projection[1]) val numberIndex = cursor.getColumnIndex(projection[1])
contactName = cursor.getString(nameIndex) contactName = cursor.getString(nameIndex)
var number = cursor.getString(numberIndex) var number = cursor.getString(numberIndex)
contactPhoneNumber = number.replace("-", "") contactPhoneNumber = number.replace("-", "")
Blog.LOGE("GetContact", "이름 : $contactName 번호 : $contactPhoneNumber ") Blog.LOGE("GetContact", "이름 : $contactName 번호 : $contactPhoneNumber ")
} }
} }
// 데이터 계열은 반드시 닫아줘야 한다. // 데이터 계열은 반드시 닫아줘야 한다.
cursor!!.close() cursor!!.close()
} catch ( e : Exception) { } catch ( e : Exception) {
e.printStackTrace() e.printStackTrace()
} }
/* get application info */ /* get application info */
@ -121,6 +118,28 @@ internal class ContactMenu : BottomSheetDialogFragment() {
binding.detailedInfo.setOnClickListener { detailedInfo() } binding.detailedInfo.setOnClickListener { detailedInfo() }
binding.call.setOnClickListener { callPhone() } binding.call.setOnClickListener { callPhone() }
binding.sms.setOnClickListener { sendSms() } binding.sms.setOnClickListener { sendSms() }
binding.listVisible.setOnClickListener {
if (contactId != null && contactId.length ?: 0 > 0) {
WorkersDb.getRealm().writeBlocking {
val result = query<SimpleContact>().query("id == $0", contactId).find()
if (result.size > 0) {
var contact = result.first()
contact.visibilityMode = if (binding.listVisible.isChecked) 1 else 0
}
}
}
}
binding.recommend.setOnClickListener {
if (contactId != null && contactId.length ?: 0 > 0) {
WorkersDb.getRealm().writeBlocking {
val result = query<SimpleContact>().query("id == $0", contactId).find()
if(result.size > 0){
var contact = result.first()
contact.blockRecommend = binding.recommend.isChecked
}
}
}
}
// binding.activityBrowser.setOnClickListener { activityBrowser() } // binding.activityBrowser.setOnClickListener { activityBrowser() }
// binding.appStore.setOnClickListener { appStore() } // binding.appStore.setOnClickListener { appStore() }
// binding.appFreeform.setOnClickListener { freeform() } // binding.appFreeform.setOnClickListener { freeform() }

View File

@ -1,68 +1,68 @@
package bums.lunatic.launcher.apps package bums.lunatic.launcher.apps
import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import bums.lunatic.launcher.databinding.AppsChildBinding // 기존 레이아웃 재사용 (또는 별도 레이아웃 생성) import bums.lunatic.launcher.databinding.AppsChildBinding
import bums.lunatic.launcher.databinding.AppsChildRecBinding import bums.lunatic.launcher.databinding.AppsChildRecBinding
import bums.lunatic.launcher.databinding.ContactItemBinding
import bums.lunatic.launcher.model.AppInfo import bums.lunatic.launcher.model.AppInfo
import bums.lunatic.launcher.workers.WorkersDb import bums.lunatic.launcher.model.SimpleContact
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class RecommendedAppsAdapter(private val pm: PackageManager) : RecyclerView.Adapter<RecommendedAppsAdapter.ViewHolder>() { sealed class RecommendationItem {
// 앱을 감싸는 클래스
data class AppItem(val appInfo: AppInfo) : RecommendationItem()
private val items = ArrayList<AppInfo>() // 연락처를 감싸는 클래스
data class ContactItem(val contact: SimpleContact) : RecommendationItem()
}
fun submitList(newItems: List<AppInfo>) {
class RecommendedAppsAdapter(
private val context: Context,
private val packageManager: PackageManager,
private val fragmentManager: FragmentManager) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val items = ArrayList<RecommendationItem>()
fun submitList(newItems: List<RecommendationItem>) {
items.clear() items.clear()
items.addAll(newItems) items.addAll(newItems)
notifyDataSetChanged() notifyDataSetChanged()
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { companion object {
// 기존 apps_child.xml을 재사용하거나, 아이콘만 보여주는 새로운 xml을 만들어도 됨 const val TYPE_APP = 0
val binding = AppsChildRecBinding.inflate(LayoutInflater.from(parent.context), parent, false) const val TYPE_CONTACT = 1
return ViewHolder(binding)
} }
override fun onBindViewHolder(holder: ViewHolder, position: Int) { override fun getItemViewType(position: Int): Int {
val item = items[position] return when (items[position]) {
is RecommendationItem.AppItem -> TYPE_APP
// 앱 이름 (추천 영역에서는 숨기거나 작게 표시 가능) is RecommendationItem.ContactItem -> TYPE_CONTACT
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)
}
} }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return (if (viewType == TYPE_APP) {
AppsViewHolder(AppsChildBinding.inflate(LayoutInflater.from(parent.context), parent, false))
} else {
ContactViewHolder(ContactItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}) as RecyclerView.ViewHolder
}
// 클릭 이벤트 override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener { when (val item = items[position]) {
try { is RecommendationItem.AppItem -> {
val intent = pm.getLaunchIntentForPackage(item.pkgName ?: "") (holder as AppsViewHolder).bind(packageManager,fragmentManager,item.appInfo)
if (intent != null) { }
holder.itemView.context.startActivity(intent) is RecommendationItem.ContactItem -> {
// [중요] 사용 로그 저장 -> 추천 정확도 상승 (holder as ContactViewHolder).bind(context, fragmentManager,item.contact)
WorkersDb.logAppUsage(item.pkgName ?: "", "APP") }
}
} catch (e: Exception) { e.printStackTrace() }
} }
} }
override fun getItemCount(): Int = items.size override fun getItemCount(): Int = items.size
class ViewHolder(val binding: AppsChildRecBinding) : RecyclerView.ViewHolder(binding.root)
} }

View File

@ -127,19 +127,12 @@ class LocationUpdateService : Service(), LocationListener {
} }
} }
protected var locationManager: LocationManager? = null protected var locationManager: LocationManager? = null
var checkGPS = false var checkGPS = false
var checkNetwork = false var checkNetwork = false
// boolean canGetLocation = false;
var loc: Location? = null
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
TODO("Not yet implemented") return null
} }
override fun onLocationChanged(p0: Location) { override fun onLocationChanged(p0: Location) {

View File

@ -43,6 +43,8 @@ object TaskAggregator {
if (oldApp != null) { if (oldApp != null) {
newApp.clickCount = oldApp.clickCount newApp.clickCount = oldApp.clickCount
newApp.lastUseDate = oldApp.lastUseDate newApp.lastUseDate = oldApp.lastUseDate
newApp.visibilityMode = oldApp.visibilityMode
newApp.blockRecommend = oldApp.blockRecommend
// 즐겨찾기 여부 등 보존해야 할 다른 필드가 있다면 여기서 복사 // 즐겨찾기 여부 등 보존해야 할 다른 필드가 있다면 여기서 복사
// newApp.isFavorite = oldApp.isFavorite // newApp.isFavorite = oldApp.isFavorite
} }
@ -75,16 +77,18 @@ object TaskAggregator {
if (oldContact != null) { if (oldContact != null) {
contact.touchCount = oldContact.touchCount contact.touchCount = oldContact.touchCount
contact.lastedTouchDateTime = oldContact.lastedTouchDateTime contact.lastedTouchDateTime = oldContact.lastedTouchDateTime
contact.visibilityMode = oldContact.visibilityMode
contact.blockRecommend = oldContact.blockRecommend
} }
copyToRealm(contact, UpdatePolicy.ALL) copyToRealm(contact, UpdatePolicy.ALL)
} }
// 삭제된 연락처 처리 // 삭제된 연락처 처리
val contactsToDelete = query<SimpleContact>(SimpleContact::class).find().filter { // val contactsToDelete = query<SimpleContact>(SimpleContact::class).find().filter {
!activeContactIds.contains(it.id) // !activeContactIds.contains(it.id)
} // }
contactsToDelete.forEach { delete(it) } // contactsToDelete.forEach { delete(it) }
} }

View File

@ -1,5 +1,7 @@
package bums.lunatic.launcher.workers package bums.lunatic.launcher.workers
import android.app.usage.UsageStatsManager
import android.content.Context
import bums.lunatic.launcher.BuildConfig import bums.lunatic.launcher.BuildConfig
import bums.lunatic.launcher.common.letTrue import bums.lunatic.launcher.common.letTrue
import bums.lunatic.launcher.model.AppInfo import bums.lunatic.launcher.model.AppInfo
@ -57,6 +59,10 @@ class CustMigration : AutomaticSchemaMigration {
Blog.LOGE(migrationContext.newRealm.configuration.schemaVersion.toString()) Blog.LOGE(migrationContext.newRealm.configuration.schemaVersion.toString())
} }
} }
data class ScoredItem(val key: String, val type: String, val score: Double)
enum class UsageLogType { APP, CONTACT };
enum class UsageUpdateType { JC, COUNT, DATETIME };
object WorkersDb { object WorkersDb {
//RecentCall::class, RecentSms::class, //RecentCall::class, RecentSms::class,
@ -70,30 +76,73 @@ object WorkersDb {
) )
//,UserActionModel::class //,UserActionModel::class
// [추가] 앱/연락처 사용 시 로그 저장 (기존 updateAppUse 대신 이거 호출) // [추가] 앱/연락처 사용 시 로그 저장 (기존 updateAppUse 대신 이거 호출)
fun logAppUsage(key: String, type: String = "APP") { fun logAppUsage(key: String, type: UsageLogType = UsageLogType.APP, datetime: UsageUpdateType) {
val realm = getRealm() val realm = getRealm()
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
realm.writeBlocking { // 비동기로 하려면 write { } 사용 realm.writeBlocking {
// 1. 기존 카운트 증가 (기존 로직 유지) when(type) {
// ... (AppInfo 조회 후 clickCount++ 하는 코드) ... UsageLogType.APP -> {
var result = query<AppInfo>("pkgName == $0",key).find()
if(result.isNotEmpty()) {
val app = result.first()
when(datetime) {
UsageUpdateType.JC -> {
app.clickCount += 1
}
UsageUpdateType.COUNT -> {
app.clickCount += 15
}
UsageUpdateType.DATETIME -> {
app.clickCount += 1
app.lastUseDate = System.currentTimeMillis()
}
else -> {}
}
// 2. 상세 로그 저장 (추가된 부분) }
copyToRealm(AppUsageLog().apply { }
itemKey = key UsageLogType.CONTACT -> {
itemType = type val results = query<SimpleContact>().query("id == $0", key).find()
timestamp = System.currentTimeMillis() if(results.isNotEmpty()) {
month = calendar.get(Calendar.MONTH) val result = results.first()
dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH) when(datetime) {
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) UsageUpdateType.JC -> {
hour = calendar.get(Calendar.HOUR_OF_DAY) result.touchCount += 1
}) }
UsageUpdateType.COUNT -> {
result.touchCount += 15
}
UsageUpdateType.DATETIME -> {
result.touchCount += 1
result.lastedTouchDateTime = System.currentTimeMillis()
}
else -> {}
}
}
}
else -> {}
}
if (datetime.equals(UsageUpdateType.JC) == false) {
copyToRealm(AppUsageLog().apply {
itemKey = key
itemType = type.name
timestamp = System.currentTimeMillis()
month = calendar.get(Calendar.MONTH)
dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH)
dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
hour = calendar.get(Calendar.HOUR_OF_DAY)
})
}
} }
} }
// [핵심] 현재 시간 상황에 맞는 추천 리스트 가져오기 // [핵심] 현재 시간 상황에 맞는 추천 리스트 가져오기
fun getContextualRecommendations(limit: Int = 5): List<String> { fun getContextualRecommendations(limit: Int = 8): List<ScoredItem> {
val realm = getRealm() val realm = getRealm()
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
@ -103,13 +152,14 @@ object WorkersDb {
val curHour = calendar.get(Calendar.HOUR_OF_DAY) val curHour = calendar.get(Calendar.HOUR_OF_DAY)
// 최근 3개월 데이터만 조회 (너무 오래된 데이터는 노이즈가 됨) // 최근 3개월 데이터만 조회 (너무 오래된 데이터는 노이즈가 됨)
val threeMonthsAgo = System.currentTimeMillis() - (90L * 24 * 60 * 60 * 1000) val threeMonthsAgo = System.currentTimeMillis() - (365L * 24 * 60 * 60 * 1000)
// 쿼리: 최근 데이터만 가져와서 메모리에서 계산 (복잡한 가중치는 메모리 연산이 빠름) // 쿼리: 최근 데이터만 가져와서 메모리에서 계산 (복잡한 가중치는 메모리 연산이 빠름)
val logs = realm.query<AppUsageLog>("timestamp > $0", threeMonthsAgo).find() val logs = realm.query<AppUsageLog>("timestamp > $0", threeMonthsAgo).find()
// 점수 계산
val scores = HashMap<String, Double>() val scores = HashMap<String, Double>()
val types = HashMap<String, String>()
// 점수 계산
for (log in logs) { for (log in logs) {
var score = 1.0 // 기본 점수 (최근에 썼다는 것 자체로 의미 있음) var score = 1.0 // 기본 점수 (최근에 썼다는 것 자체로 의미 있음)
@ -134,13 +184,17 @@ object WorkersDb {
// 최종 점수 누적 // 최종 점수 누적
val finalScore = score * decay val finalScore = score * decay
scores[log.itemKey] = (scores[log.itemKey] ?: 0.0) + finalScore scores[log.itemKey] = (scores[log.itemKey] ?: 0.0) + finalScore
types[log.itemKey] = log.itemType
} }
// 점수 높은 순으로 정렬하여 상위 N개 반환 // 점수 높은 순으로 정렬하여 상위 N개 반환
return scores.entries return scores.entries
.sortedByDescending { it.value } .sortedByDescending { it.value }
.take(limit) .take(limit)
.map { it.key } .map {
// 키, 타입, 점수를 묶어서 반환
ScoredItem(it.key, types[it.key] ?: "APP", it.value)
}
} }
val schemaVersion : Long = BuildConfig.BuildDateTime val schemaVersion : Long = BuildConfig.BuildDateTime
@ -274,17 +328,62 @@ object WorkersDb {
} }
} }
fun updateAppUse(pkg : String) { fun syncSystemUsageStats(context: Context) {
getRealm().writeBlocking { val usm = context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val result = query<AppInfo>().query("pkgName == $0",pkg).find() val calendar = Calendar.getInstance()
if(result.size > 0) { val endTime = calendar.timeInMillis
val appInfo = result.first() val startTime = endTime - (1000 * 60 * 60 * 24) // 최근 24시간 조회
appInfo.clickCount = appInfo.clickCount + 1
appInfo.lastUseDate = System.currentTimeMillis() // 1. 시스템에서 사용 기록 조회
val usageStatsList = usm.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
startTime,
endTime
)
if (usageStatsList.isNullOrEmpty()) return
val realm = getRealm() // WorkersDb의 getRealm() 활용
realm.writeBlocking {
for (stats in usageStatsList) {
val pkgName = stats.packageName
val lastUsedTime = stats.lastTimeUsed
// 2. 우리 DB(AppInfo)에 저장된 시간보다 최신인지 확인
val appInfo = query<AppInfo>("pkgName == $0", pkgName).first().find()
if (appInfo != null) {
// 시스템 기록이 내 DB 기록보다 더 최신이면 -> 런처 밖에서 실행된 것임!
if (lastUsedTime > appInfo.lastUseDate) {
// A. AppInfo 갱신
appInfo.lastUseDate = lastUsedTime
// (선택) 외부 실행도 카운트에 포함할지 결정
appInfo.clickCount += 1
// B. AppUsageLog에 로그 추가 (외부 실행 로그)
// 주의: 너무 많은 로그가 쌓일 수 있으므로 중복 체크 필요
val existingLog = query<AppUsageLog>(
"itemKey == $0 AND timestamp == $1",
pkgName, lastUsedTime
).find()
if (existingLog.isEmpty()) {
copyToRealm(AppUsageLog().apply {
itemKey = pkgName
itemType = "APP"
timestamp = lastUsedTime
// month, day 등 날짜 정보 계산해서 넣기...
})
}
}
}
} }
} }
} }
fun push(loc: LocationLog) { fun push(loc: LocationLog) {
getRealm().writeBlocking { getRealm().writeBlocking {
try { try {

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#42000000"> <item>
<shape android:shape="oval">
<solid android:color="#F0F0F0"/> </shape>
</item>
</ripple>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="#42FF0000"> <item>
<shape android:shape="oval">
<solid android:color="#F00000"/> </shape>
</item>
</ripple>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M784,840 L532,588q-30,24 -69,38t-83,14q-109,0 -184.5,-75.5T120,380q0,-109 75.5,-184.5T380,120q109,0 184.5,75.5T640,380q0,44 -14,83t-38,69l252,252 -56,56ZM380,560q75,0 127.5,-52.5T560,380q0,-75 -52.5,-127.5T380,200q-75,0 -127.5,52.5T200,380q0,75 52.5,127.5T380,560Z"
android:fillColor="#e3e3e3"/>
</vector>

View File

@ -33,6 +33,7 @@
android:padding="@dimen/eight" android:padding="@dimen/eight"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
/> />
<TextView <TextView
android:id="@+id/lastTouchDate" android:id="@+id/lastTouchDate"
@ -45,6 +46,23 @@
android:padding="@dimen/eight" android:padding="@dimen/eight"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
/> />
<CheckBox
app:layout_constraintTop_toBottomOf="@+id/totalTouch"
android:id="@+id/listVisible"
android:textColor="@color/white"
app:layout_constraintRight_toLeftOf="@id/recommend"
android:text="검색만 보임"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<CheckBox
app:layout_constraintTop_toBottomOf="@+id/totalTouch"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/recommend"
android:textColor="@color/white"
android:text="추천에 제외"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<EditText <EditText
android:inputType="none" android:inputType="none"
android:id="@+id/alterName" android:id="@+id/alterName"

View File

@ -31,7 +31,7 @@
android:singleLine="true" android:singleLine="true"
app:layout_constraintTop_toBottomOf="@id/drag_handle" app:layout_constraintTop_toBottomOf="@id/drag_handle"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/> app:layout_constraintRight_toLeftOf="@+id/search_google"/>
<androidx.appcompat.widget.AppCompatImageButton <androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/reset" android:id="@+id/reset"
@ -46,6 +46,20 @@
app:layout_constraintTop_toTopOf="@id/searchInput" app:layout_constraintTop_toTopOf="@id/searchInput"
app:srcCompat="@drawable/ic_refresh" /> app:srcCompat="@drawable/ic_refresh" />
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/search_google"
android:layout_width="45dp"
android:layout_height="45dp"
android:background="@drawable/rounded_bg"
android:padding="@dimen/eight"
android:tint="@color/white"
android:layout_marginEnd="5dp"
app:layout_constraintBottom_toBottomOf="@id/searchInput"
app:layout_constraintLeft_toRightOf="@+id/searchInput"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/searchInput"
app:srcCompat="@drawable/search" />
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView" android:id="@+id/nestedScrollView"
android:layout_width="0dp" android:layout_width="0dp"
@ -87,7 +101,7 @@
android:id="@+id/recAppsList" android:id="@+id/recAppsList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="100dp" android:minHeight="50dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:overScrollMode="never" android:overScrollMode="never"
android:nestedScrollingEnabled="false" android:nestedScrollingEnabled="false"
@ -140,10 +154,6 @@
android:id="@+id/search_nmap" android:id="@+id/search_nmap"
style="@style/SearchIcons" style="@style/SearchIcons"
android:src="@drawable/navermap"/> android:src="@drawable/navermap"/>
<bums.lunatic.launcher.view.CircleImageView
android:id="@+id/search_google"
style="@style/SearchIcons"
android:src="@drawable/google"/>
<bums.lunatic.launcher.view.CircleImageView <bums.lunatic.launcher.view.CircleImageView
android:id="@+id/search_naver" android:id="@+id/search_naver"
style="@style/SearchIcons" style="@style/SearchIcons"

View File

@ -8,7 +8,6 @@
android:clickable="true" android:clickable="true"
android:focusableInTouchMode="true"> android:focusableInTouchMode="true">
<TextView <TextView
android:id="@+id/appName" android:id="@+id/appName"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -22,6 +21,7 @@
android:padding="@dimen/eight" android:padding="@dimen/eight"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
/> />
<TextView <TextView
android:id="@+id/totalTouch" android:id="@+id/totalTouch"
android:layout_margin="20dp" android:layout_margin="20dp"
@ -34,7 +34,6 @@
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
/> />
<TextView <TextView
android:id="@+id/lastTouchDate" android:id="@+id/lastTouchDate"
android:layout_margin="20dp" android:layout_margin="20dp"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
@ -45,6 +44,23 @@
android:padding="@dimen/eight" android:padding="@dimen/eight"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
/> />
<CheckBox
app:layout_constraintTop_toBottomOf="@+id/totalTouch"
android:id="@+id/listVisible"
android:textColor="@color/white"
app:layout_constraintRight_toLeftOf="@id/recommend"
android:text="검색만 보임"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<CheckBox
app:layout_constraintTop_toBottomOf="@+id/totalTouch"
app:layout_constraintRight_toRightOf="parent"
android:id="@+id/recommend"
android:textColor="@color/white"
android:text="추천에 제외"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView <TextView
android:textSize="30dp" android:textSize="30dp"
android:id="@+id/phoneNumber" android:id="@+id/phoneNumber"
@ -54,7 +70,7 @@
android:layout_marginTop="@dimen/eight" android:layout_marginTop="@dimen/eight"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/totalTouch" /> app:layout_constraintTop_toBottomOf="@+id/listVisible" />
<LinearLayout <LinearLayout
@ -63,38 +79,41 @@
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<bums.lunatic.launcher.view.CircleImageView <TextView
app:civ_circle_background_color="#77FFFFFF" app:autoSizeTextType="uniform"
app:civ_border_width="8dp"
app:civ_border_color="#77FFFFFF"
android:adjustViewBounds="true"
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/detailedInfo" android:id="@+id/detailedInfo"
android:layout_margin="@dimen/eight"
android:src="@drawable/contact"
/>
<bums.lunatic.launcher.view.CircleImageView
app:civ_circle_background_color="#77FFFFFF"
app:civ_border_width="8dp"
app:civ_border_color="#77FFFFFF"
android:adjustViewBounds="true"
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
android:padding="10dp"
android:layout_margin="10dp"
android:text="🗃️"
android:background="@drawable/bg_circle_emoji"
android:gravity="center"
android:visibility="visible"
/>
<TextView
app:autoSizeTextType="uniform"
android:id="@+id/sms" android:id="@+id/sms"
android:layout_margin="@dimen/eight" android:padding="10dp"
android:src="@drawable/message" android:layout_margin="10dp"
/>
<bums.lunatic.launcher.view.CircleImageView
app:civ_circle_background_color="#77FFFFFF"
app:civ_border_width="8dp"
app:civ_border_color="#77FFFFFF"
android:adjustViewBounds="true"
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
android:text="✉️"
android:background="@drawable/bg_circle_emoji"
android:gravity="center"
android:visibility="visible"
/>
<TextView
app:autoSizeTextType="uniform"
android:id="@+id/call" android:id="@+id/call"
android:layout_margin="@dimen/eight" android:padding="10dp"
android:src="@drawable/phonecall" android:layout_margin="10dp"
android:layout_width="80dp"
android:layout_height="80dp"
android:text="📱"
android:background="@drawable/bg_circle_emoji"
android:gravity="center"
android:visibility="visible"
/> />
</LinearLayout> </LinearLayout>

View File

@ -20,86 +20,95 @@
app:layout_constraintEnd_toEndOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<TextView <TextView
app:autoSizeTextType="uniform"
android:id="@+id/delete_zone" android:id="@+id/delete_zone"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:text="삭제" android:text="🗑️"
android:textColor="#FFFFFF" android:background="@drawable/bg_circle_emoji"
android:background="#99FF0000"
android:padding="20dp"
android:gravity="center" android:gravity="center"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="10dp"/> android:layout_marginTop="10dp"/>
<LinearLayout
<androidx.fragment.app.FragmentContainerView android:id="@+id/fragment_layer"
android:id="@+id/fragment_container" android:orientation="vertical"
android:visibility="gone" android:layout_width="match_parent"
android:layout_width="0dp" android:layout_height="match_parent"
android:layout_height="0dp" android:clipChildren="false"
android:visibility="visible"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/current_address"/> app:layout_constraintEnd_toEndOf="parent" >
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:visibility="gone"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/controll_panel"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="40dp">
<ImageButton
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/fragment_container"
app:layout_constraintLeft_toLeftOf="parent"
android:id="@+id/back"
android:src="@drawable/back_vector"
tools:ignore="ContentDescription"
style="@style/CommonBottom" />
<ImageButton
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/fragment_container"
app:layout_constraintLeft_toRightOf="@id/back"
android:id="@+id/reload"
android:src="@drawable/ic_refresh"
tools:ignore="ContentDescription"
style="@style/CommonBottom"/>
<TextView
android:text="asdasdsadasd"
android:layout_weight="1"
android:id="@+id/current_address"
app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintRight_toLeftOf="@id/dl_video"
app:layout_constraintLeft_toRightOf="@id/reload"
android:textColor="@color/white"
android:gravity="center"
android:textSize="@dimen/_12sp"
android:ellipsize="middle"
app:layout_constraintRight_toRightOf="parent"
android:layout_width="0dp"
android:layout_height="@dimen/main_top_height"/>
<ImageButton <ImageButton
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintTop_toBottomOf="@id/fragment_container" app:layout_constraintRight_toLeftOf="@id/share"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintBottom_toBottomOf="parent"
android:id="@+id/back" android:id="@+id/dl_video"
android:visibility="gone" android:src="@drawable/dl_vid"
android:src="@drawable/back_vector" tools:ignore="ContentDescription"
tools:ignore="ContentDescription" style="@style/CommonBottom"/>
style="@style/CommonBottom" /> <ImageButton
<ImageButton app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/fragment_container" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/back" android:layout_marginRight="60dp"
android:id="@+id/reload" android:id="@+id/share"
android:visibility="gone" android:foregroundTint="@color/white"
android:src="@drawable/ic_refresh" android:src="@drawable/ic_share"
tools:ignore="ContentDescription" tools:ignore="ContentDescription"
style="@style/CommonBottom"/> style="@style/CommonBottom"/>
<TextView </LinearLayout>
android:text="asdasdsadasd" </LinearLayout>
android:id="@+id/current_address"
app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintRight_toLeftOf="@id/dl_video"
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"
android:layout_width="0dp"
android:layout_height="@dimen/main_top_height"/>
<ImageButton
app:layout_constraintTop_toTopOf="@id/back"
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"/>
<ImageButton
app:layout_constraintTop_toTopOf="@id/back"
app:layout_constraintRight_toRightOf="parent"
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"
style="@style/CommonBottom"/>
<bums.lunatic.launcher.view.FloatingActionMenu <bums.lunatic.launcher.view.FloatingActionMenu
android:id="@+id/floating_action_menu" android:id="@+id/floating_action_menu"
android:layout_margin="5dp" android:layout_margin="5dp"
android:visibility="gone"
app:menu_colorNormal="#80FF0000" app:menu_colorNormal="#80FF0000"
app:menu_fab_size="mini" app:menu_fab_size="mini"
app:menu_icon="@drawable/ic_add" app:menu_icon="@drawable/ic_add"

View File

@ -17,7 +17,7 @@
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:visibility="visible" android:visibility="visible"
android:background="@null" android:background="@null"
android:src="@drawable/ic_search" android:src="@drawable/search"
android:layout_width="30dp" android:layout_width="30dp"
android:tint="@color/finestSilver" android:tint="@color/finestSilver"
android:foregroundTint="@color/finestSilver" android:foregroundTint="@color/finestSilver"