Feat: Added work profile support with icon.

Signed-off-by: HeCodes2Much <wayne6324@gmail.com>
This commit is contained in:
HeCodes2Much
2024-05-28 21:41:44 +01:00
parent bbe9c98464
commit e61a9894b2
12 changed files with 161 additions and 32 deletions

View File

@@ -14,6 +14,7 @@ import android.graphics.drawable.AdaptiveIconDrawable
import android.net.Uri
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
import android.provider.AlarmClock
import android.provider.CalendarContract
import android.provider.Settings
@@ -297,6 +298,16 @@ fun Context.searchCustomSearchEngine(searchQuery: String? = null): Boolean {
return true
}
fun Context.isWorkProfileEnabled(): Boolean {
val userManager = getSystemService(Context.USER_SERVICE) as? UserManager
return if (userManager != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val profiles = userManager.userProfiles
profiles.size > 1
} else {
false
}
}
fun Context.backupSharedPreferences(backupFileName: String) {
val sharedPreferences: SharedPreferences =
this.getSharedPreferences(Constants.PREFS_FILENAME, 0)

View File

@@ -17,7 +17,7 @@ interface AppInfoDAO {
suspend fun update(app: AppInfo)
@Delete
suspend fun delete(app: AppInfo)
fun delete(app: AppInfo)
@Query("SELECT * FROM app ORDER BY app_name COLLATE NOCASE ASC")
fun getAllApps(): List<AppInfo>
@@ -73,9 +73,12 @@ interface AppInfoDAO {
@Query("SELECT * FROM app WHERE is_favorite = 1")
fun getFavoriteAppInfo(): List<AppInfo>
@Query("SELECT * FROM app WHERE package_name = :packageName")
@Query("SELECT * FROM app WHERE package_name = :packageName AND is_work = 0")
suspend fun getAppByPackageName(packageName: String): AppInfo?
@Query("SELECT * FROM app WHERE package_name = :packageName AND is_work = 1")
suspend fun getAppByPackageNameWork(packageName: String): AppInfo?
private fun logUpdate(message: String, appInfo: AppInfo) {
// You can replace this with a logging library like Timber for more advanced logging capabilities.
Log.d(

View File

@@ -26,8 +26,11 @@ data class AppInfo(
@ColumnInfo(name = "is_lock")
var lock: Boolean,
@ColumnInfo(name = "is_work")
var work: Boolean,
@ColumnInfo(name = "create_time")
var createTime: String = "",
var createTime: String,
@ColumnInfo(name = "app_order")
var appOrder: Int = -1

View File

@@ -85,8 +85,8 @@ internal open class OnSwipeTouchListener(c: Context?) : OnTouchListener {
if (diffY < 0) onSwipeUp() else onSwipeDown()
}
}
} catch (exception: Exception) {
exception.printStackTrace()
} catch (exception: NullPointerException) {
return false
}
return false
}

View File

@@ -3,8 +3,11 @@ package com.github.droidworksstudio.launcher.repository
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.os.Build
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.annotation.RequiresApi
import com.github.droidworksstudio.launcher.Constants
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
import com.github.droidworksstudio.launcher.data.entities.AppInfo
@@ -12,11 +15,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.withContext
import java.lang.reflect.Method
import java.time.LocalDateTime
import javax.inject.Inject
class AppInfoRepository @Inject constructor(
private val appDao: AppInfoDAO
private val appDao: AppInfoDAO,
) {
@Inject
@@ -98,7 +103,9 @@ class AppInfoRepository @Inject constructor(
packages
}
suspend fun initInstalledAppInfo(context: Context): List<AppInfo> = withContext(Dispatchers.IO) {
@RequiresApi(Build.VERSION_CODES.O)
suspend fun initInstalledAppInfo(context: Context): List<AppInfo> =
withContext(Dispatchers.IO) {
val appList: MutableList<AppInfo> = mutableListOf()
val allApps = appDao.getAllAppsFlow().firstOrNull()
@@ -109,29 +116,69 @@ class AppInfoRepository @Inject constructor(
val launcherApps =
context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val excludedPackageNames = mutableListOf(Constants.PACKAGE_NAME,Constants.PACKAGE_NAME_DEBUG)
val excludedPackageNames =
mutableListOf(Constants.PACKAGE_NAME, Constants.PACKAGE_NAME_DEBUG)
val getIdentifierMethod: Method =
UserHandle::class.java.getDeclaredMethod("getIdentifier")
val newAppList: List<AppInfo> = userManager.userProfiles
.flatMap { profile ->
launcherApps.getActivityList(null, profile)
.mapNotNull { app ->
val packageName = app.applicationInfo.packageName
if (packageName !in existingPackageNames && packageName !in excludedPackageNames) {
AppInfo(
appName = app.label.toString(),
packageName = packageName,
favorite = false,
hidden = false,
lock = false
)
} else {
val existingApp = getAppByPackageName(packageName)
existingApp?.let { appList.add(it) }
existingApp
}
// Invoke the getIdentifier method on the UserHandle instance
val userId = getIdentifierMethod.invoke(profile) as Int
when (userId) {
0 -> {
// Handle the case when profile is UserHandle{0}
launcherApps.getActivityList(null, profile)
.mapNotNull { app ->
val packageName = app.applicationInfo.packageName
val currentDateTime = LocalDateTime.now()
if (packageName !in existingPackageNames && packageName !in excludedPackageNames) {
AppInfo(
appName = app.label.toString(),
packageName = packageName,
favorite = false,
hidden = false,
lock = false,
createTime = currentDateTime.toString(),
work = false,
)
} else {
val existingApp = getAppByPackageName(packageName)
existingApp?.let { appList.add(it) }
existingApp
}
}
}
else -> {
// Handle other profiles
launcherApps.getActivityList(null, profile)
.mapNotNull { app ->
val packageName = app.applicationInfo.packageName
val currentDateTime = LocalDateTime.now()
if (packageName !in existingPackageNames && packageName !in excludedPackageNames) {
AppInfo(
appName = app.label.toString(),
packageName = packageName,
favorite = false,
hidden = false,
lock = false,
createTime = currentDateTime.toString(),
work = true,
)
} else {
val existingApp = getAppByPackageNameWork(packageName)
existingApp?.let { appList.add(it) }
existingApp
}
}
}
}
}
appDao.insertAll(newAppList.sortedBy { it.appName })
Log.d("Tag", "State: ${newAppList.sortedBy { it.appName }}")
@@ -142,6 +189,7 @@ class AppInfoRepository @Inject constructor(
appList
}
@RequiresApi(Build.VERSION_CODES.R)
suspend fun compareInstalledApp(): List<AppInfo> = withContext(Dispatchers.IO) {
val installedPackages = getInstalledPackages()
val uninstalledApps = mutableListOf<AppInfo>()
@@ -176,4 +224,8 @@ class AppInfoRepository @Inject constructor(
private suspend fun getAppByPackageName(packageName: String): AppInfo? {
return appDao.getAppByPackageName(packageName)
}
private suspend fun getAppByPackageNameWork(packageName: String): AppInfo? {
return appDao.getAppByPackageNameWork(packageName)
}
}

View File

@@ -1,16 +1,14 @@
package com.github.droidworksstudio.launcher.ui.activities
import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
@@ -19,7 +17,6 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import com.github.droidworksstudio.ktx.isTablet
import com.github.droidworksstudio.launcher.Constants
import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.databinding.ActivityMainBinding
import com.github.droidworksstudio.launcher.helper.AppHelper
@@ -68,6 +65,7 @@ class MainActivity : AppCompatActivity() {
}
@RequiresApi(Build.VERSION_CODES.R)
private fun setupDataBase() {
lifecycleScope.launch {
viewModel.initializeInstalledAppInfo(this@MainActivity)
@@ -118,6 +116,7 @@ class MainActivity : AppCompatActivity() {
|| super.onSupportNavigateUp()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onResume() {
super.onResume()
backToHomeScreen()

View File

@@ -37,7 +37,6 @@ class DrawAdapter(
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val appInfo = getItem(position)
(holder as DrawViewHolder).bind(appInfo)
}

View File

@@ -2,6 +2,7 @@ package com.github.droidworksstudio.launcher.ui.drawer
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
@@ -10,6 +11,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -64,7 +66,13 @@ class DrawFragment : Fragment(),
private val viewModel: AppViewModel by viewModels()
private val drawAdapter: DrawAdapter by lazy { DrawAdapter(this, this, preferenceHelper) }
private val drawAdapter: DrawAdapter by lazy {
DrawAdapter(
this,
this,
preferenceHelper
)
}
private lateinit var context: Context
override fun onCreateView(
@@ -98,6 +106,7 @@ class DrawFragment : Fragment(),
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun observeDrawerApps() {
viewModel.compareInstalledAppInfo()
@@ -242,12 +251,14 @@ class DrawFragment : Fragment(),
binding.searchViewText.hideKeyboard()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onResume() {
super.onResume()
observeDrawerApps()
if (preferenceHelper.automaticKeyboard) binding.searchViewText.showKeyboard()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onStart() {
super.onStart()
observeDrawerApps()

View File

@@ -1,8 +1,14 @@
package com.github.droidworksstudio.launcher.ui.drawer
import android.util.Log
import android.view.Gravity
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.LinearLayoutCompat
import androidx.recyclerview.widget.RecyclerView
import com.github.droidworksstudio.ktx.dpToPx
import com.github.droidworksstudio.ktx.isWorkProfileEnabled
import com.github.droidworksstudio.launcher.R
import com.github.droidworksstudio.launcher.data.entities.AppInfo
import com.github.droidworksstudio.launcher.databinding.ItemDrawBinding
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
@@ -14,7 +20,9 @@ class DrawViewHolder(
private val onAppLongClickedListener: OnItemClickedListener.OnAppLongClickedListener,
private val preferenceHelper: PreferenceHelper
) :
RecyclerView.ViewHolder(binding.root) {
fun bind(appInfo: AppInfo) {
binding.apply {
val layoutParams = LinearLayoutCompat.LayoutParams(
@@ -30,7 +38,28 @@ class DrawViewHolder(
appDrawName.text = appInfo.appName
appDrawName.setTextColor(preferenceHelper.appColor)
appDrawName.textSize = preferenceHelper.appTextSize
Log.d("Tag", "Draw Adapter: ${appInfo.appName + appInfo.id}")
Log.d("Tag", "Draw Adapter: ${appInfo.appName + appInfo.id} | ${appInfo.work}")
val icon = AppCompatResources.getDrawable(appDrawName.context, R.drawable.work_profile)
val px = preferenceHelper.appTextSize.toInt().dpToPx()
icon?.setBounds(0, 0, px, px)
if (appInfo.work) {
val appLabelGravity = preferenceHelper.homeAppAlignment
if (appLabelGravity == Gravity.START) {
appDrawName.setCompoundDrawables(icon, null, null, null)
} else {
appDrawName.setCompoundDrawables(null, null, icon, null)
}
appDrawName.compoundDrawablePadding = 20
if (!appDrawName.context.isWorkProfileEnabled()) {
appDrawName.visibility = View.GONE
}
} else {
// If appInfo.work is false, remove the drawable
appDrawName.setCompoundDrawables(null, null, null, null)
appDrawName.compoundDrawablePadding = 0
}
}
itemView.setOnClickListener {

View File

@@ -1,7 +1,9 @@
package com.github.droidworksstudio.launcher.viewmodel
import android.content.Context
import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.droidworksstudio.launcher.data.entities.AppInfo
@@ -22,47 +24,58 @@ class AppViewModel @Inject constructor(
val drawApps: Flow<List<AppInfo>> = appInfoRepository.getDrawApps().conflate()
val favoriteApps: Flow<List<AppInfo>> = appInfoRepository.getFavoriteApps().conflate()
val hiddenApps: Flow<List<AppInfo>> = appInfoRepository.getHiddenApps().conflate()
@RequiresApi(Build.VERSION_CODES.R)
fun initializeInstalledAppInfo(context: Context) {
viewModelScope.launch {
appInfoRepository.initInstalledAppInfo(context)
}
}
fun updateAppInfoFavorite(appInfo: AppInfo) {
viewModelScope.launch {
appInfoRepository.updateFavoriteAppInfo(appInfo)
Log.d("Tag", "ViewModel Home Order : ${appInfo.appOrder}")
}
}
fun updateAppInfoAppName(appInfo: AppInfo, newAppName: String) {
viewModelScope.launch {
appInfoRepository.updateAppName(appInfo, newAppName)
Log.d("Tag", "ViewModel Home Name : ${appInfo.appName}")
}
}
fun updateAppHidden(appInfo: AppInfo, appHidden: Boolean) {
viewModelScope.launch {
appInfoRepository.updateAppHidden(appInfo, appHidden)
}
}
fun updateAppLock(appInfo: AppInfo, appLock: Boolean) {
viewModelScope.launch {
appInfoRepository.updateAppLock(appInfo, appLock)
}
}
@RequiresApi(Build.VERSION_CODES.R)
fun compareInstalledAppInfo() {
viewModelScope.launch {
appInfoRepository.compareInstalledApp()
}
}
suspend fun updateAppOrder(appInfoList: List<AppInfo>) {
withContext(Dispatchers.IO) {
appInfoRepository.updateAppOrder(appInfoList)
}
}
fun update(appInfo: AppInfo) {
viewModelScope.launch {
appInfoRepository.updateInfo(appInfo)
}
}
fun searchAppInfo(query: String?) = appInfoRepository.searchNote(query)
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40sp"
android:height="40sp"
android:viewportWidth="40"
android:viewportHeight="40">
<path
android:fillColor="@android:color/white"
android:pathData="M6.542,36.292Q4.708,36.292 3.438,35.021Q2.167,33.75 2.167,31.917V13.292Q2.167,11.458 3.438,10.188Q4.708,8.917 6.542,8.917H12.25V5.583Q12.25,3.75 13.521,2.479Q14.792,1.208 16.625,1.208H23.375Q25.208,1.208 26.479,2.479Q27.75,3.75 27.75,5.583V8.917H33.458Q35.292,8.917 36.583,10.188Q37.875,11.458 37.875,13.292V31.917Q37.875,33.75 36.583,35.021Q35.292,36.292 33.458,36.292ZM6.542,31.917H33.458Q33.458,31.917 33.458,31.917Q33.458,31.917 33.458,31.917V13.292Q33.458,13.292 33.458,13.292Q33.458,13.292 33.458,13.292H6.542Q6.542,13.292 6.542,13.292Q6.542,13.292 6.542,13.292V31.917Q6.542,31.917 6.542,31.917Q6.542,31.917 6.542,31.917ZM16.625,8.917H23.375V5.583Q23.375,5.583 23.375,5.583Q23.375,5.583 23.375,5.583H16.625Q16.625,5.583 16.625,5.583Q16.625,5.583 16.625,5.583ZM6.542,31.917Q6.542,31.917 6.542,31.917Q6.542,31.917 6.542,31.917V13.292Q6.542,13.292 6.542,13.292Q6.542,13.292 6.542,13.292Q6.542,13.292 6.542,13.292Q6.542,13.292 6.542,13.292V31.917Q6.542,31.917 6.542,31.917Q6.542,31.917 6.542,31.917Z" />
</vector>

View File

@@ -53,7 +53,7 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/drawAdapter"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/search_view_text" />