Moves the account import idea to a route after login
This commit is contained in:
@@ -97,6 +97,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.ListOfPeopleList
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.FollowPackMetadataScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.FollowPackMetadataScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleListMetadataScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.list.metadata.PeopleListMetadataScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.memberEdit.FollowListAndPackAndUserScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser.ImportFollowListScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.NotificationScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages.NewPublicMessageScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages.NewPublicMessageScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.privacy.PrivacyOptionsScreen
|
||||||
@@ -177,6 +178,8 @@ fun AppNavigation(
|
|||||||
composableFromEnd<Route.Settings> { SettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Settings> { SettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.UserSettings> { UserSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.UserSettings> { UserSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.ReactionsSettings> { ReactionsSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.ReactionsSettings> { ReactionsSettingsScreen(accountViewModel, nav) }
|
||||||
|
composableFromEnd<Route.ImportFollows> { ImportFollowListScreen(accountViewModel, nav) }
|
||||||
|
|
||||||
composableFromEndArgs<Route.Nip47NWCSetup> { NIP47SetupScreen(accountViewModel, nav, it.nip47) }
|
composableFromEndArgs<Route.Nip47NWCSetup> { NIP47SetupScreen(accountViewModel, nav, it.nip47) }
|
||||||
composableFromEndArgs<Route.UpdateZapAmount> { UpdateZapAmountScreen(accountViewModel, nav, it.nip47) }
|
composableFromEndArgs<Route.UpdateZapAmount> { UpdateZapAmountScreen(accountViewModel, nav, it.nip47) }
|
||||||
composableFromEndArgs<Route.EditRelays> { AllRelayListScreen(accountViewModel, nav) }
|
composableFromEndArgs<Route.EditRelays> { AllRelayListScreen(accountViewModel, nav) }
|
||||||
|
|||||||
@@ -53,6 +53,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable object BookmarkGroups : Route()
|
@Serializable object BookmarkGroups : Route()
|
||||||
|
|
||||||
|
@Serializable object ImportFollows : Route()
|
||||||
|
|
||||||
@Serializable data class BookmarkGroupView(
|
@Serializable data class BookmarkGroupView(
|
||||||
val dTag: String,
|
val dTag: String,
|
||||||
val bookmarkType: BookmarkType,
|
val bookmarkType: BookmarkType,
|
||||||
|
|||||||
@@ -29,27 +29,16 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.service.followimport.Kind3EventData
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoggedInPage
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoggedInPage
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedOff.LoginOrSignupScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.signup.ImportFollowListSection
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.signup.ImportFollowListViewModel
|
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
|
||||||
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
|
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountScreen(accountSessionManager: AccountSessionManager) {
|
fun AccountScreen(accountSessionManager: AccountSessionManager) {
|
||||||
@@ -75,11 +64,7 @@ fun AccountScreen(accountSessionManager: AccountSessionManager) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
is AccountState.LoggedIn -> {
|
is AccountState.LoggedIn -> {
|
||||||
if (state.isNewAccount) {
|
LoggedInSetup(state, accountSessionManager)
|
||||||
NewAccountImportFollowsSetup(state.account, accountSessionManager)
|
|
||||||
} else {
|
|
||||||
LoggedInSetup(state, accountSessionManager)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,80 +124,3 @@ fun LoggedInSetup(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NewAccountImportFollowsSetup(
|
|
||||||
account: Account,
|
|
||||||
accountSessionManager: AccountSessionManager,
|
|
||||||
) {
|
|
||||||
val importViewModel: ImportFollowListViewModel = viewModel()
|
|
||||||
|
|
||||||
LaunchedEffect(account) {
|
|
||||||
importViewModel.configure(
|
|
||||||
fetchEvent = { kind, author, limit, onEvent ->
|
|
||||||
val filter =
|
|
||||||
Filter(
|
|
||||||
kinds = listOf(kind),
|
|
||||||
authors = listOf(author),
|
|
||||||
limit = limit,
|
|
||||||
)
|
|
||||||
val relayUrls =
|
|
||||||
listOf(
|
|
||||||
"wss://relay.damus.io",
|
|
||||||
"wss://nos.lol",
|
|
||||||
"wss://relay.nostr.band",
|
|
||||||
"wss://purplepag.es",
|
|
||||||
)
|
|
||||||
val filterMap =
|
|
||||||
relayUrls.associate { url ->
|
|
||||||
RelayUrlNormalizer.normalize(url) to listOf(filter)
|
|
||||||
}
|
|
||||||
val listener =
|
|
||||||
object : com.vitorpamplona.quartz.nip01Core.relay.client.reqs.IRequestListener {
|
|
||||||
override fun onEvent(
|
|
||||||
event: com.vitorpamplona.quartz.nip01Core.core.Event,
|
|
||||||
isLive: Boolean,
|
|
||||||
relay: com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl,
|
|
||||||
forFilters: List<Filter>?,
|
|
||||||
) {
|
|
||||||
if (event is ContactListEvent) {
|
|
||||||
onEvent(
|
|
||||||
Kind3EventData(
|
|
||||||
pTags =
|
|
||||||
event.tags
|
|
||||||
.filter { it.size >= 2 && it[0] == "p" }
|
|
||||||
.map { it.drop(1) },
|
|
||||||
createdAt = event.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val subId =
|
|
||||||
com.vitorpamplona.quartz.nip01Core.relay.client.single
|
|
||||||
.newSubId()
|
|
||||||
Amethyst.instance.client.openReqSubscription(subId, filterMap, listener)
|
|
||||||
AutoCloseable { Amethyst.instance.client.close(subId) }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Surface(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
color = MaterialTheme.colorScheme.background,
|
|
||||||
) {
|
|
||||||
ImportFollowListSection(
|
|
||||||
onFollowsApplied = { entries ->
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
for (entry in entries) {
|
|
||||||
val user = account.cache.getOrCreateUser(entry.pubkeyHex)
|
|
||||||
account.follow(user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSkip = { accountSessionManager.finishNewAccountSetup() },
|
|
||||||
onDone = { accountSessionManager.finishNewAccountSetup() },
|
|
||||||
viewModel = importViewModel,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-13
@@ -87,7 +87,6 @@ sealed class AccountState {
|
|||||||
class LoggedIn(
|
class LoggedIn(
|
||||||
val account: Account,
|
val account: Account,
|
||||||
var route: Route? = null,
|
var route: Route? = null,
|
||||||
val isNewAccount: Boolean = false,
|
|
||||||
) : AccountState()
|
) : AccountState()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,20 +183,10 @@ class AccountSessionManager(
|
|||||||
fun startUI(
|
fun startUI(
|
||||||
accountSettings: AccountSettings,
|
accountSettings: AccountSettings,
|
||||||
route: Route? = null,
|
route: Route? = null,
|
||||||
isNewAccount: Boolean = false,
|
|
||||||
) {
|
) {
|
||||||
val account = accountsCache.loadAccount(accountSettings)
|
val account = accountsCache.loadAccount(accountSettings)
|
||||||
_accountContent.update {
|
_accountContent.update {
|
||||||
AccountState.LoggedIn(account, route, isNewAccount = isNewAccount)
|
AccountState.LoggedIn(account, route)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun finishNewAccountSetup() {
|
|
||||||
val current = _accountContent.value
|
|
||||||
if (current is AccountState.LoggedIn && current.isNewAccount) {
|
|
||||||
_accountContent.update {
|
|
||||||
AccountState.LoggedIn(current.account, current.route, isNewAccount = false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,7 +278,7 @@ class AccountSessionManager(
|
|||||||
|
|
||||||
localPreferences.setDefaultAccount(accountSettings)
|
localPreferences.setDefaultAccount(accountSettings)
|
||||||
|
|
||||||
startUI(accountSettings, isNewAccount = true)
|
startUI(accountSettings, route = Route.ImportFollows)
|
||||||
|
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
delay(2000) // waits for the new user to connect to the new relays.
|
delay(2000) // waits for the new user to connect to the new relays.
|
||||||
|
|||||||
+83
-60
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.signup
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.expandVertically
|
import androidx.compose.animation.expandVertically
|
||||||
@@ -50,8 +50,6 @@ import androidx.compose.material.icons.filled.CheckCircle
|
|||||||
import androidx.compose.material.icons.filled.PersonAdd
|
import androidx.compose.material.icons.filled.PersonAdd
|
||||||
import androidx.compose.material.icons.outlined.Circle
|
import androidx.compose.material.icons.outlined.Circle
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
@@ -59,6 +57,7 @@ import androidx.compose.material3.Icon
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -72,20 +71,59 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.service.followimport.FollowEntry
|
import com.vitorpamplona.amethyst.service.followimport.FollowEntry
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
// ── Public entry points ────────────────────────────────────────────────
|
@Composable
|
||||||
|
fun ImportFollowListScreen(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
val importViewModel: ImportFollowListViewModel = viewModel()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
topBar = {
|
||||||
|
TopBarExtensibleWithBackButton(
|
||||||
|
title = {},
|
||||||
|
popBack = nav::popBack,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(padding),
|
||||||
|
) {
|
||||||
|
ImportFollowListSection(
|
||||||
|
onFollowsApplied = { entries ->
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
for (entry in entries) {
|
||||||
|
val user = accountViewModel.getOrCreateUser(entry.pubkeyHex)
|
||||||
|
accountViewModel.follow(user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSkip = nav::popBack,
|
||||||
|
onDone = nav::popBack,
|
||||||
|
viewModel = importViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Embeddable section for the signup wizard.
|
|
||||||
*/
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ImportFollowListSection(
|
fun ImportFollowListSection(
|
||||||
onFollowsApplied: suspend (List<FollowEntry>) -> Unit,
|
onFollowsApplied: suspend (List<FollowEntry>) -> Unit,
|
||||||
@@ -117,11 +155,11 @@ fun ImportFollowListSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ImportFollowState.Resolving -> {
|
is ImportFollowState.Resolving -> {
|
||||||
LoadingIndicator("Resolving ${s.identifier}…")
|
LoadingIndicator(stringResource(R.string.resolving, s.identifier))
|
||||||
}
|
}
|
||||||
|
|
||||||
is ImportFollowState.Fetching -> {
|
is ImportFollowState.Fetching -> {
|
||||||
LoadingIndicator("Fetching follow list…")
|
LoadingIndicator(stringResource(R.string.fetching_follow_list))
|
||||||
}
|
}
|
||||||
|
|
||||||
is ImportFollowState.Preview -> {
|
is ImportFollowState.Preview -> {
|
||||||
@@ -133,7 +171,7 @@ fun ImportFollowListSection(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ImportFollowState.Applying -> {
|
is ImportFollowState.Applying -> {
|
||||||
LoadingIndicator("Following ${s.count} accounts…")
|
LoadingIndicator(stringResource(R.string.following_accounts, s.count))
|
||||||
}
|
}
|
||||||
|
|
||||||
is ImportFollowState.Done -> {
|
is ImportFollowState.Done -> {
|
||||||
@@ -151,30 +189,6 @@ fun ImportFollowListSection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Standalone dialog for post-signup use (settings / profile screen).
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun ImportFollowListDialog(
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onFollowsApplied: suspend (List<FollowEntry>) -> Unit,
|
|
||||||
) {
|
|
||||||
Dialog(onDismissRequest = onDismiss) {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier.fillMaxWidth().padding(8.dp),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
|
|
||||||
) {
|
|
||||||
ImportFollowListSection(
|
|
||||||
onFollowsApplied = onFollowsApplied,
|
|
||||||
onSkip = onDismiss,
|
|
||||||
onDone = onDismiss,
|
|
||||||
modifier = Modifier.height(600.dp),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Internal composables ───────────────────────────────────────────────
|
// ── Internal composables ───────────────────────────────────────────────
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -189,14 +203,14 @@ private fun ImportHeader() {
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(10.dp))
|
||||||
Text(
|
Text(
|
||||||
"Import Follow List",
|
stringResource(R.string.import_follow_list),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
"Start with a great feed by following the same people as someone you trust.",
|
stringResource(R.string.start_with_a_great_feed_by_following_the_same_people_as_someone_you_trust),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@@ -215,15 +229,15 @@ private fun InputSection(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = identifier,
|
value = identifier,
|
||||||
onValueChange = { identifier = it },
|
onValueChange = { identifier = it },
|
||||||
label = { Text("Profile to import from") },
|
label = { Text(stringResource(R.string.profile_to_import_from)) },
|
||||||
placeholder = { Text("npub1…, alice@example.com, or example.bit") },
|
placeholder = { Text(stringResource(R.string.name_search_npub1_alice_example_com)) },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
supportingText = {
|
supportingText = {
|
||||||
Text(
|
Text(
|
||||||
"Supports npub, NIP-05, hex, and Namecoin (.bit / d/ / id/)",
|
stringResource(R.string.supports_npub_nip_05_hex_and_namecoin_bit_d_id),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||||
)
|
)
|
||||||
@@ -244,7 +258,7 @@ private fun InputSection(
|
|||||||
enabled = enabled && identifier.isNotBlank(),
|
enabled = enabled && identifier.isNotBlank(),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(12.dp),
|
||||||
) { Text("Look Up Follow List") }
|
) { Text(stringResource(R.string.look_up_follow_list)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,16 +267,14 @@ private fun IdleHint() {
|
|||||||
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(24.dp)) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(24.dp)) {
|
||||||
Text(
|
Text(
|
||||||
"Tip",
|
stringResource(R.string.tip),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(4.dp))
|
Spacer(Modifier.height(4.dp))
|
||||||
Text(
|
Text(
|
||||||
"Enter the profile of a friend or community leader. " +
|
stringResource(R.string.import_follows_tips),
|
||||||
"You can use their npub, NIP-05 address, or a Namecoin name " +
|
|
||||||
"like alice@example.bit or id/alice for blockchain-verified identities.",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@@ -303,17 +315,19 @@ private fun PreviewList(
|
|||||||
|
|
||||||
// Summary
|
// Summary
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().padding(vertical = 4.dp),
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
"${state.totalCount} accounts found",
|
stringResource(R.string.accounts_found, state.totalCount),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
"${state.selectedCount} selected",
|
stringResource(R.string.selected, state.selectedCount),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
)
|
)
|
||||||
@@ -321,7 +335,10 @@ private fun PreviewList(
|
|||||||
|
|
||||||
// Select all
|
// Select all
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().clickable { onSelectAll(state.selectedCount < state.totalCount) }.padding(vertical = 8.dp),
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable { onSelectAll(state.selectedCount < state.totalCount) }
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Checkbox(
|
Checkbox(
|
||||||
@@ -362,7 +379,7 @@ private fun NamecoinResolvedBadge(namecoinSource: String) {
|
|||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
"Resolved via Namecoin",
|
stringResource(R.string.resolved_via_namecoin),
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
color = Color(0xFF4A90D9),
|
color = Color(0xFF4A90D9),
|
||||||
@@ -383,7 +400,10 @@ private fun FollowEntryRow(
|
|||||||
onToggle: () -> Unit,
|
onToggle: () -> Unit,
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
Modifier.fillMaxWidth().clickable(onClick = onToggle).padding(vertical = 6.dp, horizontal = 4.dp),
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onToggle)
|
||||||
|
.padding(vertical = 6.dp, horizontal = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -399,7 +419,10 @@ private fun FollowEntryRow(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.width(10.dp))
|
Spacer(Modifier.width(10.dp))
|
||||||
Box(
|
Box(
|
||||||
Modifier.size(32.dp).clip(CircleShape).background(MaterialTheme.colorScheme.primaryContainer),
|
Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(MaterialTheme.colorScheme.primaryContainer),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -455,13 +478,13 @@ private fun DoneMessage(
|
|||||||
)
|
)
|
||||||
Spacer(Modifier.height(12.dp))
|
Spacer(Modifier.height(12.dp))
|
||||||
Text(
|
Text(
|
||||||
"Now following $count accounts",
|
stringResource(R.string.now_following_accounts, count),
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.SemiBold,
|
fontWeight = FontWeight.SemiBold,
|
||||||
)
|
)
|
||||||
Spacer(Modifier.height(6.dp))
|
Spacer(Modifier.height(6.dp))
|
||||||
Text(
|
Text(
|
||||||
"Your feed is ready.",
|
stringResource(R.string.your_feed_is_ready),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
)
|
)
|
||||||
@@ -499,14 +522,14 @@ private fun BottomActions(
|
|||||||
when (state) {
|
when (state) {
|
||||||
is ImportFollowState.Preview -> {
|
is ImportFollowState.Preview -> {
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
TextButton(onClick = onSkip) { Text("Skip") }
|
TextButton(onClick = onSkip) { Text(stringResource(R.string.skip)) }
|
||||||
Row {
|
Row {
|
||||||
OutlinedButton(onClick = onSearchAnother, shape = RoundedCornerShape(12.dp)) {
|
OutlinedButton(onClick = onSearchAnother, shape = RoundedCornerShape(12.dp)) {
|
||||||
Text("Search Another")
|
Text(stringResource(R.string.search_another))
|
||||||
}
|
}
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Button(onClick = onApply, enabled = state.selectedCount > 0, shape = RoundedCornerShape(12.dp)) {
|
Button(onClick = onApply, enabled = state.selectedCount > 0, shape = RoundedCornerShape(12.dp)) {
|
||||||
Text("Follow ${state.selectedCount} accounts")
|
Text(stringResource(R.string.follow_accounts, state.selectedCount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -515,15 +538,15 @@ private fun BottomActions(
|
|||||||
is ImportFollowState.Done -> {
|
is ImportFollowState.Done -> {
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
OutlinedButton(onClick = onSearchAnother, shape = RoundedCornerShape(12.dp)) {
|
OutlinedButton(onClick = onSearchAnother, shape = RoundedCornerShape(12.dp)) {
|
||||||
Text("Import More")
|
Text(stringResource(R.string.import_more))
|
||||||
}
|
}
|
||||||
Button(onClick = onDone, shape = RoundedCornerShape(12.dp)) { Text("Continue") }
|
Button(onClick = onDone, shape = RoundedCornerShape(12.dp)) { Text(stringResource(R.string.continue_txt)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is ImportFollowState.Idle, is ImportFollowState.Error -> {
|
is ImportFollowState.Idle, is ImportFollowState.Error -> {
|
||||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
|
||||||
TextButton(onClick = onSkip) { Text("Skip for now") }
|
TextButton(onClick = onSkip) { Text(stringResource(R.string.skip_for_now)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.screen.signup
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser
|
||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -1229,6 +1229,7 @@
|
|||||||
<string name="route_video">Shorts</string>
|
<string name="route_video">Shorts</string>
|
||||||
<string name="route_chess">Chess</string>
|
<string name="route_chess">Chess</string>
|
||||||
<string name="route_security_filters">Security Filters</string>
|
<string name="route_security_filters">Security Filters</string>
|
||||||
|
<string name="route_import_follows">Import Follows</string>
|
||||||
|
|
||||||
<string name="new_post">New Post</string>
|
<string name="new_post">New Post</string>
|
||||||
<string name="new_short">New Shorts: images or videos</string>
|
<string name="new_short">New Shorts: images or videos</string>
|
||||||
@@ -1715,4 +1716,26 @@
|
|||||||
<string name="kind_voice_msg">Voice Msg</string>
|
<string name="kind_voice_msg">Voice Msg</string>
|
||||||
<string name="kind_voice_reply">Voice Reply</string>
|
<string name="kind_voice_reply">Voice Reply</string>
|
||||||
<string name="kind_wiki">Wiki</string>
|
<string name="kind_wiki">Wiki</string>
|
||||||
|
<string name="start_with_a_great_feed_by_following_the_same_people_as_someone_you_trust">Start with a great feed by following the same people as someone you trust.</string>
|
||||||
|
<string name="import_follow_list">Import Follow List</string>
|
||||||
|
<string name="profile_to_import_from">Profile to import from</string>
|
||||||
|
<string name="name_search_npub1_alice_example_com">name search, npub1…, alice@example.com</string>
|
||||||
|
<string name="supports_npub_nip_05_hex_and_namecoin_bit_d_id">Supports npub, NIP-05, hex, and Namecoin (.bit / d/ / id/)</string>
|
||||||
|
<string name="look_up_follow_list">Look Up Follow List</string>
|
||||||
|
<string name="tip">Tip</string>
|
||||||
|
<string name="accounts_found">%1$d accounts found</string>
|
||||||
|
<string name="selected">%1$d selected</string>
|
||||||
|
<string name="resolved_via_namecoin">Resolved via Namecoin</string>
|
||||||
|
<string name="now_following_accounts">Now following %1$d accounts</string>
|
||||||
|
<string name="your_feed_is_ready">Your feed is ready.</string>
|
||||||
|
<string name="skip">Skip</string>
|
||||||
|
<string name="search_another">Search Another</string>
|
||||||
|
<string name="follow_accounts">Follow %1$d accounts</string>
|
||||||
|
<string name="import_more">Import More</string>
|
||||||
|
<string name="continue_txt">Continue</string>
|
||||||
|
<string name="skip_for_now">Skip for now</string>
|
||||||
|
<string name="resolving">Resolving %1$s…</string>
|
||||||
|
<string name="fetching_follow_list">Fetching follow list…</string>
|
||||||
|
<string name="following_accounts">Following %1$d accounts…</string>
|
||||||
|
<string name="import_follows_tips">"Enter the profile of a friend or community leader. You can use their npub, NIP-05 address, or a Namecoin name like alice@example.bit or id/alice for blockchain-verified identities."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user