diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt index d221f48fb..a56be7070 100644 --- a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt +++ b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/ResolvedUserFlags.kt @@ -37,6 +37,8 @@ data class ResolvedUserFlags( val requireCoinbaseEmailVerification: ResolvedFlag, val tipPresets: ResolvedFlag>, val usernameMinBalance: ResolvedFlag, + val messageEditWindow: ResolvedFlag, + val messageDeleteWindow: ResolvedFlag, ) internal fun UserFlags.resolve(overrides: Overrides): ResolvedUserFlags = ResolvedUserFlags( @@ -56,4 +58,7 @@ internal fun UserFlags.resolve(overrides: Overrides): ResolvedUserFlags = Resolv requireCoinbaseEmailVerification = ResolvedFlag(requireCoinbaseEmailVerification, overrides.requireCoinbaseEmailVerification), tipPresets = ResolvedFlag(tipPresets, FieldOverride.None), usernameMinBalance = ResolvedFlag(usernameMinBalance, FieldOverride.None), + // Read-only for now — no debug override support until the edit/delete UI lands. + messageEditWindow = ResolvedFlag(messageEditWindow, FieldOverride.None), + messageDeleteWindow = ResolvedFlag(messageDeleteWindow, FieldOverride.None), ) \ No newline at end of file diff --git a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt index 805c5f7cd..395b25986 100644 --- a/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt +++ b/apps/flipcash/shared/userflags/src/main/kotlin/com/flipcash/app/userflags/UserFlagsCoordinator.kt @@ -182,6 +182,8 @@ private data class CachedFlags( val requireCoinbaseEmailVerification: Boolean, val tipPresets: List = emptyList(), val usernameMinBalance: CachedFiat = CachedFiat(quarks = 0), + val messageEditWindowMillis: Long? = null, + val messageDeleteWindowMillis: Long? = null, ) { fun toDomain(): UserFlags = UserFlags( isStaff = isStaff, @@ -202,6 +204,8 @@ private data class CachedFlags( requireCoinbaseEmailVerification = requireCoinbaseEmailVerification, tipPresets = tipPresets.map { it.toDomain() }, usernameMinBalance = usernameMinBalance.toDomain(), + messageEditWindow = messageEditWindowMillis?.milliseconds, + messageDeleteWindow = messageDeleteWindowMillis?.milliseconds, ) companion object { @@ -226,6 +230,8 @@ private data class CachedFlags( requireCoinbaseEmailVerification = flags.requireCoinbaseEmailVerification, tipPresets = flags.tipPresets.map { CachedTipPresets.fromDomain(it) }, usernameMinBalance = CachedFiat.fromDomain(flags.usernameMinBalance), + messageEditWindowMillis = flags.messageEditWindow?.inWholeMilliseconds, + messageDeleteWindowMillis = flags.messageDeleteWindow?.inWholeMilliseconds, ) } } diff --git a/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt b/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt index ecbb86909..3c6441187 100644 --- a/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt +++ b/apps/flipcash/shared/userflags/src/test/kotlin/com/flipcash/app/userflags/ResolvedUserFlagsTest.kt @@ -36,6 +36,8 @@ class ResolvedUserFlagsTest { assertEquals(true, resolved.requireCoinbaseEmailVerification.effectiveValue) assertEquals(ServerFlags.tipPresets, resolved.tipPresets.effectiveValue) assertEquals(Fiat(quarks = 5_000_000L), resolved.usernameMinBalance.effectiveValue) + assertEquals(15.seconds, resolved.messageEditWindow.effectiveValue) + assertEquals(60.seconds, resolved.messageDeleteWindow.effectiveValue) } @Test @@ -142,6 +144,8 @@ private val ServerFlags = UserFlags( TipPresets(region = "US", minimum = 1.0, low = 2.0, medium = 3.0, high = 4.0), ), usernameMinBalance = Fiat(quarks = 5_000_000L), + messageEditWindow = 15.seconds, + messageDeleteWindow = 60.seconds, ) // Every value here differs from the matching server value above, so a flag that reads the diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7d8c5155..035cc0725 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -69,7 +69,7 @@ protovalidate-kt = "0.1.1" # 0.3.0 is the first release of either package to ship R8 keep rules for its generated # messages, which is what lets proguard-rules.pro drop its own. ocp-client-protocol = "0.3.0" -flipcash2-client-protocol = "0.3.0" +flipcash2-client-protocol = "0.4.0" # The Android port is the ONLY libphonenumber this app depends on, deliberately. Google's # `com.googlecode` artifact used to sit alongside it; the two ship separate copies of the metadata, diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt index 0156fce55..8457bfbe7 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/internal/domain/UserFlagsMapper.kt @@ -32,6 +32,12 @@ internal class UserFlagsMapper @Inject constructor(): requireCoinbaseEmailVerification = from.requireCoinbaseEmailVerification, tipPresets = from.tipPresetsList.map { it.toDomain() }, usernameMinBalance = Fiat(quarks = from.usernameMinBalance), + messageEditWindow = if (from.hasMessageEditWindow()) { + from.messageEditWindow.seconds.toDuration(DurationUnit.SECONDS) + } else null, + messageDeleteWindow = if (from.hasMessageDeleteWindow()) { + from.messageDeleteWindow.seconds.toDuration(DurationUnit.SECONDS) + } else null, ) } } diff --git a/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt b/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt index 6b4f46e72..a69afbae0 100644 --- a/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt +++ b/services/flipcash/src/main/kotlin/com/flipcash/services/models/UserFlags.kt @@ -23,6 +23,12 @@ data class UserFlags( val tipPresets: List, // USDF that must be held across all currencies before a username can be set. val usernameMinBalance: Fiat, + // Duration after message creation when a message can be edited. Absent when + // the server hasn't set a window (not the same as a zero-length window). + val messageEditWindow: Duration?, + // Duration after message creation when a message can be deleted. Absent when + // the server hasn't set a window (not the same as a zero-length window). + val messageDeleteWindow: Duration?, ) { companion object { val Default = UserFlags( @@ -42,6 +48,8 @@ data class UserFlags( requireCoinbaseEmailVerification = false, tipPresets = emptyList(), usernameMinBalance = Fiat.Zero, + messageEditWindow = null, + messageDeleteWindow = null, ) } } \ No newline at end of file diff --git a/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt index 12f85c3ed..53c4d664d 100644 --- a/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt +++ b/services/flipcash/src/test/kotlin/com/flipcash/services/internal/domain/UserFlagsMapperTest.kt @@ -180,4 +180,27 @@ class UserFlagsMapperTest { assertTrue(result.tipPresets.isEmpty()) } + + @Test + fun `maps message edit and delete windows when set`() { + val proto = FlipcashAccountService.UserFlags.newBuilder() + .setMessageEditWindow(com.google.protobuf.Duration.newBuilder().setSeconds(15).build()) + .setMessageDeleteWindow(com.google.protobuf.Duration.newBuilder().setSeconds(60).build()) + .build() + + val result = mapper.map(proto) + + assertEquals(15.seconds, result.messageEditWindow) + assertEquals(60.seconds, result.messageDeleteWindow) + } + + @Test + fun `message edit and delete windows are null when unset, not zero`() { + val proto = userFlags { } + + val result = mapper.map(proto) + + assertNull(result.messageEditWindow) + assertNull(result.messageDeleteWindow) + } }