diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt index 107b27e..86eb9a7 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AppListActivity.kt @@ -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 { - 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) diff --git a/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt b/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt index e212cbe..e5eb258 100644 --- a/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt +++ b/app/src/main/java/io/nekohasekai/sagernet/ui/AppManagerActivity.kt @@ -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) diff --git a/app/src/main/res/layout/layout_app_list.xml b/app/src/main/res/layout/layout_app_list.xml index 45432fa..4128a88 100644 --- a/app/src/main/res/layout/layout_app_list.xml +++ b/app/src/main/res/layout/layout_app_list.xml @@ -116,4 +116,8 @@ app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:listitem="@layout/layout_apps_item" /> + + diff --git a/app/src/main/res/layout/layout_app_placeholder.xml b/app/src/main/res/layout/layout_app_placeholder.xml new file mode 100644 index 0000000..456ac8a --- /dev/null +++ b/app/src/main/res/layout/layout_app_placeholder.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/layout/layout_apps.xml b/app/src/main/res/layout/layout_apps.xml index 1dd32b2..e648248 100644 --- a/app/src/main/res/layout/layout_apps.xml +++ b/app/src/main/res/layout/layout_apps.xml @@ -153,4 +153,8 @@ app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:listitem="@layout/layout_apps_item" /> + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index cd92b7e..29ee463 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -495,4 +495,6 @@ 恢复默认设置 恢复默认设置,但节点、分组等数据将保留。如需完全清除数据,请在系统设置中直接清除应用数据。 最小化 + 无法读取已安装的应用。\n这通常是由于系统限制了应用的读取权限(例如某些中国厂商系统)。\n请在系统设置中授予权限。 + 打开系统设置 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a30c703..cd2701b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -575,4 +575,6 @@ Restore default settings 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. Minimize + Unable to read installed apps.\nThis is usually because the system has restricted app read permissions.\nPlease grant permissions in the system settings. + Open System Settings \ No newline at end of file