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
44 changed files with 1141 additions and 473 deletions

View file

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

View file

@ -12,6 +12,7 @@ import java.io.File
object ConfigManager {
data class TemplateInfo(val name: String?, val isWhiteList: Boolean)
data class PresetInfo(val name: String, val translation: String)
private const val TAG = "ConfigManager"
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_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_SORT_METHOD = "app_filter_sort_method"
@ -67,6 +68,10 @@ object PrefManager {
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
get() = pref.getBoolean(PREF_APP_FILTER_SHOW_SYSTEM, false)
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)
}
override fun getPackagesForPreset(presetName: String) =
getServiceLegacy()?.getPackagesForPreset(presetName)
override fun syncConfig(json: String) {
getServiceLegacy()?.syncConfig(json)
}

View file

@ -1,16 +1,35 @@
package icu.nullptr.hidemyapplist.ui.adapter
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.PrefManager
import icu.nullptr.hidemyapplist.ui.view.AppItemView
import org.frknkrc44.hma_oss.R
class AppManageAdapter(
private val onItemClickListener: (String) -> Unit
) : AppSelectAdapter() {
private val riskyPackages = arrayOf(GMS_PACKAGE_NAME, GSF_PACKAGE_NAME)
inner class ViewHolder(view: AppItemView) : AppSelectAdapter.ViewHolder(view) {
init {
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])
}
}

View file

@ -8,6 +8,7 @@ import icu.nullptr.hidemyapplist.ui.view.AppItemView
import icu.nullptr.hidemyapplist.util.PackageHelper
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.frknkrc44.hma_oss.BuildConfig
abstract class AppSelectAdapter(
private val firstFilter: ((String) -> Boolean)? = null
@ -20,13 +21,15 @@ abstract class AppSelectAdapter(
private inner class AppFilter : Filter() {
override fun performFiltering(constraint: CharSequence): FilterResults {
return runBlocking {
val constraintLowered = constraint.toString().lowercase()
val constraintLowered = constraint.toString().trim().lowercase()
val filteredList = PackageHelper.appList.first().filter {
if (firstFilter?.invoke(it) == false) 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 packageInfo = PackageHelper.loadPackageInfo(it)
label.lowercase().contains(constraintLowered) || packageInfo.packageName.lowercase().contains(constraintLowered)
label.lowercase().contains(constraintLowered) || it.lowercase().contains(constraintLowered)
}
FilterResults().also { it.values = filteredList }
@ -35,8 +38,11 @@ abstract class AppSelectAdapter(
@Suppress("UNCHECKED_CAST", "NotifyDataSetChanged")
override fun publishResults(constraint: CharSequence, results: FilterResults) {
filteredList = results.values as List<String>
notifyDataSetChanged()
val values = results.values
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.View
import android.view.WindowInsets
import android.widget.TextView
import androidx.activity.addCallback
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import by.kirich1409.viewbindingdelegate.viewBinding
import icu.nullptr.hidemyapplist.service.PrefManager
import icu.nullptr.hidemyapplist.ui.adapter.AppSelectAdapter
@ -20,10 +22,11 @@ import icu.nullptr.hidemyapplist.util.PackageHelper
import kotlinx.coroutines.launch
import org.frknkrc44.hma_oss.R
import org.frknkrc44.hma_oss.databinding.FragmentAppSelectBinding
import org.frknkrc44.hma_oss.ui.fragment.AppPresetFragment
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 adapter: AppSelectAdapter
@ -34,17 +37,21 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
navController.navigateUp()
}
private fun applyFilter() {
protected fun applyFilter() {
adapter.filter.filter(search)
}
private fun sortList() {
protected open fun sortList() {
lifecycleScope.launch {
PackageHelper.sortList(firstComparator)
applyFilter()
}
}
open fun invalidateCache() {
PackageHelper.invalidateCache()
}
private fun onMenuOptionSelected(item: MenuItem) {
when (item.itemId) {
R.id.menu_show_system -> {
@ -75,11 +82,13 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
sortList()
}
open fun getFragmentTitle() = getString(R.string.title_app_select)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack() }
setupToolbar(
toolbar = binding.toolbar,
title = getString(R.string.title_app_select),
title = getFragmentTitle(),
navigationIcon = R.drawable.baseline_arrow_back_24,
navigationOnClick = { onBack() },
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.adapter = adapter
binding.swipeRefresh.setOnRefreshListener {
PackageHelper.invalidateCache()
invalidateCache()
}
adapter.registerAdapterDataObserver(
EmptyDataObserver(
binding.list,
binding.listEmptyContainer
)
)
lifecycleScope.launch {
PackageHelper.isRefreshing
.flowWithLifecycle(lifecycle)
@ -145,4 +161,44 @@ abstract class AppSelectFragment : Fragment(R.layout.fragment_app_select) {
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
"detailLog" -> ConfigManager.detailLog
"hideIcon" -> PrefManager.hideIcon
"bypassRiskyPackageWarning" -> PrefManager.bypassRiskyPackageWarning
"appDataIsolation" -> CommonUtils.isAppDataIsolationEnabled
"voldAppDataIsolation" -> CommonUtils.isVoldAppDataIsolationEnabled
"forceMountData" -> ConfigManager.forceMountData
@ -121,6 +122,7 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen
"detailLog" -> ConfigManager.detailLog = value
"forceMountData" -> ConfigManager.forceMountData = value
"hideIcon" -> PrefManager.hideIcon = value
"bypassRiskyPackageWarning" -> PrefManager.bypassRiskyPackageWarning = value
"appDataIsolation" -> Unit
"voldAppDataIsolation" -> Unit
else -> throw IllegalArgumentException("Invalid key: $key")
@ -267,6 +269,11 @@ class SettingsFragment : Fragment(R.layout.fragment_settings), PreferenceFragmen
configureDataIsolation()
findPreference<SwitchPreferenceCompat>("bypassRiskyPackageWarning")?.setOnPreferenceChangeListener { _, newValue ->
activity?.recreate()
true
}
findPreference<Preference>("stopSystemService")?.setOnPreferenceClickListener {
if (ServiceClient.serviceVersion != 0) {
MaterialAlertDialogBuilder(requireContext())

View file

@ -1,6 +1,5 @@
package icu.nullptr.hidemyapplist.ui.fragment
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
@ -12,7 +11,6 @@ import androidx.fragment.app.setFragmentResultListener
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.recyclerview.widget.LinearLayoutManager
import by.kirich1409.viewbindingdelegate.viewBinding
import com.google.android.material.transition.MaterialContainerTransform
import icu.nullptr.hidemyapplist.common.JsonConfig
import icu.nullptr.hidemyapplist.service.ConfigManager
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 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?) {
setupToolbar(
toolbar = binding.toolbar,

View file

@ -56,16 +56,6 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) {
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?) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { onBack(false) }
setupToolbar(
@ -80,6 +70,7 @@ class TemplateSettingsFragment : Fragment(R.layout.fragment_template_settings) {
)
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.targetApps.setOnClickListener {
setFragmentResultListener("app_select") { _, bundle ->

View file

@ -15,7 +15,7 @@ class AppItemView @JvmOverloads constructor(
defStyleRes: Int = 0
) : 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
get() = binding.enabled.visibility == VISIBLE
@ -35,7 +35,12 @@ class AppItemView @JvmOverloads constructor(
fun load(packageName: String) {
binding.packageName.text = packageName
binding.label.text = PackageHelper.loadAppLabel(packageName)
binding.icon.setImageBitmap(PackageHelper.loadAppIcon(packageName))
try {
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
)
private object Comparators {
object Comparators {
val byLabel = Comparator<String> { o1, o2 ->
val n1 = loadAppLabel(o1).lowercase(Locale.getDefault())
val n2 = loadAppLabel(o2).lowercase(Locale.getDefault())
Collator.getInstance(Locale.getDefault()).compare(n1, n2)
try {
val n1 = loadAppLabel(o1).lowercase(Locale.getDefault())
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 n1 = o1.lowercase(Locale.getDefault())
@ -38,14 +42,22 @@ object PackageHelper {
Collator.getInstance(Locale.getDefault()).compare(n1, n2)
}
val byInstallTime = Comparator<String> { o1, o2 ->
val n1 = loadPackageInfo(o1).firstInstallTime
val n2 = loadPackageInfo(o2).firstInstallTime
n2.compareTo(n1)
try {
val n1 = loadPackageInfo(o1).firstInstallTime
val n2 = loadPackageInfo(o2).firstInstallTime
n2.compareTo(n1)
} catch (_: Throwable) {
byPackageName.compare(o1, o2)
}
}
val byUpdateTime = Comparator<String> { o1, o2 ->
val n1 = loadPackageInfo(o1).lastUpdateTime
val n2 = loadPackageInfo(o2).lastUpdateTime
n2.compareTo(n1)
try {
val n1 = loadPackageInfo(o1).lastUpdateTime
val n2 = loadPackageInfo(o2).lastUpdateTime
n2.compareTo(n1)
} catch (_: Throwable) {
byPackageName.compare(o1, o2)
}
}
}
@ -99,6 +111,10 @@ object PackageHelper {
mAppList.emit(list)
}
fun exists(packageName: String) = runBlocking {
packageCache.first().contains(packageName)
}
fun loadPackageInfo(packageName: String): PackageInfo = runBlocking {
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

@ -267,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) {
text1.text = getString(R.string.title_logs)
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())
}
}

View file

@ -32,10 +32,42 @@
android:clipToPadding="false"
android:scrollbars="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/list_empty_container"
android:visibility="gone"
android:gravity="center"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/about_page_item_category_padding_horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:importantForAccessibility="no"
android:src="@drawable/outline_android_24"
android:layout_marginBottom="8dp"
android:layout_width="@dimen/app_icon_size"
android:layout_height="@dimen/app_icon_size"
app:tint="?colorControlNormal" />
<TextView
android:id="@+id/list_empty_text"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -54,6 +54,10 @@
android:id="@+id/manage_templates"
layout="@layout/view_home_item" />
<include
android:id="@+id/manage_presets"
layout="@layout/view_home_item" />
<include
android:id="@+id/nav_logs"
layout="@layout/view_home_item" />
@ -75,6 +79,8 @@
android:id="@+id/backup_config"
app:icon="@drawable/outline_backup_24"
android:text="@string/home_backup_config"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginRight="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -84,6 +90,8 @@
android:id="@+id/restore_config"
app:icon="@drawable/outline_settings_backup_restore_24"
android:text="@string/home_restore_config"
android:maxLines="1"
android:ellipsize="end"
android:layout_marginLeft="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?actionBarSize"
android:elevation="0dp" />
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/preset_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
android:layout_weight="1" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

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