Skip to content

Commit 8cfae9a

Browse files
guiyanakuangclaudehappy-otter
authored
✨ Add granular sync control for specific content types (#3737)
* ✨ Add granular sync control for specific content types - Add 7 new config fields to control sync per content type (text, url, html, rtf, image, file, color) - Add type filtering logic in SyncPasteTaskExecutor - Move sync settings from Pasteboard Settings to Network Settings - Add "Sync Settings" and "Sync Content Types" sections in Network Settings UI - Add i18n support for 9 languages Closes #3657 Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> * 🔨 Move max_sync_file_size and clipboard_relay to Network Settings - Remove max_sync_file_size from PasteboardSettingsContentView (already in Network Settings) - Move enable_clipboard_relay from DesktopPasteboardSettingsContentView to new DesktopNetworkSettingsContentView - Add syncExtContent parameter to NetworkSettingsContentView for platform-specific extension - Create DesktopNetworkSettingsContentView wrapper that adds clipboard_relay in sync settings section Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> * 🎨 Rename sync_content_types to Global Sync Type Toggles Clarify that these are global toggles for sync types, as device-specific toggles may be added in the future. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Happy <yesreply@happy.engineering>
1 parent 4114a66 commit 8cfae9a

File tree

18 files changed

+290
-57
lines changed

18 files changed

+290
-57
lines changed

app/src/commonMain/kotlin/com/crosspaste/task/SyncPasteTaskExecutor.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.crosspaste.task
22

33
import com.crosspaste.app.AppControl
44
import com.crosspaste.app.AppInfo
5+
import com.crosspaste.config.AppConfig
6+
import com.crosspaste.config.CommonConfigManager
57
import com.crosspaste.db.paste.PasteDao
68
import com.crosspaste.db.task.PasteTask
79
import com.crosspaste.db.task.SyncExtraInfo
@@ -15,6 +17,7 @@ import com.crosspaste.net.clientapi.SuccessResult
1517
import com.crosspaste.net.clientapi.createFailureResult
1618
import com.crosspaste.net.filter
1719
import com.crosspaste.paste.PasteData
20+
import com.crosspaste.paste.PasteType
1821
import com.crosspaste.sync.SyncHandler
1922
import com.crosspaste.sync.SyncManager
2023
import com.crosspaste.utils.HostAndPort
@@ -32,6 +35,7 @@ import kotlin.collections.filter
3235
class SyncPasteTaskExecutor(
3336
private val appControl: AppControl,
3437
private val appInfo: AppInfo,
38+
private val configManager: CommonConfigManager,
3539
private val pasteDao: PasteDao,
3640
private val pasteClientApi: PasteClientApi,
3741
private val syncManager: SyncManager,
@@ -53,13 +57,40 @@ class SyncPasteTaskExecutor(
5357
}
5458

5559
return pasteDao.getNoDeletePasteData(pasteTask.pasteDataId)?.let { pasteData ->
60+
// Check if sync is enabled for this paste type
61+
if (!isSyncEnabledForPasteType(pasteData)) {
62+
logger.debug { "Sync disabled for paste type: ${pasteData.getType().name}" }
63+
return@let createEmptyResult(syncExtraInfo)
64+
}
65+
5666
val syncResults = executeSyncTasks(pasteData, syncExtraInfo)
5767
processResults(syncResults, syncExtraInfo, pasteTask.modifyTime)
5868
} ?: run {
5969
createEmptyResult(syncExtraInfo)
6070
}
6171
}
6272

73+
private fun isSyncEnabledForPasteType(pasteData: PasteData): Boolean {
74+
val config = configManager.config.value
75+
val pasteType = pasteData.getType()
76+
return isSyncEnabledForType(config, pasteType)
77+
}
78+
79+
private fun isSyncEnabledForType(
80+
config: AppConfig,
81+
pasteType: PasteType,
82+
): Boolean =
83+
when (pasteType) {
84+
PasteType.TEXT_TYPE -> config.enableSyncText
85+
PasteType.URL_TYPE -> config.enableSyncUrl
86+
PasteType.HTML_TYPE -> config.enableSyncHtml
87+
PasteType.RTF_TYPE -> config.enableSyncRtf
88+
PasteType.IMAGE_TYPE -> config.enableSyncImage
89+
PasteType.FILE_TYPE -> config.enableSyncFile
90+
PasteType.COLOR_TYPE -> config.enableSyncColor
91+
else -> true
92+
}
93+
6394
private fun createEmptyResult(syncExtraInfo: SyncExtraInfo): PasteTaskResult {
6495
syncExtraInfo.syncFails.clear()
6596
return SuccessPasteTaskResult(jsonUtils.JSON.encodeToString(syncExtraInfo))

app/src/commonMain/kotlin/com/crosspaste/ui/settings/NetworkSettingsContentView.kt

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,18 @@ import androidx.compose.foundation.layout.padding
88
import androidx.compose.foundation.lazy.LazyColumn
99
import androidx.compose.foundation.lazy.itemsIndexed
1010
import androidx.compose.material.icons.Icons
11+
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
12+
import androidx.compose.material.icons.automirrored.outlined.Article
1113
import androidx.compose.material.icons.filled.Cable
14+
import androidx.compose.material.icons.filled.Shield
15+
import androidx.compose.material.icons.filled.SyncAlt
1216
import androidx.compose.material.icons.filled.WifiFind
17+
import androidx.compose.material.icons.outlined.Code
18+
import androidx.compose.material.icons.outlined.Description
19+
import androidx.compose.material.icons.outlined.Image
20+
import androidx.compose.material.icons.outlined.Link
21+
import androidx.compose.material.icons.outlined.Palette
22+
import androidx.compose.material.icons.outlined.TextFields
1323
import androidx.compose.material3.CircularProgressIndicator
1424
import androidx.compose.material3.HorizontalDivider
1525
import androidx.compose.material3.Text
@@ -29,6 +39,7 @@ import com.crosspaste.dto.sync.SyncInfo
2939
import com.crosspaste.i18n.GlobalCopywriter
3040
import com.crosspaste.net.NetworkInterfaceInfo
3141
import com.crosspaste.net.NetworkInterfaceService
42+
import com.crosspaste.ui.base.Counter
3243
import com.crosspaste.ui.base.SectionHeader
3344
import com.crosspaste.ui.devices.SyncScopeFactory
3445
import com.crosspaste.ui.theme.AppUISize.medium
@@ -38,7 +49,7 @@ import com.crosspaste.utils.getJsonUtils
3849
import org.koin.compose.koinInject
3950

4051
@Composable
41-
fun NetworkSettingsContentView() {
52+
fun NetworkSettingsContentView(syncExtContent: @Composable () -> Unit = {}) {
4253
val configManager = koinInject<CommonConfigManager>()
4354
val copywriter = koinInject<GlobalCopywriter>()
4455
val networkInterfaceService = koinInject<NetworkInterfaceService>()
@@ -135,6 +146,107 @@ fun NetworkSettingsContentView() {
135146
}
136147
}
137148

149+
item {
150+
SectionHeader("sync_settings", topPadding = medium)
151+
}
152+
153+
item {
154+
SettingSectionCard {
155+
SettingListSwitchItem(
156+
title = "encrypted_sync",
157+
icon = Icons.Default.Shield,
158+
checked = config.enableEncryptSync,
159+
) {
160+
configManager.updateConfig("enableEncryptSync", it)
161+
}
162+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
163+
SettingListSwitchItem(
164+
title = "sync_file_size_limit",
165+
icon = Icons.Default.SyncAlt,
166+
checked = config.enabledSyncFileSizeLimit,
167+
) { newEnabledSyncFileSizeLimit ->
168+
configManager.updateConfig("enabledSyncFileSizeLimit", newEnabledSyncFileSizeLimit)
169+
}
170+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
171+
SettingListItem(
172+
title = "max_sync_file_size",
173+
icon = Icons.AutoMirrored.Filled.InsertDriveFile,
174+
trailingContent = {
175+
Counter(defaultValue = config.maxSyncFileSize, unit = "MB", rule = {
176+
it >= 0
177+
}) { currentMaxSyncFileSize ->
178+
configManager.updateConfig("maxSyncFileSize", currentMaxSyncFileSize)
179+
}
180+
},
181+
)
182+
syncExtContent()
183+
}
184+
}
185+
186+
item {
187+
SectionHeader("sync_content_types", topPadding = medium)
188+
}
189+
190+
item {
191+
SettingSectionCard {
192+
SettingListSwitchItem(
193+
title = "sync_text",
194+
icon = Icons.Outlined.TextFields,
195+
checked = config.enableSyncText,
196+
) { enableSyncText ->
197+
configManager.updateConfig("enableSyncText", enableSyncText)
198+
}
199+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
200+
SettingListSwitchItem(
201+
title = "sync_url",
202+
icon = Icons.Outlined.Link,
203+
checked = config.enableSyncUrl,
204+
) { enableSyncUrl ->
205+
configManager.updateConfig("enableSyncUrl", enableSyncUrl)
206+
}
207+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
208+
SettingListSwitchItem(
209+
title = "sync_html",
210+
icon = Icons.Outlined.Code,
211+
checked = config.enableSyncHtml,
212+
) { enableSyncHtml ->
213+
configManager.updateConfig("enableSyncHtml", enableSyncHtml)
214+
}
215+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
216+
SettingListSwitchItem(
217+
title = "sync_rtf",
218+
icon = Icons.AutoMirrored.Outlined.Article,
219+
checked = config.enableSyncRtf,
220+
) { enableSyncRtf ->
221+
configManager.updateConfig("enableSyncRtf", enableSyncRtf)
222+
}
223+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
224+
SettingListSwitchItem(
225+
title = "sync_image",
226+
icon = Icons.Outlined.Image,
227+
checked = config.enableSyncImage,
228+
) { enableSyncImage ->
229+
configManager.updateConfig("enableSyncImage", enableSyncImage)
230+
}
231+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
232+
SettingListSwitchItem(
233+
title = "sync_file",
234+
icon = Icons.Outlined.Description,
235+
checked = config.enableSyncFile,
236+
) { enableSyncFile ->
237+
configManager.updateConfig("enableSyncFile", enableSyncFile)
238+
}
239+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
240+
SettingListSwitchItem(
241+
title = "sync_color",
242+
icon = Icons.Outlined.Palette,
243+
checked = config.enableSyncColor,
244+
) { enableSyncColor ->
245+
configManager.updateConfig("enableSyncColor", enableSyncColor)
246+
}
247+
}
248+
}
249+
138250
item {
139251
SectionHeader("blacklist", topPadding = medium)
140252
}

app/src/commonMain/kotlin/com/crosspaste/ui/settings/PasteboardSettingsContentView.kt

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
55
import androidx.compose.foundation.layout.padding
66
import androidx.compose.foundation.lazy.LazyColumn
77
import androidx.compose.material.icons.Icons
8-
import androidx.compose.material.icons.automirrored.filled.InsertDriveFile
98
import androidx.compose.material.icons.filled.Archive
109
import androidx.compose.material.icons.filled.ContentPasteGo
1110
import androidx.compose.material.icons.filled.FormatPaint
1211
import androidx.compose.material.icons.filled.MusicNote
13-
import androidx.compose.material.icons.filled.Shield
1412
import androidx.compose.material.icons.filled.Start
15-
import androidx.compose.material.icons.filled.SyncAlt
1613
import androidx.compose.material3.HorizontalDivider
1714
import androidx.compose.runtime.Composable
1815
import androidx.compose.runtime.collectAsState
@@ -46,14 +43,6 @@ fun PasteboardSettingsContentView(extContent: @Composable () -> Unit = {}) {
4643
pasteboardService.toggle()
4744
}
4845
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
49-
SettingListSwitchItem(
50-
title = "encrypted_sync",
51-
icon = Icons.Default.Shield,
52-
checked = config.enableEncryptSync,
53-
) {
54-
configManager.updateConfig("enableEncryptSync", it)
55-
}
56-
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
5746
SettingListSwitchItem(
5847
title = "paste_primary_type_only",
5948
icon = Icons.Default.FormatPaint,
@@ -87,14 +76,6 @@ fun PasteboardSettingsContentView(extContent: @Composable () -> Unit = {}) {
8776
)
8877
}
8978
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
90-
SettingListSwitchItem(
91-
title = "sync_file_size_limit",
92-
icon = Icons.Default.SyncAlt,
93-
checked = config.enabledSyncFileSizeLimit,
94-
) { newEnabledSyncFileSizeLimit ->
95-
configManager.updateConfig("enabledSyncFileSizeLimit", newEnabledSyncFileSizeLimit)
96-
}
97-
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
9879
SettingListItem(
9980
title = "max_back_up_file_size",
10081
icon = Icons.Default.Archive,
@@ -106,18 +87,6 @@ fun PasteboardSettingsContentView(extContent: @Composable () -> Unit = {}) {
10687
}
10788
},
10889
)
109-
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
110-
SettingListItem(
111-
title = "max_sync_file_size",
112-
icon = Icons.AutoMirrored.Filled.InsertDriveFile,
113-
trailingContent = {
114-
Counter(defaultValue = config.maxSyncFileSize, unit = "MB", rule = {
115-
it >= 0
116-
}) { currentMaxSyncFileSize ->
117-
configManager.updateConfig("maxSyncFileSize", currentMaxSyncFileSize)
118-
}
119-
},
120-
)
12190
extContent()
12291
}
12392
}

app/src/desktopMain/kotlin/com/crosspaste/DesktopModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ class DesktopModule(
472472
PullFileTaskExecutor(get(), get(), get(), get(), get(), get(), get()),
473473
PullIconTaskExecutor(get(), get(), get(), get()),
474474
SwitchLanguageTaskExecutor(get(), get()),
475-
SyncPasteTaskExecutor(get(), get(), get(), get(), get()),
475+
SyncPasteTaskExecutor(get(), get(), get(), get(), get(), get()),
476476
),
477477
get(),
478478
)

app/src/desktopMain/kotlin/com/crosspaste/config/DesktopAppConfig.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ data class DesktopAppConfig(
4848
val proxyPort: String = "7890",
4949
val showGrantAccessibility: Boolean = true,
5050
val enableClipboardRelay: Boolean = false,
51+
// Sync content type controls
52+
override val enableSyncText: Boolean = true,
53+
override val enableSyncUrl: Boolean = true,
54+
override val enableSyncHtml: Boolean = true,
55+
override val enableSyncRtf: Boolean = true,
56+
override val enableSyncImage: Boolean = true,
57+
override val enableSyncFile: Boolean = true,
58+
override val enableSyncColor: Boolean = true,
5159
) : AppConfig {
5260
override fun copy(
5361
key: String,
@@ -135,5 +143,12 @@ data class DesktopAppConfig(
135143
proxyHost = if (key == "proxyHost") toString(value) else proxyHost,
136144
proxyPort = if (key == "proxyPort") toString(value) else proxyPort,
137145
showGrantAccessibility = if (key == "showGrantAccessibility") toBoolean(value) else showGrantAccessibility,
146+
enableSyncText = if (key == "enableSyncText") toBoolean(value) else enableSyncText,
147+
enableSyncUrl = if (key == "enableSyncUrl") toBoolean(value) else enableSyncUrl,
148+
enableSyncHtml = if (key == "enableSyncHtml") toBoolean(value) else enableSyncHtml,
149+
enableSyncRtf = if (key == "enableSyncRtf") toBoolean(value) else enableSyncRtf,
150+
enableSyncImage = if (key == "enableSyncImage") toBoolean(value) else enableSyncImage,
151+
enableSyncFile = if (key == "enableSyncFile") toBoolean(value) else enableSyncFile,
152+
enableSyncColor = if (key == "enableSyncColor") toBoolean(value) else enableSyncColor,
138153
)
139154
}

app/src/desktopMain/kotlin/com/crosspaste/ui/DesktopScreenProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import com.crosspaste.ui.paste.PasteExportContentView
4444
import com.crosspaste.ui.paste.PasteImportContentView
4545
import com.crosspaste.ui.paste.createPasteDataScope
4646
import com.crosspaste.ui.paste.edit.PasteTextEditContentView
47+
import com.crosspaste.ui.settings.DesktopNetworkSettingsContentView
4748
import com.crosspaste.ui.settings.DesktopPasteboardSettingsContentView
48-
import com.crosspaste.ui.settings.NetworkSettingsContentView
4949
import com.crosspaste.ui.settings.PasteboardSettingsContentView
5050
import com.crosspaste.ui.settings.SettingsContentView
5151
import com.crosspaste.ui.settings.ShortcutKeysContentView
@@ -357,7 +357,7 @@ class DesktopScreenProvider(
357357
@Composable
358358
private fun NetworkSettingsScreen() {
359359
ScreenLayout {
360-
NetworkSettingsContentView()
360+
DesktopNetworkSettingsContentView()
361361
}
362362
}
363363

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.crosspaste.ui.settings
2+
3+
import androidx.compose.foundation.layout.padding
4+
import androidx.compose.material.icons.Icons
5+
import androidx.compose.material.icons.filled.Share
6+
import androidx.compose.material3.HorizontalDivider
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.collectAsState
9+
import androidx.compose.runtime.getValue
10+
import androidx.compose.ui.Modifier
11+
import com.crosspaste.config.DesktopConfigManager
12+
import com.crosspaste.ui.theme.AppUISize.xxxxLarge
13+
import org.koin.compose.koinInject
14+
15+
@Composable
16+
fun DesktopNetworkSettingsContentView() {
17+
NetworkSettingsContentView(
18+
syncExtContent = {
19+
DesktopSyncExtContent()
20+
},
21+
)
22+
}
23+
24+
@Composable
25+
private fun DesktopSyncExtContent() {
26+
val configManager = koinInject<DesktopConfigManager>()
27+
28+
val config by configManager.config.collectAsState()
29+
30+
HorizontalDivider(modifier = Modifier.padding(start = xxxxLarge))
31+
SettingListSwitchItem(
32+
title = "enable_clipboard_relay",
33+
icon = Icons.Default.Share,
34+
checked = config.enableClipboardRelay,
35+
) { enableClipboardRelay ->
36+
configManager.updateConfig("enableClipboardRelay", enableClipboardRelay)
37+
}
38+
}

0 commit comments

Comments
 (0)