Add a hint for an empty app list

This commit is contained in:
armv9 2025-10-26 14:29:08 +09:00
parent 963418f9e3
commit ec76238dd1
7 changed files with 72 additions and 12 deletions

View File

@ -2,7 +2,6 @@ package io.nekohasekai.sagernet.ui
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.os.Bundle
@ -45,6 +44,11 @@ import kotlin.coroutines.coroutineContext
class AppListActivity : ThemedActivity() {
companion object {
private const val SWITCH = "switch"
private val cachedApps
get() = PackageCache.installedPackages.toMutableMap().apply {
remove(BuildConfig.APPLICATION_ID)
}
}
private class ProxiedApp(
@ -96,7 +100,7 @@ class AppListActivity : ThemedActivity() {
var filteredApps = apps
suspend fun reload() {
apps = getCachedApps().mapNotNull { (packageName, packageInfo) ->
apps = cachedApps.mapNotNull { (packageName, packageInfo) ->
coroutineContext[Job]!!.ensureActive()
packageInfo.applicationInfo?.let { ProxiedApp(packageManager, it, packageName) }
}.sortedWith(compareBy({ !isProxiedApp(it) }, { it.name.toString() }))
@ -156,7 +160,7 @@ class AppListActivity : ThemedActivity() {
private fun initProxiedUids(str: String = DataStore.routePackages) {
proxiedUids.clear()
val apps = getCachedApps()
val apps = cachedApps
for (line in str.lineSequence()) {
val app = (apps[line] ?: continue)
val uid = app.applicationInfo?.uid ?: continue
@ -174,14 +178,12 @@ class AppListActivity : ThemedActivity() {
val adapter = binding.list.adapter as AppsAdapter
withContext(Dispatchers.IO) { adapter.reload() }
adapter.filter.filter(binding.search.text?.toString() ?: "")
binding.list.crossFadeFrom(loading)
}
}
fun getCachedApps(): MutableMap<String, PackageInfo> {
val packages = PackageCache.installedPackages
return packages.toMutableMap().apply {
remove(BuildConfig.APPLICATION_ID)
if (apps.isEmpty()) {
binding.list.visibility = View.GONE
binding.appPlaceholder.root.crossFadeFrom(loading)
} else {
binding.list.crossFadeFrom(loading)
}
}
}
@ -191,6 +193,14 @@ class AppListActivity : ThemedActivity() {
binding = LayoutAppListBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.appPlaceholder.openSettings.setOnClickListener {
val intent =
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = android.net.Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
setSupportActionBar(binding.toolbar)
supportActionBar?.apply {
setTitle(R.string.select_apps)

View File

@ -184,7 +184,12 @@ class AppManagerActivity : ThemedActivity() {
val adapter = binding.list.adapter as AppsAdapter
withContext(Dispatchers.IO) { adapter.reload() }
adapter.filter.filter(binding.search.text?.toString() ?: "")
binding.list.crossFadeFrom(loading)
if (apps.isEmpty()) {
binding.list.visibility = View.GONE
binding.appPlaceholder.root.crossFadeFrom(loading)
} else {
binding.list.crossFadeFrom(loading)
}
}
}
@ -194,6 +199,14 @@ class AppManagerActivity : ThemedActivity() {
binding = LayoutAppsBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.appPlaceholder.openSettings.setOnClickListener {
val intent =
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = android.net.Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}
setSupportActionBar(binding.toolbar)
supportActionBar?.apply {
setTitle(R.string.proxied_apps)

View File

@ -116,4 +116,8 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/layout_apps_item" />
<include
android:id="@+id/app_placeholder"
layout="@layout/layout_app_placeholder" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="32dp"
android:visibility="gone">
<TextView
android:id="@+id/empty_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingBottom="16dp"
android:text="@string/app_list_permission_denied"
android:textColor="?attr/colorOnBackground"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/open_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/open_app_settings" />
</LinearLayout>

View File

@ -153,4 +153,8 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:listitem="@layout/layout_apps_item" />
<include
android:id="@+id/app_placeholder"
layout="@layout/layout_app_placeholder" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -495,4 +495,6 @@
<string name="reset_settings">恢复默认设置</string>
<string name="reset_settings_message">恢复默认设置,但节点、分组等数据将保留。如需完全清除数据,请在系统设置中直接清除应用数据。</string>
<string name="minimize">最小化</string>
<string name="app_list_permission_denied">无法读取已安装的应用。\n这通常是由于系统限制了应用的读取权限例如某些中国厂商系统。\n请在系统设置中授予权限。</string>
<string name="open_app_settings">打开系统设置</string>
</resources>

View File

@ -575,4 +575,6 @@
<string name="reset_settings">Restore default settings</string>
<string name="reset_settings_message">Restore default settings, but data such as nodes and groups will be retained. To completely clear data, clear application data directly in the system settings.</string>
<string name="minimize">Minimize</string>
<string name="app_list_permission_denied">Unable to read installed apps.\nThis is usually because the system has restricted app read permissions.\nPlease grant permissions in the system settings.</string>
<string name="open_app_settings">Open System Settings</string>
</resources>