1package com.peachpasswords.sync23import android.content.Context4import dagger.hilt.android.qualifiers.ApplicationContext5import javax.inject.Inject6import javax.inject.Singleton78910111213141516171819enum class SyncMode {20 PUBLIC,21 PRIVATE,22 OFF,23 ;2425 fun wire(): String = when (this) {26 PUBLIC -> "public"27 PRIVATE -> "private"28 OFF -> "off"29 }3031 companion object {32 fun parse(raw: String?): SyncMode? = when (raw) {33 "public" -> PUBLIC34 "private" -> PRIVATE35 "off" -> OFF36 else -> null37 }38 }39}40414243444546sealed interface RelayDecision {47 @JvmInline48 value class Dial(val relayUrl: String) : RelayDecision49 data class Refuse(val reason: SyncOutcomeReason) : RelayDecision50}5152535455565758fun isPrivateRelayUnconfigured(mode: SyncMode, storedRelayUrl: String): Boolean =59 mode == SyncMode.PRIVATE && storedRelayUrl == Rendezvous.DEFAULT_RELAY_URL6061626364656667fun modeForRelayUrl(normalizedRelayUrl: String): SyncMode =68 if (normalizedRelayUrl == Rendezvous.DEFAULT_RELAY_URL) SyncMode.PUBLIC else SyncMode.PRIVATE69707172737475fun privateRelayUrlOf(storedRelayUrl: String): String =76 storedRelayUrl.takeIf { it != Rendezvous.DEFAULT_RELAY_URL }.orEmpty()7778fun relayInForce(mode: SyncMode, storedRelayUrl: String): RelayDecision = when {79 mode == SyncMode.OFF -> RelayDecision.Refuse(SyncOutcomeReason.SYNC_DISABLED)80 isPrivateRelayUnconfigured(mode, storedRelayUrl) ->81 RelayDecision.Refuse(SyncOutcomeReason.PRIVATE_RELAY_UNSET)82 mode == SyncMode.PUBLIC -> RelayDecision.Dial(Rendezvous.DEFAULT_RELAY_URL)83 else -> RelayDecision.Dial(storedRelayUrl)84}85868788899091@Singleton92class RelaySettingsStore @Inject constructor(93 @ApplicationContext context: Context,94 private val devices: PairedDeviceStore,95) {96 private val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)9798 fun syncMode(): SyncMode {99 SyncMode.parse(prefs.getString(KEY_MODE, null))?.let { return it }100 101 102 103 104 return when {105 prefs.getBoolean(LEGACY_KEY_LAN_ONLY, false) -> SyncMode.OFF106 storedRelayUrl() != Rendezvous.DEFAULT_RELAY_URL -> SyncMode.PRIVATE107 else -> SyncMode.PUBLIC108 }109 }110111 112113114115116117 fun setSyncMode(mode: SyncMode) {118 devices.propagateRelaySetting(mode, storedRelayUrl())119 prefs.edit().putString(KEY_MODE, mode.wire()).apply()120 }121122 123 fun relayInForce(): RelayDecision = relayInForce(syncMode(), storedRelayUrl())124125 126 fun privateRelayUrl(): String = privateRelayUrlOf(storedRelayUrl())127128 129130131132133134135136 fun setRelayUrl(input: String): String {137 val normalized = Rendezvous.normalizeRelayUrl(input)138 devices.propagateRelaySetting(modeForRelayUrl(normalized), normalized)139 prefs.edit()140 .putString(KEY_URL, normalized)141 .putString(KEY_MODE, modeForRelayUrl(normalized).wire())142 .apply()143 return normalized144 }145146 private fun storedRelayUrl(): String = prefs.getString(KEY_URL, null)147 ?.let { runCatching { Rendezvous.normalizeRelayUrl(it) }.getOrNull() }148 ?: Rendezvous.DEFAULT_RELAY_URL149150 companion object {151 const val PREFS_FILE = "peach_relay_settings"152 const val KEY_URL = "relay_url"153 const val KEY_MODE = "sync_mode"154 155 const val LEGACY_KEY_LAN_ONLY = "lan_only"156157 fun readRelayUrl(context: Context): String =158 context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)159 .getString(KEY_URL, null)160 ?.let { runCatching { Rendezvous.normalizeRelayUrl(it) }.getOrNull() }161 ?: Rendezvous.DEFAULT_RELAY_URL162 }163}