Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ build/
/app/foss/release
/app/premium/release
/captures
/.kotlin/

# Ignore Gradle GUI config
gradle-app.setting
Expand Down
5 changes: 4 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@ task("downloadGeoFiles") {

doLast {
geoFilesUrls.forEach { (downloadUrl, outputFileName) ->
val url = URL(downloadUrl)
val outputPath = file("$geoFilesDownloadDir/$outputFileName")
if (outputPath.exists()) {
return@forEach
}
val url = URL(downloadUrl)
outputPath.parentFile.mkdirs()
url.openStream().use { input ->
Files.copy(input, outputPath.toPath(), StandardCopyOption.REPLACE_EXISTING)
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@
android:configChanges="uiMode"
android:exported="false"
android:label="@string/access_control_packages" />
<activity
android:name=".AppsStrategyActivity"
android:configChanges="uiMode"
android:exported="false"
android:label="@string/access_control_packages" />
<activity
android:name=".HelpActivity"
android:configChanges="uiMode"
Expand Down
46 changes: 37 additions & 9 deletions app/src/main/java/com/github/kr328/clash/AccessControlActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import androidx.core.content.getSystemService
import com.github.kr328.clash.common.util.uuid
import com.github.kr328.clash.design.AccessControlDesign
import com.github.kr328.clash.design.model.AppInfo
import com.github.kr328.clash.design.util.toAppInfo
import com.github.kr328.clash.service.model.AppsStrategyConfig
import com.github.kr328.clash.service.store.ServiceStore
import com.github.kr328.clash.util.startClashService
import com.github.kr328.clash.util.stopClashService
Expand All @@ -21,22 +23,48 @@ import kotlinx.coroutines.withContext

class AccessControlActivity : BaseActivity<AccessControlDesign>() {
override suspend fun main() {
val configUuid = intent.uuid
val service = ServiceStore(this)

val selected = withContext(Dispatchers.IO) {
service.accessControlPackages.toMutableSet()
val (config, selected) = withContext(Dispatchers.IO) {
if (configUuid != null) {
val cfg = service.appsStrategyConfigs.find { it.uuid == configUuid }
if (cfg != null) {
cfg to cfg.packages.toMutableSet()
} else {
null to service.accessControlPackages.toMutableSet()
}
} else {
null to service.accessControlPackages.toMutableSet()
}
}

defer {
withContext(Dispatchers.IO) {
val changed = selected != service.accessControlPackages
service.accessControlPackages = selected
if (clashRunning && changed) {
stopClashService()
while (clashRunning) {
delay(200)
if (config != null) {
val changed = selected != config.packages.toSet()
if (changed) {
val updatedConfig = AppsStrategyConfig(
uuid = config.uuid,
name = config.name,
mode = config.mode,
packages = selected.toList()
)
val configs = service.appsStrategyConfigs.map {
if (it.uuid == config.uuid) updatedConfig else it
}
service.appsStrategyConfigs = configs
}
} else {
val changed = selected != service.accessControlPackages
service.accessControlPackages = selected
if (clashRunning && changed) {
stopClashService()
while (clashRunning) {
delay(200)
}
startClashService()
}
startClashService()
}
}
}
Expand Down
154 changes: 154 additions & 0 deletions app/src/main/java/com/github/kr328/clash/AppsStrategyActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.github.kr328.clash

import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.setUUID
import com.github.kr328.clash.design.AppsStrategyDesign
import com.github.kr328.clash.design.dialog.requestModelTextInput
import com.github.kr328.clash.design.requestAppListsConfigEdit
import com.github.kr328.clash.service.model.AccessControlMode
import com.github.kr328.clash.service.model.AppsStrategyConfig
import com.github.kr328.clash.service.store.ServiceStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.selects.select
import kotlinx.coroutines.withContext
import java.util.*

class AppsStrategyActivity : BaseActivity<AppsStrategyDesign>() {
override suspend fun main() {
val design = AppsStrategyDesign(this)

setContentDesign(design)

design.fetch()

while (isActive) {
select<Unit> {
events.onReceive {
when (it) {
Event.ActivityStart -> {
design.fetch()
}

else -> Unit
}
}
design.requests.onReceive {
when (it) {
is AppsStrategyDesign.Request.Choose -> {
startActivity(AccessControlActivity::class.intent.setUUID(it.config.uuid))
}

is AppsStrategyDesign.Request.Edit -> {
val result = requestAppListsConfigEdit(
initialName = it.config.name,
initialMode = it.config.mode,
title = getString(com.github.kr328.clash.design.R.string.edit_config_name),
hint = getString(com.github.kr328.clash.design.R.string.config_name),
showDelete = true
)
if (result != null) {
if (result.deleted) {
deleteConfig(it.config.uuid)
design.fetch()
} else if (result.name != it.config.name || result.mode != it.config.mode) {
updateConfig(it.config.uuid, result.name, result.mode)
design.fetch()
}
}
}

is AppsStrategyDesign.Request.Active -> {
setActiveConfig(it.config.uuid)
}

AppsStrategyDesign.Request.Create -> {
val name = requestModelTextInput(
initial = "",
title = getString(com.github.kr328.clash.design.R.string.new_config),
hint = getString(com.github.kr328.clash.design.R.string.config_name)
)
if (name.isNotBlank()) {
createConfig(name)
}
}
}
}
}
}
}

private suspend fun AppsStrategyDesign.fetch() {
val configs = queryConfigs()
val activeUuid = queryActiveUuid()
patchConfigs(configs, activeUuid)
}

private suspend fun queryConfigs(): List<AppsStrategyConfig> {
return withContext(Dispatchers.IO) {
val service = ServiceStore(this@AppsStrategyActivity)
service.appsStrategyConfigs
}
}

private suspend fun queryActiveUuid(): UUID? {
return withContext(Dispatchers.IO) {
val service = ServiceStore(this@AppsStrategyActivity)
service.activeAppsStrategyConfigUuid
}
}

private suspend fun setActiveConfig(uuid: UUID) {
withContext(Dispatchers.IO) {
val service = ServiceStore(this@AppsStrategyActivity)
service.activeAppsStrategyConfigUuid = uuid
design?.fetch()
}
}

private suspend fun deleteConfig(uuid: UUID) {
withContext(Dispatchers.IO) {
val service = ServiceStore(this@AppsStrategyActivity)
val configs = service.appsStrategyConfigs.filter { it.uuid != uuid }
service.appsStrategyConfigs = configs
if (service.activeAppsStrategyConfigUuid == uuid) {
service.activeAppsStrategyConfigUuid = null
}
}
}

private suspend fun createConfig(name: String) {
withContext(Dispatchers.IO) {
val service = ServiceStore(this@AppsStrategyActivity)
val configs = service.appsStrategyConfigs.toMutableList()
val newConfig = AppsStrategyConfig(
uuid = UUID.randomUUID(),
name = name,
mode = com.github.kr328.clash.service.model.AccessControlMode.AcceptAll,
packages = emptyList()
)
configs.add(newConfig)
service.appsStrategyConfigs = configs
design?.fetch()
}
}

private suspend fun updateConfig(uuid: UUID, newName: String, newMode: AccessControlMode) {
withContext(Dispatchers.IO) {
val service = ServiceStore(this@AppsStrategyActivity)
val configs = service.appsStrategyConfigs.map { config ->
if (config.uuid == uuid) {
AppsStrategyConfig(
uuid = config.uuid,
name = newName,
mode = newMode,
packages = config.packages
)
} else {
config
}
}
service.appsStrategyConfigs = configs
}
}
}
19 changes: 17 additions & 2 deletions app/src/main/java/com/github/kr328/clash/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@ package com.github.kr328.clash
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.PersistableBundle
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.github.kr328.clash.common.util.intent
import com.github.kr328.clash.common.util.ticker
import com.github.kr328.clash.design.MainDesign
import com.github.kr328.clash.design.ui.ToastDuration
import com.github.kr328.clash.service.store.ServiceStore
import com.github.kr328.clash.util.startClashService
import com.github.kr328.clash.util.stopClashService
import com.github.kr328.clash.util.withClash
Expand Down Expand Up @@ -59,6 +58,8 @@ class MainActivity : BaseActivity<MainDesign>() {
startActivity(ProfilesActivity::class.intent)
MainDesign.Request.OpenProviders ->
startActivity(ProvidersActivity::class.intent)
MainDesign.Request.OpenAppsStrategy ->
startActivity(AppsStrategyActivity::class.intent)
MainDesign.Request.OpenLogs -> {
if (LogcatService.running) {
startActivity(LogcatActivity::class.intent)
Expand Down Expand Up @@ -99,6 +100,8 @@ class MainActivity : BaseActivity<MainDesign>() {
withProfile {
setProfileName(queryActive()?.name)
}

setAppsStrategyName(queryActiveAppsStrategyName())
}

private suspend fun MainDesign.fetchTraffic() {
Expand Down Expand Up @@ -143,6 +146,18 @@ class MainActivity : BaseActivity<MainDesign>() {
}
}

private suspend fun queryActiveAppsStrategyName(): String? {
return withContext(Dispatchers.IO) {
val service = ServiceStore(this@MainActivity)
val activeUuid = service.activeAppsStrategyConfigUuid
if (activeUuid != null) {
service.appsStrategyConfigs.find { it.uuid == activeUuid }?.name
} else {
null
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Expand Down
Loading