Compare commits

...
Message SHA1
Fix Xposed module detection, add more packages to presets (thanks to ShirkNeko)
This version also contains translation updates

frknkrc44   authored and   Cuynu   commited 2025-09-16 23:44:40 +07:00
94fbc8babe
fix: strings.xml: duplicate strings
Signed-off-by: Cuynu <cuynu@proton.me>

Cuynu   commited 2025-09-16 23:44:00 +07:00
53a85adbcd
Add risky package warning with bypass switch
frknkrc44   authored and   Cuynu   commited 2025-09-16 23:35:43 +07:00
3b1fb478ca
Fix a few crashes on search functions, remove useless permissions, update translations
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:49:46 +07:00
4942a02337
Optimize imports, try to add GitHub release support
This is not a code change release, so you can continue to use the previous version.

frknkrc44   authored and   Cuynu   commited 2025-09-16 22:49:35 +07:00
d9a8e0d5a9
Fix "Show presets" display language, fix freezing when loading preset details, update translations
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:49:08 +07:00
7d743b085e
Add "Show presets" option, fix backup/restore config buttons, update translations
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:48:58 +07:00
b51a3f695e
Prevent adding HMA-OSS to the scope, show info text in list the when app/scope list is empty, add working mode indicator to template settings
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:48:51 +07:00
11fc2fc206
Fix crash after editing uninstalled app config, update translations
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:48:41 +07:00
0b5935328e
Reflect indonesian translation changes (#7)
Change-Id: I9b00f45213e37169188aa25be8145840afcd5769

Neebe3289   authored and   Cuynu   commited 2025-09-16 22:47:37 +07:00
add8a2392c
Update Chinese translation (#5)
Chinese translation

idyll   authored and   Cuynu   commited 2025-09-16 22:46:56 +07:00
2302c77782
Revert channel message template to old one, improve multi-user support (WIP)
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:46:37 +07:00
7f21297ac9
Proper PT-BR (Brazilian Portuguese) translation (#2)
DanGLES3   authored and   Cuynu   commited 2025-09-16 22:44:59 +07:00
cb55dea35b
Import Indonesian strings.xml (#3)
Change-Id: Ica5a606007c75096124eab2a16b732e82fc6b334

Neebe3289   authored and   Cuynu   commited 2025-09-16 22:44:03 +07:00
cba451f01c
Update Japanese translation (#4)
* Update ja-rJP translation

* Fix minor Japanese translation

karigane   authored and   Cuynu   commited 2025-09-16 22:43:45 +07:00
fc89fe78ea
Fix signing info bug, simplify PmsHook, add caf and calyxos to custom ROM preset
frknkrc44   authored and   Cuynu   commited 2025-09-16 22:41:43 +07:00
8c1ea074e4
fix: WebViewPreset.kt: missing [,]
my eye issues

Signed-off-by: Cuynu <cuynu@proton.me>

Cuynu   commited 2025-09-08 15:00:10 +07:00
2891fc384f
fix: WebViewPreset.kt: wrong mulch webview packageName
my eye issues

Signed-off-by: Cuynu <cuynu@proton.me>

Cuynu   commited 2025-09-08 14:52:37 +07:00
0dee4195a7
chore: AboutActivity.kt: new fact about myself
this is true tho

Signed-off-by: Cuynu <cuynu@proton.me>

Cuynu   commited 2025-09-08 14:27:19 +07:00
0e10062bb9
Fix ReLSPosed module not activated issue
frknkrc44   authored and   Cuynu   commited 2025-09-08 14:13:34 +07:00
9f1dd0bb36
feat: about: move myself to fake dev category
im not a dev

Signed-off-by: Cuynu <cuynu@proton.me>

Cuynu   commited 2025-09-08 11:55:16 +07:00
91184f643f
feat: WebViewPreset.kt: add support for mulch webview
found on crdroid

Signed-off-by: Cuynu <cuynu@proton.me>

Cuynu   commited 2025-09-08 11:10:03 +07:00
ced2a93b48
49 changed files with 1153 additions and 491 deletions

View file

@ -1,8 +1,3 @@
# Xposed
-keepclassmembers class icu.nullptr.hidemyapplist.MyApp {
boolean isHooked;
}
# Enum class # Enum class
-keepclassmembers,allowoptimization enum * { -keepclassmembers,allowoptimization enum * {
public static **[] values(); public static **[] values();

View file

@ -6,8 +6,6 @@
android:name="android.permission.QUERY_ALL_PACKAGES" android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" /> tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:name="icu.nullptr.hidemyapplist.MyApp" android:name="icu.nullptr.hidemyapplist.MyApp"
android:allowBackup="true" android:allowBackup="true"

View file

@ -17,10 +17,6 @@ import kotlin.system.exitProcess
lateinit var hmaApp: MyApp lateinit var hmaApp: MyApp
class MyApp : Application() { class MyApp : Application() {
@JvmField
var isHooked = false
val globalScope = CoroutineScope(Dispatchers.Default) val globalScope = CoroutineScope(Dispatchers.Default)
val appIconLoader by lazy { val appIconLoader by lazy {
val iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size) val iconSize = resources.getDimensionPixelSize(R.dimen.app_icon_size)

View file

@ -12,6 +12,7 @@ import java.io.File
object ConfigManager { object ConfigManager {
data class TemplateInfo(val name: String?, val isWhiteList: Boolean) data class TemplateInfo(val name: String?, val isWhiteList: Boolean)
data class PresetInfo(val name: String, val translation: String)
private const val TAG = "ConfigManager" private const val TAG = "ConfigManager"
private lateinit var config: JsonConfig private lateinit var config: JsonConfig

View file

@ -19,6 +19,7 @@ object PrefManager {
private const val PREF_THEME_COLOR = "theme_color" private const val PREF_THEME_COLOR = "theme_color"
private const val PREF_HIDE_ICON = "hide_icon" private const val PREF_HIDE_ICON = "hide_icon"
private const val PREF_BYPASS_RISKY_PACKAGE_WARNING = "bypass_risky_package_warning"
private const val PREF_APP_FILTER_SHOW_SYSTEM = "app_filter_show_system" private const val PREF_APP_FILTER_SHOW_SYSTEM = "app_filter_show_system"
private const val PREF_APP_FILTER_SORT_METHOD = "app_filter_sort_method" private const val PREF_APP_FILTER_SORT_METHOD = "app_filter_sort_method"
@ -67,6 +68,10 @@ object PrefManager {
hmaApp.packageManager.setComponentEnabledSetting(component, status, PackageManager.DONT_KILL_APP) hmaApp.packageManager.setComponentEnabledSetting(component, status, PackageManager.DONT_KILL_APP)
} }
var bypassRiskyPackageWarning: Boolean
get() = pref.getBoolean(PREF_BYPASS_RISKY_PACKAGE_WARNING, false)
set(value) = pref.edit().putBoolean(PREF_BYPASS_RISKY_PACKAGE_WARNING, value).apply()
var appFilter_showSystem: Boolean var appFilter_showSystem: Boolean
get() = pref.getBoolean(PREF_APP_FILTER_SHOW_SYSTEM, false) get() = pref.getBoolean(PREF_APP_FILTER_SHOW_SYSTEM, false)
set(value) = pref.edit().putBoolean(PREF_APP_FILTER_SHOW_SYSTEM, value).apply() set(value) = pref.edit().putBoolean(PREF_APP_FILTER_SHOW_SYSTEM, value).apply()

View file

@ -88,6 +88,9 @@ object ServiceClient : IHMAService, IBinder.DeathRecipient {
getServiceLegacy()?.handlePackageEvent(eventType, packageName) getServiceLegacy()?.handlePackageEvent(eventType, packageName)
} }
override fun getPackagesForPreset(presetName: String) =
getServiceLegacy()?.getPackagesForPreset(presetName)
override fun syncConfig(json: String) { override fun syncConfig(json: String) {
getServiceLegacy()?.syncConfig(json) getServiceLegacy()?.syncConfig(json)
} }

View file

@ -49,7 +49,10 @@ class AboutActivity : AbsAboutActivity() {
items.add(Contributor(R.drawable.cont_icon_designer, "辉少菌", "Icon designer", "http://www.coolapk.com/u/1560270")) items.add(Contributor(R.drawable.cont_icon_designer, "辉少菌", "Icon designer", "http://www.coolapk.com/u/1560270"))
items.add(Line()) items.add(Line())
items.add(Contributor(R.drawable.cont_cpp_master, "LoveSy", "Idea provider", "https://github.com/yujincheng08")) items.add(Contributor(R.drawable.cont_cpp_master, "LoveSy", "Idea provider", "https://github.com/yujincheng08"))
items.add(Contributor(R.drawable.cont_cuynu, "Cuynu", "loser?", "https://git.disroot.org/cuynu"))
items.add(Category(getString(R.string.about_fakedev)))
items.add(Contributor(R.drawable.cont_cuynu, "Cuynu", "another loser with no coding skills", "https://git.disroot.org/cuynu"))
items.add(Category(getString(R.string.about_support))) items.add(Category(getString(R.string.about_support)))
items.add(Card("Disroot\nhttps://git.disroot.org/cuynu/HMAL-OSS")) items.add(Card("Disroot\nhttps://git.disroot.org/cuynu/HMAL-OSS"))

View file

@ -1,16 +1,35 @@
package icu.nullptr.hidemyapplist.ui.adapter package icu.nullptr.hidemyapplist.ui.adapter
import android.view.ViewGroup import android.view.ViewGroup
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import icu.nullptr.hidemyapplist.common.Constants.GMS_PACKAGE_NAME
import icu.nullptr.hidemyapplist.common.Constants.GSF_PACKAGE_NAME
import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.service.ConfigManager
import icu.nullptr.hidemyapplist.service.PrefManager
import icu.nullptr.hidemyapplist.ui.view.AppItemView import icu.nullptr.hidemyapplist.ui.view.AppItemView
import org.frknkrc44.hma_oss.R
class AppManageAdapter( class AppManageAdapter(
private val onItemClickListener: (String) -> Unit private val onItemClickListener: (String) -> Unit
) : AppSelectAdapter() { ) : AppSelectAdapter() {
private val riskyPackages = arrayOf(GMS_PACKAGE_NAME, GSF_PACKAGE_NAME)
inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) { inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) {
init { init {
view.setOnClickListener { view.setOnClickListener {
if (!PrefManager.bypassRiskyPackageWarning && riskyPackages.contains(view.binding.packageName.text)) {
MaterialAlertDialogBuilder(view.context)
.setTitle(R.string.app_warning_risky_package_title)
.setMessage(R.string.app_warning_risky_package_desc)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(android.R.string.ok) { _, _ ->
onItemClickListener.invoke(filteredList[absoluteAdapterPosition])
}
.show()
return@setOnClickListener
}
onItemClickListener.invoke(filteredList[absoluteAdapterPosition]) onItemClickListener.invoke(filteredList[absoluteAdapterPosition])
} }
} }

View file

@ -8,6 +8,7 @@ import icu.nullptr.hidemyapplist.ui.view.AppItemView
import icu.nullptr.hidemyapplist.util.PackageHelper import icu.nullptr.hidemyapplist.util.PackageHelper
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.frknkrc44.hma_oss.BuildConfig
abstract class AppSelectAdapter( abstract class AppSelectAdapter(
private val firstFilter: ((String) -> Boolean)? = null private val firstFilter: ((String) -> Boolean)? = null
@ -20,13 +21,15 @@ abstract class AppSelectAdapter(
private inner class AppFilter : Filter() { private inner class AppFilter : Filter() {
override fun performFiltering(constraint: CharSequence): FilterResults { override fun performFiltering(constraint: CharSequence): FilterResults {
return runBlocking { return runBlocking {
val constraintLowered = constraint.toString().lowercase() val constraintLowered = constraint.toString().trim().lowercase()
val filteredList = PackageHelper.appList.first().filter { val filteredList = PackageHelper.appList.first().filter {
if (firstFilter?.invoke(it) == false) return@filter false if (firstFilter?.invoke(it) == false) return@filter false
if (!PrefManager.appFilter_showSystem && PackageHelper.isSystem(it)) return@filter false if (!PrefManager.appFilter_showSystem && PackageHelper.isSystem(it)) return@filter false
if (it == BuildConfig.APPLICATION_ID &&
(this@AppSelectAdapter.javaClass == AppManageAdapter::class.java ||
(this@AppSelectAdapter.javaClass == AppScopeAdapter::class.java && firstFilter != null))) return@filter false
val label = PackageHelper.loadAppLabel(it) val label = PackageHelper.loadAppLabel(it)
val packageInfo = PackageHelper.loadPackageInfo(it) label.lowercase().contains(constraintLowered) || it.lowercase().contains(constraintLowered)
label.lowercase().contains(constraintLowered) || packageInfo.packageName.lowercase().contains(constraintLowered)
} }
FilterResults().also { it.values = filteredList } FilterResults().also { it.values = filteredList }
@ -35,8 +38,11 @@ abstract class AppSelectAdapter(
@Suppress("UNCHECKED_CAST", "NotifyDataSetChanged") @Suppress("UNCHECKED_CAST", "NotifyDataSetChanged")
override fun publishResults(constraint: CharSequence, results: FilterResults) { override fun publishResults(constraint: CharSequence, results: FilterResults) {
filteredList = results.values as List<String> val values = results.values
notifyDataSetChanged() if (values != null) {
filteredList = values as List<String>
notifyDataSetChanged()
}
} }
} }

View file

@ -5,12 +5,14 @@ import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowInsets import android.view.WindowInsets
import android.widget.TextView
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import icu.nullptr.hidemyapplist.service.PrefManager import icu.nullptr.hidemyapplist.service.PrefManager
import icu.nullptr.hidemyapplist.ui.adapter.AppSelectAdapter import icu.nullptr.hidemyapplist.ui.adapter.AppSelectAdapter
@ -20,10 +22,11 @@ import icu.nullptr.hidemyapplist.util.PackageHelper
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.frknkrc44.hma_oss.R import org.frknkrc44.hma_oss.R
import org.frknkrc44.hma_oss.databinding.FragmentAppSelectBinding import org.frknkrc44.hma_oss.databinding.FragmentAppSelectBinding
import org.frknkrc44.hma_oss.ui.fragment.AppPresetFragment
abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) { abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
private val binding by viewBinding<FragmentAppSelectBinding>() protected val binding by viewBinding<FragmentAppSelectBinding>()
protected abstract val firstComparator: Comparator<String> protected abstract val firstComparator: Comparator<String>
protected abstract val adapter: AppSelectAdapter protected abstract val adapter: AppSelectAdapter
@ -34,17 +37,21 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
navController.navigateUp() navController.navigateUp()
} }
private fun applyFilter() { protected fun applyFilter() {
adapter.filter.filter(search) adapter.filter.filter(search)
} }
private fun sortList() { protected open fun sortList() {
lifecycleScope.launch { lifecycleScope.launch {
PackageHelper.sortList(firstComparator) PackageHelper.sortList(firstComparator)
applyFilter() applyFilter()
} }
} }
open fun invalidateCache() {
PackageHelper.invalidateCache()
}
private fun onMenuOptionSelected(item: MenuItem) { private fun onMenuOptionSelected(item: MenuItem) {
when (item.itemId) { when (item.itemId) {
R.id.menu_show_system -> { R.id.menu_show_system -> {
@ -75,11 +82,13 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
sortList() sortList()
} }
open fun getFragmentTitle() = getString(R.string.title_app_select)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() } requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() }
setupToolbar( setupToolbar(
toolbar = binding.toolbar, toolbar = binding.toolbar,
title = getString(R.string.title_app_select), title = getFragmentTitle(),
navigationIcon = R.drawable.baseline_arrow_back_24, navigationIcon = R.drawable.baseline_arrow_back_24,
navigationOnClick = { onBack() }, navigationOnClick = { onBack() },
menuRes = R.menu.menu_app_list, menuRes = R.menu.menu_app_list,
@ -110,9 +119,16 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
binding.list.layoutManager = LinearLayoutManager(context) binding.list.layoutManager = LinearLayoutManager(context)
binding.list.adapter = adapter binding.list.adapter = adapter
binding.swipeRefresh.setOnRefreshListener { binding.swipeRefresh.setOnRefreshListener {
PackageHelper.invalidateCache() invalidateCache()
} }
adapter.registerAdapterDataObserver(
EmptyDataObserver(
binding.list,
binding.listEmptyContainer
)
)
lifecycleScope.launch { lifecycleScope.launch {
PackageHelper.isRefreshing PackageHelper.isRefreshing
.flowWithLifecycle(lifecycle) .flowWithLifecycle(lifecycle)
@ -145,4 +161,44 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
insets insets
} }
} }
// Credit: https://medium.com/nerd-for-tech/empty-dataset-in-recyclerview-ad86833dd5c6
inner class EmptyDataObserver(
private val recyclerView: RecyclerView,
private val emptyView: View
): RecyclerView.AdapterDataObserver() {
private val fragmentType by lazy {
when(this@AppSelectFragment.javaClass) {
ScopeFragment::class.java -> 0
AppPresetFragment::class.java -> 1
else -> 2
}
}
val atLeastEntryCount: Int get() {
if (!search.isBlank()) return 0
return if (fragmentType < 2) 1 else 2
}
private fun checkIfEmpty() {
val emptyViewVisible = recyclerView.adapter!!.itemCount < atLeastEntryCount
emptyView.visibility = if (emptyViewVisible) View.VISIBLE else View.GONE
if (emptyViewVisible) {
emptyView.findViewById<TextView>(R.id.list_empty_text).text = getString(
when (fragmentType) {
0 -> R.string.list_empty_no_enabled
1 -> R.string.list_empty_preset
else -> R.string.list_empty_no_apps
}
)
}
recyclerView.visibility = if (emptyViewVisible) View.GONE else View.VISIBLE
}
override fun onChanged() {
super.onChanged()
checkIfEmpty()
}
}
} }

View file

@ -97,6 +97,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen
"blackDarkTheme" -> PrefManager.blackDarkTheme "blackDarkTheme" -> PrefManager.blackDarkTheme
"detailLog" -> ConfigManager.detailLog "detailLog" -> ConfigManager.detailLog
"hideIcon" -> PrefManager.hideIcon "hideIcon" -> PrefManager.hideIcon
"bypassRiskyPackageWarning" -> PrefManager.bypassRiskyPackageWarning
"appDataIsolation" -> CommonUtils.isAppDataIsolationEnabled "appDataIsolation" -> CommonUtils.isAppDataIsolationEnabled
"voldAppDataIsolation" -> CommonUtils.isVoldAppDataIsolationEnabled "voldAppDataIsolation" -> CommonUtils.isVoldAppDataIsolationEnabled
"forceMountData" -> ConfigManager.forceMountData "forceMountData" -> ConfigManager.forceMountData
@ -121,6 +122,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen
"detailLog" -> ConfigManager.detailLog = value "detailLog" -> ConfigManager.detailLog = value
"forceMountData" -> ConfigManager.forceMountData = value "forceMountData" -> ConfigManager.forceMountData = value
"hideIcon" -> PrefManager.hideIcon = value "hideIcon" -> PrefManager.hideIcon = value
"bypassRiskyPackageWarning" -> PrefManager.bypassRiskyPackageWarning = value
"appDataIsolation" -> Unit "appDataIsolation" -> Unit
"voldAppDataIsolation" -> Unit "voldAppDataIsolation" -> Unit
else -> throw IllegalArgumentException("Invalid key: $key") else -> throw IllegalArgumentException("Invalid key: $key")
@ -267,6 +269,11 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen
configureDataIsolation() configureDataIsolation()
findPreference<SwitchPreferenceCompat>("bypassRiskyPackageWarning")?.setOnPreferenceChangeListener { _, newValue ->
activity?.recreate()
true
}
findPreference<Preference>("stopSystemService")?.setOnPreferenceClickListener { findPreference<Preference>("stopSystemService")?.setOnPreferenceClickListener {
if (ServiceClient.serviceVersion != 0) { if (ServiceClient.serviceVersion != 0) {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())

View file

@ -1,6 +1,5 @@
package icu.nullptr.hidemyapplist.ui.fragment package icu.nullptr.hidemyapplist.ui.fragment
import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
@ -12,7 +11,6 @@ import androidx.fragment.app.setFragmentResultListener
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import by.kirich1409.viewbindingdelegate.viewBinding import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.transition.MaterialContainerTransform
import icu.nullptr.hidemyapplist.common.JsonConfig import icu.nullptr.hidemyapplist.common.JsonConfig
import icu.nullptr.hidemyapplist.service.ConfigManager import icu.nullptr.hidemyapplist.service.ConfigManager
import icu.nullptr.hidemyapplist.ui.adapter.TemplateAdapter import icu.nullptr.hidemyapplist.ui.adapter.TemplateAdapter
@ -26,14 +24,6 @@ class TemplateManageFragment : Fragment(R.layout.fragment_template_manage) {
private val binding by viewBinding<FragmentTemplateManageBinding>() private val binding by viewBinding<FragmentTemplateManageBinding>()
private val adapter = TemplateAdapter(this::navigateToSettings) private val adapter = TemplateAdapter(this::navigateToSettings)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.nav_host_fragment
scrimColor = Color.TRANSPARENT
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupToolbar( setupToolbar(
toolbar = binding.toolbar, toolbar = binding.toolbar,

View file

@ -56,16 +56,6 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) {
navController.navigateUp() navController.navigateUp()
} }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*
sharedElementEnterTransition = MaterialContainerTransform().apply {
drawingViewId = R.id.nav_host_fragment
scrimColor = Color.TRANSPARENT
}
*/
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack(false) } requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack(false) }
setupToolbar( setupToolbar(
@ -80,6 +70,7 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) {
) )
binding.templateName.setText(viewModel.name) binding.templateName.setText(viewModel.name)
binding.workMode.setText(if (viewModel.isWhiteList) R.string.whitelist else R.string.blacklist)
binding.templateName.addTextChangedListener { viewModel.name = it.toString() } binding.templateName.addTextChangedListener { viewModel.name = it.toString() }
binding.targetApps.setOnClickListener { binding.targetApps.setOnClickListener {
setFragmentResultListener("app_select") { _, bundle -> setFragmentResultListener("app_select") { _, bundle ->

View file

@ -15,7 +15,7 @@ class AppItemView @JvmOverloads constructor(
defStyleRes: Int = 0 defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) { ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
private val binding by viewBinding<AppItemViewBinding>(createMethod = CreateMethod.INFLATE) val binding by viewBinding<AppItemViewBinding>(createMethod = CreateMethod.INFLATE)
var showEnabled: Boolean var showEnabled: Boolean
get() = binding.enabled.visibility == VISIBLE get() = binding.enabled.visibility == VISIBLE
@ -35,7 +35,12 @@ class AppItemView @JvmOverloads constructor(
fun load(packageName: String) { fun load(packageName: String) {
binding.packageName.text = packageName binding.packageName.text = packageName
binding.label.text = PackageHelper.loadAppLabel(packageName) try {
binding.icon.setImageBitmap(PackageHelper.loadAppIcon(packageName)) binding.label.text = PackageHelper.loadAppLabel(packageName)
binding.icon.setImageBitmap(PackageHelper.loadAppIcon(packageName))
} catch (e: Throwable) {
binding.label.text = packageName
binding.icon.setImageResource(android.R.drawable.sym_def_app_icon)
}
} }
} }

View file

@ -26,11 +26,15 @@ object PackageHelper {
val icon: Bitmap val icon: Bitmap
) )
private object Comparators { object Comparators {
val byLabel = Comparator<String> { o1, o2 -> val byLabel = Comparator<String> { o1, o2 ->
val n1 = loadAppLabel(o1).lowercase(Locale.getDefault()) try {
val n2 = loadAppLabel(o2).lowercase(Locale.getDefault()) val n1 = loadAppLabel(o1).lowercase(Locale.getDefault())
Collator.getInstance(Locale.getDefault()).compare(n1, n2) val n2 = loadAppLabel(o2).lowercase(Locale.getDefault())
Collator.getInstance(Locale.getDefault()).compare(n1, n2)
} catch (_: Throwable) {
byPackageName.compare(o1, o2)
}
} }
val byPackageName = Comparator<String> { o1, o2 -> val byPackageName = Comparator<String> { o1, o2 ->
val n1 = o1.lowercase(Locale.getDefault()) val n1 = o1.lowercase(Locale.getDefault())
@ -38,14 +42,22 @@ object PackageHelper {
Collator.getInstance(Locale.getDefault()).compare(n1, n2) Collator.getInstance(Locale.getDefault()).compare(n1, n2)
} }
val byInstallTime = Comparator<String> { o1, o2 -> val byInstallTime = Comparator<String> { o1, o2 ->
val n1 = loadPackageInfo(o1).firstInstallTime try {
val n2 = loadPackageInfo(o2).firstInstallTime val n1 = loadPackageInfo(o1).firstInstallTime
n2.compareTo(n1) val n2 = loadPackageInfo(o2).firstInstallTime
n2.compareTo(n1)
} catch (_: Throwable) {
byPackageName.compare(o1, o2)
}
} }
val byUpdateTime = Comparator<String> { o1, o2 -> val byUpdateTime = Comparator<String> { o1, o2 ->
val n1 = loadPackageInfo(o1).lastUpdateTime try {
val n2 = loadPackageInfo(o2).lastUpdateTime val n1 = loadPackageInfo(o1).lastUpdateTime
n2.compareTo(n1) val n2 = loadPackageInfo(o2).lastUpdateTime
n2.compareTo(n1)
} catch (_: Throwable) {
byPackageName.compare(o1, o2)
}
} }
} }
@ -99,6 +111,10 @@ object PackageHelper {
mAppList.emit(list) mAppList.emit(list)
} }
fun exists(packageName: String) = runBlocking {
packageCache.first().contains(packageName)
}
fun loadPackageInfo(packageName: String): PackageInfo = runBlocking { fun loadPackageInfo(packageName: String): PackageInfo = runBlocking {
packageCache.first()[packageName]!!.info packageCache.first()[packageName]!!.info
} }

View file

@ -0,0 +1,74 @@
package org.frknkrc44.hma_oss.ui.adapter
import android.view.ViewGroup
import android.widget.Filter
import icu.nullptr.hidemyapplist.service.ServiceClient
import icu.nullptr.hidemyapplist.ui.adapter.AppSelectAdapter
import icu.nullptr.hidemyapplist.ui.view.AppItemView
import icu.nullptr.hidemyapplist.util.PackageHelper
import kotlinx.coroutines.runBlocking
class AppPresetAdapter(
private val presetName: String
) : AppSelectAdapter() {
var packages = mutableListOf<String>()
fun updateList() {
packages.clear()
packages += ServiceClient.getPackagesForPreset(presetName)?.toList() ?: listOf()
}
inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) {
override fun bind(packageName: String) {
(itemView as AppItemView).let {
it.load(packageName)
it.alpha = if (!PackageHelper.exists(packageName)) 0.5f else 1.0f
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = AppItemView(parent.context, false)
view.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
return ViewHolder(view)
}
private inner class PresetFilter : Filter() {
override fun performFiltering(constraint: CharSequence): FilterResults {
return runBlocking {
val constraintLowered = constraint.toString().trim().lowercase()
val filteredList = packages.filter {
if (constraintLowered.isEmpty()) return@filter true
if (it.lowercase().contains(constraintLowered)) return@filter true
try {
val label = PackageHelper.loadAppLabel(it)
return@filter label.lowercase().contains(constraintLowered)
} catch (e: Throwable) {
return@filter false
}
}
FilterResults().also { it.values = filteredList }
}
}
@Suppress("UNCHECKED_CAST", "NotifyDataSetChanged")
override fun publishResults(constraint: CharSequence, results: FilterResults) {
val values = results.values
if (values != null) {
filteredList = values as List<String>
notifyDataSetChanged()
}
}
}
private val mFilter = PresetFilter()
override fun getFilter(): Filter = mFilter
}

View file

@ -0,0 +1,76 @@
package org.frknkrc44.hma_oss.ui.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import icu.nullptr.hidemyapplist.common.AppPresets
import icu.nullptr.hidemyapplist.service.ConfigManager
import icu.nullptr.hidemyapplist.ui.view.ListItemView
import org.frknkrc44.hma_oss.BuildConfig
import org.frknkrc44.hma_oss.R
class AppPresetListAdapter(
context: Context,
private val onClickListener: ((ConfigManager.PresetInfo) -> Unit)?
) : RecyclerView.Adapter<AppPresetListAdapter.ViewHolder>() {
private lateinit var list: List<ConfigManager.PresetInfo>
init {
updateList(context)
}
inner class ViewHolder(view: ListItemView) : RecyclerView.ViewHolder(view) {
init {
view.setOnClickListener {
onClickListener?.invoke(list[absoluteAdapterPosition])
}
}
fun bind(presetName: String) {
with(itemView as ListItemView) {
setIcon(R.drawable.baseline_assignment_24)
text = presetName
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = ListItemView(parent.context)
view.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
return ViewHolder(view)
}
override fun getItemCount() = list.size
override fun getItemId(position: Int) = list[position].name.hashCode().toLong()
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(list[position].translation)
@SuppressLint("NotifyDataSetChanged", "DiscouragedApi")
private fun updateList(context: Context) {
val presetNames = AppPresets.instance.getAllPresetNames()
val presetTranslations = presetNames.map { name ->
try {
val id = context.resources.getIdentifier(
"preset_${name}",
"string",
BuildConfig.APPLICATION_ID
)
return@map if (id != 0) { context.resources.getString(id) } else { name }
} catch (_: Throwable) {}
name
}
list = presetNames
.map { ConfigManager.PresetInfo(it, presetTranslations[presetNames.indexOf(it)]) }
.sortedWith { a, b -> a.translation.lowercase().compareTo(b.translation.lowercase()) }
notifyDataSetChanged()
}
}

View file

@ -0,0 +1,70 @@
package org.frknkrc44.hma_oss.ui.fragment
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import icu.nullptr.hidemyapplist.hmaApp
import icu.nullptr.hidemyapplist.service.PrefManager
import icu.nullptr.hidemyapplist.ui.fragment.AppSelectFragment
import icu.nullptr.hidemyapplist.util.PackageHelper
import icu.nullptr.hidemyapplist.util.PackageHelper.Comparators
import kotlinx.coroutines.launch
import org.frknkrc44.hma_oss.ui.adapter.AppPresetAdapter
class AppPresetFragment() : AppSelectFragment() {
override val firstComparator: Comparator<String> = Comparator.comparing(PackageHelper::exists).reversed()
override val adapter by lazy {
val args by navArgs<AppPresetFragmentArgs>()
AppPresetAdapter(args.presetName)
}
override fun getFragmentTitle(): String {
val args by navArgs<AppPresetFragmentArgs>()
return args.presetTitle
}
override fun sortList() {
hmaApp.globalScope.launch {
sortPresetList()
lifecycleScope.launch {
applyFilter()
}
}
}
fun sortPresetList() {
var comparator = when (PrefManager.appFilter_sortMethod) {
PrefManager.SortMethod.BY_LABEL -> Comparators.byLabel
PrefManager.SortMethod.BY_PACKAGE_NAME -> Comparators.byPackageName
PrefManager.SortMethod.BY_INSTALL_TIME -> Comparators.byInstallTime
PrefManager.SortMethod.BY_UPDATE_TIME -> Comparators.byUpdateTime
}
if (PrefManager.appFilter_reverseOrder) comparator = comparator.reversed()
val packages = adapter.packages.sortedWith(firstComparator.then(comparator))
lifecycleScope.launch {
adapter.packages.clear()
adapter.packages += packages
}
}
override fun invalidateCache() {
super.invalidateCache()
adapter.updateList()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
binding.swipeRefresh.isRefreshing = true
adapter.updateList()
sortList()
}
}
}

View file

@ -116,7 +116,6 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
val serviceVersion = ServiceClient.serviceVersion val serviceVersion = ServiceClient.serviceVersion
val color = when { val color = when {
!hmaApp.isHooked -> getColor(R.color.gray)
serviceVersion == 0 -> getColor(R.color.invalid) serviceVersion == 0 -> getColor(R.color.invalid)
else -> themeColor(android.R.attr.colorPrimary) else -> themeColor(android.R.attr.colorPrimary)
} }
@ -126,7 +125,7 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
root.outlineAmbientShadowColor = color root.outlineAmbientShadowColor = color
root.outlineSpotShadowColor = color root.outlineSpotShadowColor = color
if (hmaApp.isHooked) { if (serviceVersion > 0) {
moduleStatusIcon.setImageResource(R.drawable.sentiment_calm_24px) moduleStatusIcon.setImageResource(R.drawable.sentiment_calm_24px)
val versionNameSimple = BuildConfig.VERSION_NAME.substringBefore(".r") val versionNameSimple = BuildConfig.VERSION_NAME.substringBefore(".r")
moduleStatus.text = moduleStatus.text =
@ -268,6 +267,14 @@ class HomeFragment : Fragment(R.layout.fragment_home) {
} }
} }
with(binding.managePresets) {
text1.text = getString(R.string.title_preset_manage)
icon.setImageResource(R.drawable.baseline_my_location_24)
root.setOnClickListener {
navigate(R.id.nav_presets)
}
}
with(binding.navLogs) { with(binding.navLogs) {
text1.text = getString(R.string.title_logs) text1.text = getString(R.string.title_logs)
icon.setImageResource(R.drawable.outline_assignment_24) icon.setImageResource(R.drawable.outline_assignment_24)

View file

@ -0,0 +1,65 @@
package org.frknkrc44.hma_oss.ui.fragment
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.WindowInsets
import androidx.core.view.doOnPreDraw
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import by.kirich1409.viewbindingdelegate.viewBinding
import icu.nullptr.hidemyapplist.service.ConfigManager
import icu.nullptr.hidemyapplist.ui.util.navController
import icu.nullptr.hidemyapplist.ui.util.setupToolbar
import org.frknkrc44.hma_oss.R
import org.frknkrc44.hma_oss.databinding.FragmentPresetManageBinding
import org.frknkrc44.hma_oss.ui.adapter.AppPresetListAdapter
class PresetManageFragment : Fragment(R.layout.fragment_preset_manage) {
private val binding by viewBinding<FragmentPresetManageBinding>()
private val adapter by lazy {
AppPresetListAdapter(requireContext(), this::navigateToPreset)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
setupToolbar(
toolbar = binding.toolbar,
title = getString(R.string.title_preset_manage),
navigationIcon = R.drawable.baseline_arrow_back_24,
navigationOnClick = { navController.navigateUp() }
)
postponeEnterTransition()
view.doOnPreDraw { startPostponedEnterTransition() }
binding.presetList.layoutManager = LinearLayoutManager(context)
binding.presetList.adapter = adapter
binding.root.setOnApplyWindowInsetsListener { v, insets ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val barInsets = insets.getInsets(WindowInsets.Type.systemBars())
binding.root.setPadding(
barInsets.left,
barInsets.top,
barInsets.right,
barInsets.bottom,
)
} else {
@Suppress("deprecation")
binding.root.setPadding(
insets.systemWindowInsetLeft,
insets.systemWindowInsetTop,
insets.systemWindowInsetRight,
insets.systemWindowInsetBottom,
)
}
insets
}
}
private fun navigateToPreset(presetInfo: ConfigManager.PresetInfo) {
val args = AppPresetFragmentArgs(presetInfo.name, presetInfo.translation)
navController.navigate(R.id.nav_preset_inner_manage, args.toBundle())
}
}

Some files were not shown because too many files have changed in this diff Show more