feat: add export button to relay settings screen
Adds a share icon button in the top bar of the relay settings screen that exports all relay configurations (except Connected Relays) as human-readable text via Android's share intent. Each section includes a header and description, with one relay URL per line. https://claude.ai/code/session_013PhuahpBFh6djVHzSsmNKM
This commit is contained in:
+6
@@ -43,6 +43,7 @@ fun ActionTopBar(
|
|||||||
isActive: () -> Boolean = { true },
|
isActive: () -> Boolean = { true },
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onPost: () -> Unit,
|
onPost: () -> Unit,
|
||||||
|
additionalActions: @Composable (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
ShorterTopAppBar(
|
ShorterTopAppBar(
|
||||||
title = {
|
title = {
|
||||||
@@ -63,6 +64,9 @@ fun ActionTopBar(
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
|
if (additionalActions != null) {
|
||||||
|
additionalActions()
|
||||||
|
}
|
||||||
Button(
|
Button(
|
||||||
modifier = HalfHorzPadding,
|
modifier = HalfHorzPadding,
|
||||||
enabled = isActive(),
|
enabled = isActive(),
|
||||||
@@ -100,12 +104,14 @@ fun SavingTopBar(
|
|||||||
isActive: () -> Boolean = { true },
|
isActive: () -> Boolean = { true },
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onPost: () -> Unit,
|
onPost: () -> Unit,
|
||||||
|
additionalActions: @Composable (() -> Unit)? = null,
|
||||||
) = ActionTopBar(
|
) = ActionTopBar(
|
||||||
titleRes = titleRes,
|
titleRes = titleRes,
|
||||||
postRes = R.string.save,
|
postRes = R.string.save,
|
||||||
isActive = isActive,
|
isActive = isActive,
|
||||||
onCancel = onCancel,
|
onCancel = onCancel,
|
||||||
onPost = onPost,
|
onPost = onPost,
|
||||||
|
additionalActions = additionalActions,
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|||||||
+193
@@ -20,6 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
@@ -27,7 +29,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Share
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
@@ -36,6 +42,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -77,6 +84,7 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||||
import com.vitorpamplona.amethyst.ui.theme.SettingsCategoryFirstModifier
|
import com.vitorpamplona.amethyst.ui.theme.SettingsCategoryFirstModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.SettingsCategorySpacingModifier
|
import com.vitorpamplona.amethyst.ui.theme.SettingsCategorySpacingModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfo
|
||||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -175,10 +183,36 @@ fun MappedAllRelayListView(
|
|||||||
val proxyRelays by proxyViewModel.relays.collectAsStateWithLifecycle()
|
val proxyRelays by proxyViewModel.relays.collectAsStateWithLifecycle()
|
||||||
val relayFeedsFeedState by relayFeedsViewModel.relays.collectAsStateWithLifecycle()
|
val relayFeedsFeedState by relayFeedsViewModel.relays.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
SavingTopBar(
|
SavingTopBar(
|
||||||
titleRes = R.string.relay_settings,
|
titleRes = R.string.relay_settings,
|
||||||
|
additionalActions = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
exportRelaySettings(
|
||||||
|
context = context,
|
||||||
|
homeRelays = homeFeedState,
|
||||||
|
notifRelays = notifFeedState,
|
||||||
|
dmRelays = dmFeedState,
|
||||||
|
privateOutboxRelays = privateOutboxFeedState,
|
||||||
|
proxyRelays = proxyRelays,
|
||||||
|
broadcastRelays = broadcastRelays,
|
||||||
|
indexerRelays = indexerRelays,
|
||||||
|
searchRelays = searchFeedState,
|
||||||
|
localRelays = localFeedState,
|
||||||
|
trustedRelays = trustedFeedState,
|
||||||
|
favoriteRelays = relayFeedsFeedState,
|
||||||
|
blockedRelays = blockedFeedState,
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Share,
|
||||||
|
contentDescription = stringRes(R.string.export_relay_settings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
onCancel = {
|
onCancel = {
|
||||||
dmViewModel.clear()
|
dmViewModel.clear()
|
||||||
nip65ViewModel.clear()
|
nip65ViewModel.clear()
|
||||||
@@ -447,3 +481,162 @@ fun SettingsCategoryWithButton(
|
|||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatRelaySection(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
relays: List<BasicRelaySetupInfo>,
|
||||||
|
builder: StringBuilder,
|
||||||
|
) {
|
||||||
|
if (relays.isEmpty()) return
|
||||||
|
builder.appendLine("## $title")
|
||||||
|
builder.appendLine("# $description")
|
||||||
|
builder.appendLine()
|
||||||
|
relays.forEach { relay ->
|
||||||
|
builder.appendLine(relay.relay.url)
|
||||||
|
}
|
||||||
|
builder.appendLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildRelaySettingsText(
|
||||||
|
context: Context,
|
||||||
|
homeRelays: List<BasicRelaySetupInfo>,
|
||||||
|
notifRelays: List<BasicRelaySetupInfo>,
|
||||||
|
dmRelays: List<BasicRelaySetupInfo>,
|
||||||
|
privateOutboxRelays: List<BasicRelaySetupInfo>,
|
||||||
|
proxyRelays: List<BasicRelaySetupInfo>,
|
||||||
|
broadcastRelays: List<BasicRelaySetupInfo>,
|
||||||
|
indexerRelays: List<BasicRelaySetupInfo>,
|
||||||
|
searchRelays: List<BasicRelaySetupInfo>,
|
||||||
|
localRelays: List<BasicRelaySetupInfo>,
|
||||||
|
trustedRelays: List<BasicRelaySetupInfo>,
|
||||||
|
favoriteRelays: List<BasicRelaySetupInfo>,
|
||||||
|
blockedRelays: List<BasicRelaySetupInfo>,
|
||||||
|
): String {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
builder.appendLine("# ${context.getString(R.string.relay_settings)}")
|
||||||
|
builder.appendLine()
|
||||||
|
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.public_home_section),
|
||||||
|
context.getString(R.string.public_home_section_explainer),
|
||||||
|
homeRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.public_notif_section),
|
||||||
|
context.getString(R.string.public_notif_section_explainer),
|
||||||
|
notifRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.private_inbox_section),
|
||||||
|
context.getString(R.string.private_inbox_section_explainer),
|
||||||
|
dmRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.private_outbox_section),
|
||||||
|
context.getString(R.string.private_outbox_section_explainer),
|
||||||
|
privateOutboxRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.proxy_section),
|
||||||
|
context.getString(R.string.proxy_section_explainer),
|
||||||
|
proxyRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.broadcast_section),
|
||||||
|
context.getString(R.string.broadcast_section_explainer),
|
||||||
|
broadcastRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.indexer_section),
|
||||||
|
context.getString(R.string.indexer_section_explainer),
|
||||||
|
indexerRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.search_section),
|
||||||
|
context.getString(R.string.search_section_explainer),
|
||||||
|
searchRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.local_section),
|
||||||
|
context.getString(R.string.local_section_explainer),
|
||||||
|
localRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.trusted_section),
|
||||||
|
context.getString(R.string.trusted_section_explainer),
|
||||||
|
trustedRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.favorite_section),
|
||||||
|
context.getString(R.string.favorite_section_explainer),
|
||||||
|
favoriteRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
formatRelaySection(
|
||||||
|
context.getString(R.string.blocked_section),
|
||||||
|
context.getString(R.string.blocked_section_explainer),
|
||||||
|
blockedRelays,
|
||||||
|
builder,
|
||||||
|
)
|
||||||
|
|
||||||
|
return builder.toString().trimEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun exportRelaySettings(
|
||||||
|
context: Context,
|
||||||
|
homeRelays: List<BasicRelaySetupInfo>,
|
||||||
|
notifRelays: List<BasicRelaySetupInfo>,
|
||||||
|
dmRelays: List<BasicRelaySetupInfo>,
|
||||||
|
privateOutboxRelays: List<BasicRelaySetupInfo>,
|
||||||
|
proxyRelays: List<BasicRelaySetupInfo>,
|
||||||
|
broadcastRelays: List<BasicRelaySetupInfo>,
|
||||||
|
indexerRelays: List<BasicRelaySetupInfo>,
|
||||||
|
searchRelays: List<BasicRelaySetupInfo>,
|
||||||
|
localRelays: List<BasicRelaySetupInfo>,
|
||||||
|
trustedRelays: List<BasicRelaySetupInfo>,
|
||||||
|
favoriteRelays: List<BasicRelaySetupInfo>,
|
||||||
|
blockedRelays: List<BasicRelaySetupInfo>,
|
||||||
|
) {
|
||||||
|
val text =
|
||||||
|
buildRelaySettingsText(
|
||||||
|
context,
|
||||||
|
homeRelays,
|
||||||
|
notifRelays,
|
||||||
|
dmRelays,
|
||||||
|
privateOutboxRelays,
|
||||||
|
proxyRelays,
|
||||||
|
broadcastRelays,
|
||||||
|
indexerRelays,
|
||||||
|
searchRelays,
|
||||||
|
localRelays,
|
||||||
|
trustedRelays,
|
||||||
|
favoriteRelays,
|
||||||
|
blockedRelays,
|
||||||
|
)
|
||||||
|
|
||||||
|
val sendIntent =
|
||||||
|
Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(Intent.EXTRA_TEXT, text)
|
||||||
|
putExtra(Intent.EXTRA_TITLE, context.getString(R.string.export_relay_settings))
|
||||||
|
}
|
||||||
|
|
||||||
|
val shareIntent =
|
||||||
|
Intent.createChooser(
|
||||||
|
sendIntent,
|
||||||
|
context.getString(R.string.export_relay_settings),
|
||||||
|
)
|
||||||
|
context.startActivity(shareIntent)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1379,6 +1379,8 @@
|
|||||||
<string name="blocked_section">Blocked Relays</string>
|
<string name="blocked_section">Blocked Relays</string>
|
||||||
<string name="blocked_section_explainer">Amethyst will never connect to these relays</string>
|
<string name="blocked_section_explainer">Amethyst will never connect to these relays</string>
|
||||||
|
|
||||||
|
<string name="export_relay_settings">Export relay settings</string>
|
||||||
|
|
||||||
<string name="zap_the_devs_title">Zap the Devs!</string>
|
<string name="zap_the_devs_title">Zap the Devs!</string>
|
||||||
<string name="zap_the_devs_description">Your donation helps us make a difference. Every sat counts!</string>
|
<string name="zap_the_devs_description">Your donation helps us make a difference. Every sat counts!</string>
|
||||||
<string name="donate_now">Donate Now</string>
|
<string name="donate_now">Donate Now</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user