Skip to content

Commit 0a5c209

Browse files
committed
feat: update existing subscription on duplicate URL
1 parent d64e653 commit 0a5c209

6 files changed

Lines changed: 66 additions & 11 deletions

File tree

app/src/main/kotlin/app/MainActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ import features.settings.locale.localizedAppContext
2929
import features.subscription.SubscriptionInstallConfigUseCase
3030
import features.subscription.isSubscriptionInstallConfigUri
3131
import features.subscription.runtime.AndroidSubscriptionFetcher
32+
import features.subscription.subscriptionInstallMessage
3233
import features.subscription.toSubscriptionInstallConfigOrNull
33-
import features.subscription.usecase.subscriptionUpdateMessage
3434
import kotlinx.coroutines.launch
3535
import ui.feedback.AndroidToastTipNotifier
3636

@@ -189,8 +189,9 @@ class MainActivity : ComponentActivity() {
189189
subscriptionInstallConfigUseCase.install(config)
190190
}.onSuccess { result ->
191191
tipNotifier.show(
192-
subscriptionUpdateMessage(
192+
subscriptionInstallMessage(
193193
result = result,
194+
existingUrlTemplate = getString(R.string.subscription_install_existing_url),
194195
successTemplate = getString(R.string.proxy_server_list_subscription_update_result),
195196
failedTemplate = getString(R.string.proxy_server_list_subscription_update_result_with_failed),
196197
),

app/src/main/kotlin/features/proxy/server/list/ProxyServerListMessages.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal data class ProxyServerListMessages(
1515
val noTestableServers: String,
1616
val latencyDoneTemplate: String,
1717
val realConnectionDoneTemplate: String,
18+
val subscriptionInstallExistingUrlTemplate: String,
1819
val sortDone: String,
1920
val subscriptionUpdateResultTemplate: String,
2021
val subscriptionUpdateResultWithFailedTemplate: String,
@@ -42,6 +43,7 @@ internal fun proxyServerListMessages(): ProxyServerListMessages {
4243
noTestableServers = stringResource(R.string.proxy_server_list_no_testable),
4344
latencyDoneTemplate = stringResource(R.string.proxy_server_list_latency_done),
4445
realConnectionDoneTemplate = stringResource(R.string.proxy_server_list_real_connection_done),
46+
subscriptionInstallExistingUrlTemplate = stringResource(R.string.subscription_install_existing_url),
4547
sortDone = stringResource(R.string.common_complete),
4648
subscriptionUpdateResultTemplate = stringResource(R.string.proxy_server_list_subscription_update_result),
4749
subscriptionUpdateResultWithFailedTemplate =

app/src/main/kotlin/features/proxy/server/list/ProxyServerListTopBar.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import features.subscription.DefaultSubscriptionGroupId
3333
import features.subscription.SubscriptionInstallConfigUseCase
3434
import features.subscription.runtime.AndroidSubscriptionFetchOptions
3535
import features.subscription.runtime.AndroidSubscriptionFetcher
36+
import features.subscription.subscriptionInstallMessage
3637
import features.subscription.usecase.subscriptionUpdateMessage
3738
import features.subscription.usecase.toSubscriptionFetchOptions
3839
import features.subscription.usecase.updateSubscriptions
@@ -274,8 +275,9 @@ private suspend fun installSubscriptionFromText(
274275
).install(config)
275276
}.onSuccess { result ->
276277
tipNotifier.show(
277-
subscriptionUpdateMessage(
278+
subscriptionInstallMessage(
278279
result = result,
280+
existingUrlTemplate = messages.subscriptionInstallExistingUrlTemplate,
279281
successTemplate = messages.subscriptionUpdateResultTemplate,
280282
failedTemplate = messages.subscriptionUpdateResultWithFailedTemplate,
281283
),

app/src/main/kotlin/features/subscription/SubscriptionInstallConfigUseCase.kt

Lines changed: 56 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import features.proxy.server.usecase.withUpdatedSubscriptionServers
1313
import features.subscription.runtime.AndroidSubscriptionFetcher
1414
import features.subscription.usecase.toSubscriptionFetchOptions
1515
import features.subscription.usecase.updateSubscriptions
16+
import features.subscription.usecase.subscriptionUpdateMessage
1617
import io.ktor.http.Url
18+
import ui.text.formatTemplate
1719
import utils.decodeUrlComponentPreservingPlus
1820

1921
internal data class SubscriptionInstallConfig(
@@ -22,14 +24,19 @@ internal data class SubscriptionInstallConfig(
2224
val userAgent: String,
2325
)
2426

27+
internal data class SubscriptionInstallResult(
28+
val updateResult: ProxyServerListSubscriptionUpdateResult,
29+
val existingGroupName: String?,
30+
)
31+
2532
internal class SubscriptionInstallConfigUseCase(
2633
private val stateStore: AndroidAppStateStore,
2734
private val subscriptionFetcher: AndroidSubscriptionFetcher,
2835
) {
29-
suspend fun install(config: SubscriptionInstallConfig): ProxyServerListSubscriptionUpdateResult {
30-
val group = stateStore.prepareSubscriptionInstallGroup(config)
36+
suspend fun install(config: SubscriptionInstallConfig): SubscriptionInstallResult {
37+
val preparedGroup = stateStore.prepareSubscriptionInstallGroup(config)
3138
val result = updateSubscriptions(
32-
groups = listOf(group),
39+
groups = listOf(preparedGroup.group),
3340
subscriptionFetcher = subscriptionFetcher,
3441
fetchOptions = { stateStore.state.value.toSubscriptionFetchOptions(it) },
3542
)
@@ -41,10 +48,31 @@ internal class SubscriptionInstallConfigUseCase(
4148
)
4249
}
4350
}
44-
return result
51+
return SubscriptionInstallResult(
52+
updateResult = result,
53+
existingGroupName = preparedGroup.existingGroupName,
54+
)
4555
}
4656
}
4757

58+
internal fun subscriptionInstallMessage(
59+
result: SubscriptionInstallResult,
60+
existingUrlTemplate: String,
61+
successTemplate: String,
62+
failedTemplate: String,
63+
): String {
64+
val updateMessage = subscriptionUpdateMessage(
65+
result = result.updateResult,
66+
successTemplate = successTemplate,
67+
failedTemplate = failedTemplate,
68+
)
69+
val existingGroupName = result.existingGroupName ?: return updateMessage
70+
return listOf(
71+
existingUrlTemplate.formatTemplate("name" to existingGroupName),
72+
updateMessage,
73+
).joinToString(separator = "\n")
74+
}
75+
4876
internal fun Intent.toSubscriptionInstallConfigOrNull(): SubscriptionInstallConfig? {
4977
if (action != Intent.ACTION_VIEW) return null
5078
return data?.toString()?.toSubscriptionInstallConfigOrNull()
@@ -103,15 +131,26 @@ private fun Url.toRawHttpsSubscriptionInstallConfigOrNull(rawValue: String): Sub
103131

104132
private fun AndroidAppStateStore.prepareSubscriptionInstallGroup(
105133
config: SubscriptionInstallConfig,
106-
): SubscriptionGroupState {
107-
var savedGroup: SubscriptionGroupState? = null
134+
): PreparedSubscriptionInstallGroup {
135+
var preparedGroup: PreparedSubscriptionInstallGroup? = null
108136
update { state ->
137+
val existingGroup = state.existingSubscriptionGroupByUrl(config.url)
138+
if (existingGroup != null) {
139+
preparedGroup = PreparedSubscriptionInstallGroup(
140+
group = existingGroup,
141+
existingGroupName = existingGroup.name,
142+
)
143+
return@update state
144+
}
109145
val reusableGroup = state.reusableDefaultSubscriptionGroup()
110146
val group = reusableGroup?.copy(
111147
url = config.url,
112148
userAgent = config.userAgent,
113149
) ?: state.newSubscriptionGroup(config)
114-
savedGroup = group
150+
preparedGroup = PreparedSubscriptionInstallGroup(
151+
group = group,
152+
existingGroupName = null,
153+
)
115154
if (reusableGroup == null) {
116155
state.copy(
117156
subscriptionGroups = state.subscriptionGroups + group,
@@ -125,7 +164,16 @@ private fun AndroidAppStateStore.prepareSubscriptionInstallGroup(
125164
)
126165
}
127166
}
128-
return checkNotNull(savedGroup)
167+
return checkNotNull(preparedGroup)
168+
}
169+
170+
private data class PreparedSubscriptionInstallGroup(
171+
val group: SubscriptionGroupState,
172+
val existingGroupName: String?,
173+
)
174+
175+
private fun AppState.existingSubscriptionGroupByUrl(url: String): SubscriptionGroupState? {
176+
return subscriptionGroups.firstOrNull { group -> group.url == url }
129177
}
130178

131179
private fun AppState.reusableDefaultSubscriptionGroup(): SubscriptionGroupState? {

app/src/main/res/values-zh-rCN/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@
511511
<string name="subscription_auto_update_interval">自动更新间隔(小时)</string>
512512
<string name="subscription_update_via_proxy">使用代理更新</string>
513513
<string name="subscription_update_via_proxy_summary">通过本地代理请求订阅地址</string>
514+
<string name="subscription_install_existing_url">订阅地址已存在于“{name}”,正在更新该分组</string>
514515
<string name="subscription_install_config_invalid">无效的订阅链接</string>
515516
<string name="subscription_install_config_failed">导入订阅失败</string>
516517
</resources>

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@
511511
<string name="subscription_auto_update_interval">Auto update interval (hours)</string>
512512
<string name="subscription_update_via_proxy">Update via proxy</string>
513513
<string name="subscription_update_via_proxy_summary">Use the local proxy for subscription requests</string>
514+
<string name="subscription_install_existing_url">A matching subscription URL already exists in {name}; updating that group</string>
514515
<string name="subscription_install_config_invalid">Invalid subscription link</string>
515516
<string name="subscription_install_config_failed">Failed to import subscription</string>
516517
</resources>

0 commit comments

Comments
 (0)