Adds a RelaySync filter by since/until dates.
This commit is contained in:
+39
@@ -23,9 +23,13 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.amethyst.model.Constants
|
||||
import com.vitorpamplona.amethyst.service.okhttp.DefaultContentTypeInterceptor
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.accessories.RelayLogger
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.auth.RelayAuthenticator
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.sockets.okhttp.BasicOkHttpWebSocket
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -38,6 +42,7 @@ import org.junit.runner.RunWith
|
||||
class EventSyncTest {
|
||||
companion object {
|
||||
val vitor = "wss://vitor.nostr1.com".normalizeRelayUrl()
|
||||
val fiatjaf = "wss://pyramid.fiatjaf.com".normalizeRelayUrl()
|
||||
val appScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||
|
||||
val rootClient =
|
||||
@@ -70,4 +75,38 @@ class EventSyncTest {
|
||||
|
||||
sync.runSync()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFiatjafSync() =
|
||||
runBlocking {
|
||||
val sync =
|
||||
EventSync(
|
||||
accountPubKey = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
relayDb = { listOf(fiatjaf) },
|
||||
outboxTargets = { setOf(vitor) },
|
||||
inboxTargets = { setOf(vitor) },
|
||||
dmTargets = { setOf(vitor) },
|
||||
clientBuilder = {
|
||||
val newClient = NostrClient(socketBuilder, appScope)
|
||||
val logger = RelayLogger(newClient, debugSending = true, debugReceiving = false)
|
||||
|
||||
val signer = NostrSignerInternal(KeyPair())
|
||||
|
||||
// Authenticates with relays.
|
||||
val auth =
|
||||
RelayAuthenticator(
|
||||
newClient,
|
||||
appScope,
|
||||
signWithAllLoggedInUsers = { authTemplate ->
|
||||
listOf(signer.sign(authTemplate))
|
||||
},
|
||||
)
|
||||
|
||||
newClient
|
||||
},
|
||||
scope = appScope,
|
||||
)
|
||||
|
||||
sync.runSync()
|
||||
}
|
||||
}
|
||||
|
||||
+34
-10
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync.Companion.MAX_ACTIVITY_LOG
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync.Companion.MAX_CONCURRENT_RELAYS
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync.Companion.RELAY_TIMEOUT_MS
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync.EventSync.LiveSyncActivity.SourceRelayInfo
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
@@ -106,14 +107,18 @@ class EventSync(
|
||||
val eventsSent: MutableStateFlow<Int>,
|
||||
val eventsReceived: MutableStateFlow<Int>,
|
||||
val eventsAccepted: MutableStateFlow<Int>,
|
||||
val sinceFilter: Long? = null,
|
||||
val untilFilter: Long? = null,
|
||||
) : SyncState() {
|
||||
constructor(relaysCompleted: Int, totalRelays: Int, eventsSent: Int, eventsReceived: Int, eventsAccepted: Int) :
|
||||
constructor(relaysCompleted: Int, totalRelays: Int, eventsSent: Int, eventsReceived: Int, eventsAccepted: Int, sinceFilter: Long? = null, untilFilter: Long? = null) :
|
||||
this(
|
||||
relaysCompleted = MutableStateFlow(relaysCompleted),
|
||||
totalRelays = MutableStateFlow(totalRelays),
|
||||
eventsSent = MutableStateFlow(eventsSent),
|
||||
eventsReceived = MutableStateFlow(eventsReceived),
|
||||
eventsAccepted = MutableStateFlow(eventsAccepted),
|
||||
sinceFilter = sinceFilter,
|
||||
untilFilter = untilFilter,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -122,10 +127,14 @@ class EventSync(
|
||||
val totalEventsSent: Int,
|
||||
val totalEventsAccepted: Int,
|
||||
val durationMs: Long,
|
||||
val sinceFilter: Long? = null,
|
||||
val untilFilter: Long? = null,
|
||||
) : SyncState()
|
||||
|
||||
data class Error(
|
||||
val message: String,
|
||||
val sinceFilter: Long? = null,
|
||||
val untilFilter: Long? = null,
|
||||
) : SyncState()
|
||||
}
|
||||
|
||||
@@ -282,14 +291,20 @@ class EventSync(
|
||||
// Control functions
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/** User-selected date range for the sync filter (epoch seconds). */
|
||||
val sinceSecs = MutableStateFlow<Long?>(null)
|
||||
val untilSecs = MutableStateFlow<Long?>(null)
|
||||
|
||||
private var syncJob: Job? = null
|
||||
|
||||
fun start() {
|
||||
if (_syncState.value is SyncState.Running) return
|
||||
_liveActivity.value = LiveSyncActivity()
|
||||
val capturedSince = sinceSecs.value
|
||||
val capturedUntil = untilSecs.value
|
||||
syncJob =
|
||||
scope.launch(Dispatchers.IO) {
|
||||
runSync()
|
||||
runSync(capturedSince, capturedUntil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +315,10 @@ class EventSync(
|
||||
// -------------------------------------------------------------------------
|
||||
// Sync logic
|
||||
// -------------------------------------------------------------------------
|
||||
suspend fun runSync() {
|
||||
suspend fun runSync(
|
||||
filterSince: Long? = null,
|
||||
filterUntil: Long? = null,
|
||||
) {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
_liveActivity.value = LiveSyncActivity()
|
||||
@@ -311,7 +329,7 @@ class EventSync(
|
||||
|
||||
if (relaysToProcess.isEmpty()) {
|
||||
_syncState.value =
|
||||
SyncState.Error("No known relays found. Browse some content first to discover relays.")
|
||||
SyncState.Error("No known relays found. Browse some content first to discover relays.", filterSince, filterUntil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -323,14 +341,14 @@ class EventSync(
|
||||
|
||||
val defaultFilters =
|
||||
buildList {
|
||||
if (outboxTargets.isNotEmpty()) add(Filter(authors = listOf(myPubKey)))
|
||||
if (outboxTargets.isNotEmpty()) add(Filter(authors = listOf(myPubKey), since = filterSince, until = filterUntil))
|
||||
if (inboxTargets.isNotEmpty() || dmTargets.isNotEmpty()) {
|
||||
add(Filter(tags = mapOf("p" to listOf(myPubKey))))
|
||||
add(Filter(tags = mapOf("p" to listOf(myPubKey)), since = filterSince, until = filterUntil))
|
||||
}
|
||||
}
|
||||
|
||||
if (defaultFilters.isEmpty()) {
|
||||
_syncState.value = SyncState.Error("No outbox, inbox, or DM relays configured.")
|
||||
_syncState.value = SyncState.Error("No outbox, inbox, or DM relays configured.", filterSince, filterUntil)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -342,9 +360,9 @@ class EventSync(
|
||||
defaultFilters
|
||||
} else {
|
||||
buildList {
|
||||
if (it !in outboxTargets) add(Filter(authors = listOf(myPubKey)))
|
||||
if (it !in outboxTargets) add(Filter(authors = listOf(myPubKey), since = filterSince, until = filterUntil))
|
||||
if (it !in inboxTargets && it !in dmTargets) {
|
||||
add(Filter(tags = mapOf("p" to listOf(myPubKey))))
|
||||
add(Filter(tags = mapOf("p" to listOf(myPubKey)), since = filterSince, until = filterUntil))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,6 +391,8 @@ class EventSync(
|
||||
eventsSent = 0,
|
||||
eventsReceived = 0,
|
||||
eventsAccepted = 0,
|
||||
sinceFilter = filterSince,
|
||||
untilFilter = filterUntil,
|
||||
)
|
||||
|
||||
val okListener =
|
||||
@@ -563,6 +583,8 @@ class EventSync(
|
||||
totalEventsSent = runningState.eventsSent.value,
|
||||
totalEventsAccepted = runningState.eventsAccepted.value,
|
||||
durationMs = System.currentTimeMillis() - startTime,
|
||||
sinceFilter = filterSince,
|
||||
untilFilter = filterUntil,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
_syncState.value =
|
||||
@@ -571,10 +593,12 @@ class EventSync(
|
||||
totalEventsSent = runningState.eventsSent.value,
|
||||
totalEventsAccepted = runningState.eventsAccepted.value,
|
||||
durationMs = System.currentTimeMillis() - startTime,
|
||||
sinceFilter = filterSince,
|
||||
untilFilter = filterUntil,
|
||||
)
|
||||
|
||||
if (e is CancellationException) throw e
|
||||
_syncState.value = SyncState.Error(e.message ?: "Unknown error")
|
||||
_syncState.value = SyncState.Error(e.message ?: "Unknown error", filterSince, filterUntil)
|
||||
} finally {
|
||||
client.unsubscribe(okListener)
|
||||
}
|
||||
|
||||
+164
-9
@@ -21,6 +21,7 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.eventsync
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -43,10 +44,12 @@ import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.RangeSlider
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -54,6 +57,8 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
@@ -70,6 +75,9 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun EventSyncScreen(
|
||||
@@ -94,6 +102,8 @@ fun EventSyncScreen(
|
||||
syncState = syncState,
|
||||
liveActivity = liveActivity,
|
||||
isMobileOrMetered = isMobileOrMetered,
|
||||
onSinceChanged = { syncViewModel.sinceSecs.value = it },
|
||||
onUntilChanged = { syncViewModel.untilSecs.value = it },
|
||||
onStart = syncViewModel::start,
|
||||
onCancel = syncViewModel::cancel,
|
||||
)
|
||||
@@ -106,6 +116,8 @@ fun EventScreenBody(
|
||||
syncState: EventSync.SyncState,
|
||||
liveActivity: EventSync.LiveSyncActivity,
|
||||
isMobileOrMetered: Boolean = false,
|
||||
onSinceChanged: (Long?) -> Unit = {},
|
||||
onUntilChanged: (Long?) -> Unit = {},
|
||||
onStart: () -> Unit = {},
|
||||
onCancel: () -> Unit = {},
|
||||
) {
|
||||
@@ -119,10 +131,10 @@ fun EventScreenBody(
|
||||
item {
|
||||
// ---- Progress / Status area ----
|
||||
when (syncState) {
|
||||
is EventSync.SyncState.Idle -> ExplanationCard(isMobileOrMetered, onStart)
|
||||
is EventSync.SyncState.Idle -> ExplanationCard(isMobileOrMetered, onSinceChanged, onUntilChanged, onStart)
|
||||
is EventSync.SyncState.Running -> SyncProgressCard(state = syncState, onCancel)
|
||||
is EventSync.SyncState.Done -> DoneCard(state = syncState, isMobileOrMetered, onStart)
|
||||
is EventSync.SyncState.Error -> ErrorCard(syncState.message, isMobileOrMetered, onStart)
|
||||
is EventSync.SyncState.Done -> DoneCard(state = syncState, isMobileOrMetered, onSinceChanged, onUntilChanged, onStart)
|
||||
is EventSync.SyncState.Error -> ErrorCard(syncState, isMobileOrMetered, onSinceChanged, onUntilChanged, onStart)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +243,9 @@ private fun StartSyncButton(
|
||||
@Composable
|
||||
private fun ExplanationCard(
|
||||
isMobileOrMetered: Boolean,
|
||||
onStart: () -> Unit,
|
||||
onSinceChanged: (Long?) -> Unit = {},
|
||||
onUntilChanged: (Long?) -> Unit = {},
|
||||
onStart: () -> Unit = {},
|
||||
) {
|
||||
// ---- Explanation card ----
|
||||
Card(
|
||||
@@ -256,6 +270,7 @@ private fun ExplanationCard(
|
||||
Spacer(Modifier.height(4.dp))
|
||||
StepRow(number = "3", text = stringRes(R.string.event_sync_step3))
|
||||
Spacer(Modifier.height(10.dp))
|
||||
|
||||
// ---- WiFi warning ----
|
||||
if (isMobileOrMetered) {
|
||||
Card(
|
||||
@@ -274,6 +289,12 @@ private fun ExplanationCard(
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
}
|
||||
DateRangeFilterCard(
|
||||
backgroundColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
onSinceChanged = onSinceChanged,
|
||||
onUntilChanged = onUntilChanged,
|
||||
)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
StartSyncButton(isMobileOrMetered = isMobileOrMetered, onStart)
|
||||
}
|
||||
}
|
||||
@@ -352,6 +373,8 @@ private fun RelayStatement(state: EventSync.SyncState.Running) {
|
||||
private fun DoneCard(
|
||||
state: EventSync.SyncState.Done,
|
||||
isMobileOrMetered: Boolean = false,
|
||||
onSinceChanged: (Long?) -> Unit = {},
|
||||
onUntilChanged: (Long?) -> Unit = {},
|
||||
onStart: () -> Unit = { },
|
||||
) {
|
||||
Card(
|
||||
@@ -387,6 +410,16 @@ private fun DoneCard(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.7f),
|
||||
)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
|
||||
DateRangeFilterCard(
|
||||
backgroundColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
onSinceStartValue = state.sinceFilter,
|
||||
onUntilStartValue = state.untilFilter,
|
||||
onSinceChanged = onSinceChanged,
|
||||
onUntilChanged = onUntilChanged,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
StartSyncButton(isMobileOrMetered, onStart)
|
||||
}
|
||||
@@ -395,8 +428,10 @@ private fun DoneCard(
|
||||
|
||||
@Composable
|
||||
private fun ErrorCard(
|
||||
message: String,
|
||||
state: EventSync.SyncState.Error,
|
||||
isMobileOrMetered: Boolean = false,
|
||||
onSinceChanged: (Long?) -> Unit = {},
|
||||
onUntilChanged: (Long?) -> Unit = {},
|
||||
onStart: () -> Unit = {},
|
||||
) {
|
||||
Card(
|
||||
@@ -415,10 +450,19 @@ private fun ErrorCard(
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
text = message,
|
||||
text = state.message,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer,
|
||||
)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
|
||||
DateRangeFilterCard(
|
||||
backgroundColor = MaterialTheme.colorScheme.errorContainer,
|
||||
onSinceStartValue = state.sinceFilter,
|
||||
onUntilStartValue = state.untilFilter,
|
||||
onSinceChanged = onSinceChanged,
|
||||
onUntilChanged = onUntilChanged,
|
||||
)
|
||||
Spacer(Modifier.height(10.dp))
|
||||
StartSyncButton(isMobileOrMetered, onStart)
|
||||
}
|
||||
@@ -660,6 +704,117 @@ private fun ActivityLogRow(info: EventSync.LiveSyncActivity.SourceRelayInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Oldest possible "since" — roughly 2020-01-01 in epoch seconds. */
|
||||
private const val EPOCH_2023 = 1_672_549_201L
|
||||
|
||||
@Composable
|
||||
private fun DateRangeFilterCard(
|
||||
backgroundColor: Color,
|
||||
onSinceStartValue: Long? = null,
|
||||
onUntilStartValue: Long? = null,
|
||||
onSinceChanged: (Long?) -> Unit = {},
|
||||
onUntilChanged: (Long?) -> Unit = {},
|
||||
) {
|
||||
val now = remember { System.currentTimeMillis() / 1000 }
|
||||
|
||||
// Slider range: 0f = EPOCH_2020, 1f = now
|
||||
val range = (now - EPOCH_2023).toFloat()
|
||||
|
||||
// Initialise from lastSyncTimestamp when available, otherwise full range
|
||||
var sliderValues by remember(onSinceStartValue, onUntilStartValue) {
|
||||
val initialSince =
|
||||
if (onSinceStartValue != null) {
|
||||
((onSinceStartValue - EPOCH_2023) / range).coerceIn(0f, 1f)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
|
||||
val finalSince =
|
||||
if (onUntilStartValue != null) {
|
||||
((onUntilStartValue - EPOCH_2023) / range).coerceIn(0f, 1f)
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
mutableStateOf(initialSince..finalSince)
|
||||
}
|
||||
|
||||
// Derive actual timestamps
|
||||
val sinceEpoch = (EPOCH_2023 + (sliderValues.start * range).toLong())
|
||||
val untilEpoch = (EPOCH_2023 + (sliderValues.endInclusive * range).toLong())
|
||||
|
||||
// Fire callbacks
|
||||
val effectiveSince = if (sliderValues.start <= 0.001f) null else sinceEpoch
|
||||
val effectiveUntil = if (sliderValues.endInclusive >= 0.999f) null else untilEpoch
|
||||
|
||||
// Notify parent on each change
|
||||
LaunchedEffect(effectiveSince) { onSinceChanged(effectiveSince) }
|
||||
LaunchedEffect(effectiveUntil) { onUntilChanged(effectiveUntil) }
|
||||
|
||||
Card(
|
||||
modifier =
|
||||
Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(containerColor = backgroundColor),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 1.dp),
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringRes(R.string.event_sync_date_filter_title),
|
||||
style = MaterialTheme.typography.titleSmall,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(12.dp))
|
||||
|
||||
// Labels for current selection
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text =
|
||||
if (effectiveSince == null) {
|
||||
stringRes(R.string.event_sync_date_filter_all_time)
|
||||
} else {
|
||||
stringRes(R.string.event_sync_date_filter_since) + " " + formatEpochDate(sinceEpoch)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text =
|
||||
if (effectiveUntil == null) {
|
||||
stringRes(R.string.event_sync_date_filter_now)
|
||||
} else {
|
||||
stringRes(R.string.event_sync_date_filter_until) + " " + formatEpochDate(untilEpoch)
|
||||
},
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
// block left drawer and back gestures when sliding
|
||||
Modifier.pointerInput(Unit) {
|
||||
detectDragGestures { change, _ ->
|
||||
change.consume()
|
||||
}
|
||||
},
|
||||
) {
|
||||
RangeSlider(
|
||||
value = sliderValues,
|
||||
onValueChange = { sliderValues = it },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatEpochDate(epochSecs: Long): String {
|
||||
val sdf = SimpleDateFormat("MMM d, yyyy", Locale.getDefault())
|
||||
return sdf.format(Date(epochSecs * 1000))
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
@@ -747,7 +902,7 @@ private val previewActivity =
|
||||
@Preview
|
||||
fun IdleCardWifiPreview() {
|
||||
ThemeComparisonColumn {
|
||||
ExplanationCard(false, {})
|
||||
ExplanationCard(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,7 +910,7 @@ fun IdleCardWifiPreview() {
|
||||
@Preview
|
||||
fun IdleCardMobilePreview() {
|
||||
ThemeComparisonColumn {
|
||||
ExplanationCard(true, {})
|
||||
ExplanationCard(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,7 +952,7 @@ fun DoneCardPreview() {
|
||||
@Preview
|
||||
fun ErrorCardPreview() {
|
||||
ThemeComparisonColumn {
|
||||
ErrorCard(message = "No outbox, inbox, or DM relays configured.")
|
||||
ErrorCard(state = EventSync.SyncState.Error("No outbox, inbox, or DM relays configured."))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1912,4 +1912,12 @@
|
||||
<string name="attestor_proficiency_for_kinds">Proficient in verifying kinds: %1$s</string>
|
||||
<string name="attestation_attests_to">Attests to</string>
|
||||
<string name="attestation_requests_attestation_to">Requests attestation to</string>
|
||||
|
||||
<string name="event_sync_date_filter_title">Date Range</string>
|
||||
<string name="event_sync_date_filter_since">Since</string>
|
||||
<string name="event_sync_date_filter_until">Until</string>
|
||||
<string name="event_sync_date_filter_now">Now</string>
|
||||
<string name="event_sync_date_filter_all_time">All time</string>
|
||||
<string name="event_sync_date_filter_last_sync">Last sync: %1$s</string>
|
||||
<string name="event_sync_date_filter_since_last_sync">Since Last Sync</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user