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.PRIVATE6970fun relayInForce(mode: SyncMode, storedRelayUrl: String): RelayDecision = when {71 mode == SyncMode.OFF -> RelayDecision.Refuse(SyncOutcomeReason.SYNC_DISABLED)72 isPrivateRelayUnconfigured(mode, storedRelayUrl) ->73 RelayDecision.Refuse(SyncOutcomeReason.PRIVATE_RELAY_UNSET)74 mode == SyncMode.PUBLIC -> RelayDecision.Dial(Rendezvous.DEFAULT_RELAY_URL)75 else -> RelayDecision.Dial(storedRelayUrl)76}77787980818283@Singleton84class RelaySettingsStore @Inject constructor(85 @ApplicationContext context: Context,86) {87 private val prefs = context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)8889 fun syncMode(): SyncMode {90 SyncMode.parse(prefs.getString(KEY_MODE, null))?.let { return it }91 92 93 94 95 return when {96 prefs.getBoolean(LEGACY_KEY_LAN_ONLY, false) -> SyncMode.OFF97 storedRelayUrl() != Rendezvous.DEFAULT_RELAY_URL -> SyncMode.PRIVATE98 else -> SyncMode.PUBLIC99 }100 }101102 fun setSyncMode(mode: SyncMode) {103 prefs.edit().putString(KEY_MODE, mode.wire()).apply()104 }105106 107 fun relayInForce(): RelayDecision = relayInForce(syncMode(), storedRelayUrl())108109 110111112113114 fun privateRelayUrl(): String =115 storedRelayUrl().takeIf { it != Rendezvous.DEFAULT_RELAY_URL }.orEmpty()116117 118119120121122123124125 fun setRelayUrl(input: String): String {126 val normalized = Rendezvous.normalizeRelayUrl(input)127 prefs.edit()128 .putString(KEY_URL, normalized)129 .putString(KEY_MODE, modeForRelayUrl(normalized).wire())130 .apply()131 return normalized132 }133134 private fun storedRelayUrl(): String = prefs.getString(KEY_URL, null)135 ?.let { runCatching { Rendezvous.normalizeRelayUrl(it) }.getOrNull() }136 ?: Rendezvous.DEFAULT_RELAY_URL137138 companion object {139 const val PREFS_FILE = "peach_relay_settings"140 const val KEY_URL = "relay_url"141 const val KEY_MODE = "sync_mode"142 143 const val LEGACY_KEY_LAN_ONLY = "lan_only"144145 fun readRelayUrl(context: Context): String =146 context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)147 .getString(KEY_URL, null)148 ?.let { runCatching { Rendezvous.normalizeRelayUrl(it) }.getOrNull() }149 ?: Rendezvous.DEFAULT_RELAY_URL150 }151}