Skip to content

Commit 60b7ff4

Browse files
committed
fix: polish pubky edit fields
1 parent cb036a7 commit 60b7ff4

7 files changed

Lines changed: 116 additions & 21 deletions

File tree

app/src/main/java/to/bitkit/ui/components/ProfileEditForm.kt

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package to.bitkit.ui.components
22

3-
import androidx.compose.foundation.background
4-
import androidx.compose.foundation.clickable
53
import androidx.compose.foundation.layout.Arrangement
64
import androidx.compose.foundation.layout.Column
75
import androidx.compose.foundation.layout.ExperimentalLayoutApi
@@ -12,10 +10,10 @@ import androidx.compose.foundation.layout.fillMaxWidth
1210
import androidx.compose.foundation.layout.padding
1311
import androidx.compose.foundation.layout.size
1412
import androidx.compose.foundation.rememberScrollState
15-
import androidx.compose.foundation.shape.RoundedCornerShape
1613
import androidx.compose.foundation.verticalScroll
1714
import androidx.compose.material3.HorizontalDivider
1815
import androidx.compose.material3.Icon
16+
import androidx.compose.material3.IconButton
1917
import androidx.compose.runtime.Composable
2018
import androidx.compose.runtime.getValue
2119
import androidx.compose.runtime.mutableStateOf
@@ -31,6 +29,8 @@ import androidx.compose.ui.unit.dp
3129
import kotlinx.collections.immutable.ImmutableList
3230
import kotlinx.collections.immutable.persistentListOf
3331
import to.bitkit.R
32+
import to.bitkit.ui.theme.AppTextFieldDefaults
33+
import to.bitkit.ui.theme.AppTextStyles
3434
import to.bitkit.ui.theme.AppThemeSurface
3535
import to.bitkit.ui.theme.Colors
3636

@@ -43,6 +43,7 @@ fun ProfileEditForm(
4343
bio: String,
4444
onBioChange: (String) -> Unit,
4545
links: ImmutableList<ProfileEditLink>,
46+
onLinkUrlChange: (Int, String) -> Unit,
4647
onRemoveLink: (Int) -> Unit,
4748
onAddLink: () -> Unit,
4849
tags: ImmutableList<String>,
@@ -72,6 +73,8 @@ fun ProfileEditForm(
7273
onValueChange = onNameChange,
7374
placeholder = stringResource(R.string.profile__edit_name_placeholder),
7475
singleLine = true,
76+
textStyle = AppTextStyles.Display.copy(textAlign = TextAlign.Center),
77+
colors = AppTextFieldDefaults.transparent,
7578
modifier = Modifier.fillMaxWidth()
7679
)
7780
HorizontalDivider()
@@ -115,24 +118,23 @@ fun ProfileEditForm(
115118
modifier = Modifier.fillMaxWidth()
116119
)
117120
VerticalSpacer(8.dp)
118-
Row(
119-
verticalAlignment = Alignment.CenterVertically,
120-
modifier = Modifier
121-
.fillMaxWidth()
122-
.background(Colors.White10, RoundedCornerShape(8.dp))
123-
.padding(16.dp),
124-
) {
125-
BodySSB(text = link.url, modifier = Modifier.weight(1f))
126-
HorizontalSpacer(8.dp)
127-
Icon(
128-
painter = painterResource(R.drawable.ic_trash),
129-
contentDescription = null,
130-
tint = Colors.White64,
131-
modifier = Modifier
132-
.size(16.dp)
133-
.clickable { onRemoveLink(index) },
134-
)
135-
}
121+
TextInput(
122+
value = link.url,
123+
onValueChange = { onLinkUrlChange(index, it) },
124+
placeholder = stringResource(R.string.profile__add_link_url_placeholder),
125+
singleLine = true,
126+
trailingIcon = {
127+
IconButton(onClick = { onRemoveLink(index) }) {
128+
Icon(
129+
painter = painterResource(R.drawable.ic_trash),
130+
contentDescription = null,
131+
tint = Colors.White64,
132+
modifier = Modifier.size(16.dp)
133+
)
134+
}
135+
},
136+
modifier = Modifier.fillMaxWidth()
137+
)
136138
VerticalSpacer(8.dp)
137139
}
138140
Row(modifier = Modifier.fillMaxWidth()) {
@@ -259,6 +261,7 @@ private fun Preview() {
259261
bio = bio,
260262
onBioChange = { bio = it },
261263
links = persistentListOf(ProfileEditLink("X", "https://x.com/satoshinakamoto")),
264+
onLinkUrlChange = { _, _ -> },
262265
onRemoveLink = {},
263266
onAddLink = {},
264267
tags = persistentListOf("Founder"),

app/src/main/java/to/bitkit/ui/screens/contacts/EditContactScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ fun EditContactScreen(
5555
onRetryClick = { viewModel.retryLoadContact() },
5656
onNameChange = { viewModel.onNameChange(it) },
5757
onBioChange = { viewModel.onBioChange(it) },
58+
onLinkUrlChange = { index, url -> viewModel.updateLinkUrl(index, url) },
5859
onRemoveLink = { viewModel.removeLink(it) },
5960
onAddLink = { viewModel.showAddLinkSheet() },
6061
onRemoveTag = { viewModel.removeTag(it) },
@@ -77,6 +78,7 @@ private fun Content(
7778
onRetryClick: () -> Unit,
7879
onNameChange: (String) -> Unit,
7980
onBioChange: (String) -> Unit,
81+
onLinkUrlChange: (Int, String) -> Unit,
8082
onRemoveLink: (Int) -> Unit,
8183
onAddLink: () -> Unit,
8284
onRemoveTag: (Int) -> Unit,
@@ -107,6 +109,7 @@ private fun Content(
107109
bio = uiState.bio,
108110
onBioChange = onBioChange,
109111
links = uiState.links,
112+
onLinkUrlChange = onLinkUrlChange,
110113
onRemoveLink = onRemoveLink,
111114
onAddLink = onAddLink,
112115
tags = uiState.tags,
@@ -202,6 +205,7 @@ private fun Preview() {
202205
onRetryClick = {},
203206
onNameChange = {},
204207
onBioChange = {},
208+
onLinkUrlChange = { _, _ -> },
205209
onRemoveLink = {},
206210
onAddLink = {},
207211
onRemoveTag = {},

app/src/main/java/to/bitkit/ui/screens/contacts/EditContactViewModel.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ class EditContactViewModel @Inject constructor(
129129
}
130130
}
131131

132+
fun updateLinkUrl(index: Int, url: String) {
133+
_uiState.update {
134+
it.copy(
135+
links = it.links.mapIndexed { i, link ->
136+
if (i == index) link.copy(url = url) else link
137+
}.toImmutableList(),
138+
)
139+
}
140+
}
141+
132142
fun removeLink(index: Int) {
133143
_uiState.update {
134144
it.copy(links = it.links.filterIndexed { i, _ -> i != index }.toImmutableList())

app/src/main/java/to/bitkit/ui/screens/profile/EditProfileScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fun EditProfileScreen(
6161
onBackClick = onBackClick,
6262
onNameChange = viewModel::onNameChange,
6363
onBioChange = viewModel::onBioChange,
64+
onLinkUrlChange = viewModel::updateLinkUrl,
6465
onAvatarSelected = viewModel::onAvatarSelected,
6566
onAddLink = viewModel::showAddLinkSheet,
6667
onRemoveLink = viewModel::removeLink,
@@ -84,6 +85,7 @@ private fun Content(
8485
onBackClick: () -> Unit,
8586
onNameChange: (String) -> Unit,
8687
onBioChange: (String) -> Unit,
88+
onLinkUrlChange: (Int, String) -> Unit,
8789
onAvatarSelected: (Uri) -> Unit,
8890
onAddLink: () -> Unit,
8991
onRemoveLink: (Int) -> Unit,
@@ -136,6 +138,7 @@ private fun Content(
136138
bio = uiState.bio,
137139
onBioChange = onBioChange,
138140
links = uiState.links,
141+
onLinkUrlChange = onLinkUrlChange,
139142
onRemoveLink = onRemoveLink,
140143
onAddLink = onAddLink,
141144
tags = uiState.tags,
@@ -228,6 +231,7 @@ private fun Preview() {
228231
onBackClick = {},
229232
onNameChange = {},
230233
onBioChange = {},
234+
onLinkUrlChange = { _, _ -> },
231235
onAvatarSelected = {},
232236
onAddLink = {},
233237
onRemoveLink = {},

app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ class EditProfileViewModel @Inject constructor(
105105
}
106106
}
107107

108+
fun updateLinkUrl(index: Int, url: String) {
109+
_uiState.update {
110+
it.copy(
111+
links = it.links.mapIndexed { i, link ->
112+
if (i == index) link.copy(url = url) else link
113+
}.toImmutableList(),
114+
)
115+
}
116+
}
117+
108118
fun removeLink(index: Int) {
109119
_uiState.update {
110120
it.copy(links = it.links.filterIndexed { i, _ -> i != index }.toImmutableList())

app/src/test/java/to/bitkit/ui/screens/contacts/EditContactViewModelTest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,19 @@ class EditContactViewModelTest : BaseUnitTest() {
5555
assertEquals(listOf("friend"), state.tags)
5656
}
5757

58+
@Test
59+
fun `updateLinkUrl should update existing contact link`() = test {
60+
val contacts = MutableStateFlow<List<PubkyProfile>>(listOf(createContact()))
61+
whenever(pubkyRepo.contacts).thenReturn(contacts)
62+
whenever(pubkyRepo.loadContacts()).thenReturn(Unit)
63+
val sut = createSut()
64+
65+
advanceUntilIdle()
66+
sut.updateLinkUrl(0, "https://updated.example.com")
67+
68+
assertEquals("https://updated.example.com", sut.uiState.value.links.first().url)
69+
}
70+
5871
@Test
5972
fun `contact still missing after refresh produces missing state`() = test {
6073
val contacts = MutableStateFlow<List<PubkyProfile>>(emptyList())
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package to.bitkit.ui.screens.profile
2+
3+
import android.content.Context
4+
import kotlinx.collections.immutable.toImmutableList
5+
import kotlinx.coroutines.ExperimentalCoroutinesApi
6+
import kotlinx.coroutines.flow.MutableStateFlow
7+
import kotlinx.coroutines.test.advanceUntilIdle
8+
import org.junit.Test
9+
import org.mockito.kotlin.mock
10+
import org.mockito.kotlin.whenever
11+
import to.bitkit.models.PubkyProfile
12+
import to.bitkit.models.PubkyProfileLink
13+
import to.bitkit.repositories.PubkyRepo
14+
import to.bitkit.test.BaseUnitTest
15+
import kotlin.test.assertEquals
16+
17+
@OptIn(ExperimentalCoroutinesApi::class)
18+
class EditProfileViewModelTest : BaseUnitTest() {
19+
private val context: Context = mock()
20+
private val pubkyRepo: PubkyRepo = mock()
21+
22+
@Test
23+
fun `updateLinkUrl should update existing profile link`() = test {
24+
whenever(pubkyRepo.profile).thenReturn(MutableStateFlow(createProfile()))
25+
whenever(pubkyRepo.publicKey).thenReturn(MutableStateFlow(TEST_PUBLIC_KEY))
26+
27+
val sut = EditProfileViewModel(
28+
context = context,
29+
pubkyRepo = pubkyRepo,
30+
)
31+
advanceUntilIdle()
32+
33+
sut.updateLinkUrl(0, "https://updated.example.com")
34+
35+
assertEquals("https://updated.example.com", sut.uiState.value.links.first().url)
36+
}
37+
38+
private fun createProfile() = PubkyProfile(
39+
publicKey = TEST_PUBLIC_KEY,
40+
name = "Alice",
41+
bio = "Hello",
42+
imageUrl = "https://example.com/avatar.jpg",
43+
links = listOf(PubkyProfileLink("Website", "https://example.com")),
44+
tags = listOf("friend").toImmutableList(),
45+
status = null,
46+
)
47+
48+
companion object {
49+
private const val TEST_PUBLIC_KEY = "pubkyalice"
50+
}
51+
}

0 commit comments

Comments
 (0)