Skip to content

Commit 5d95156

Browse files
robbiehansondpad85
andauthored
Adding support for lightning addresses within contacts (#640)
Co-authored-by: Dominique Padiou <[email protected]>
1 parent 918c4b9 commit 5d95156

File tree

77 files changed

+3332
-1409
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3332
-1409
lines changed

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/AppView.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,7 @@ fun AppView(
157157
) {
158158
// we keep a view model storing payments so that we don't have to fetch them every time
159159
val paymentsViewModel = viewModel<PaymentsViewModel>(
160-
factory = PaymentsViewModel.Factory(
161-
paymentsManager = business.paymentsManager,
162-
contactsManager = business.contactsManager,
163-
)
160+
factory = PaymentsViewModel.Factory(business.paymentsManager,)
164161
)
165162
val noticesViewModel = viewModel<NoticesViewModel>(
166163
factory = NoticesViewModel.Factory(

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/PaymentsViewModel.kt

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import androidx.lifecycle.ViewModelProvider
2121
import androidx.lifecycle.viewModelScope
2222
import fr.acinq.lightning.utils.UUID
2323
import fr.acinq.phoenix.data.WalletPaymentInfo
24-
import fr.acinq.phoenix.managers.ContactsManager
2524
import fr.acinq.phoenix.managers.PaymentsManager
2625
import fr.acinq.phoenix.managers.PaymentsPageFetcher
2726
import kotlinx.coroutines.CoroutineExceptionHandler
@@ -37,8 +36,7 @@ import org.slf4j.LoggerFactory
3736

3837
@OptIn(ExperimentalCoroutinesApi::class)
3938
class PaymentsViewModel(
40-
private val paymentsManager: PaymentsManager,
41-
private val contactsManager: ContactsManager,
39+
paymentsManager: PaymentsManager,
4240
) : ViewModel() {
4341

4442
companion object {
@@ -83,11 +81,10 @@ class PaymentsViewModel(
8381

8482
class Factory(
8583
private val paymentsManager: PaymentsManager,
86-
private val contactsManager: ContactsManager,
8784
) : ViewModelProvider.Factory {
8885
override fun <T : ViewModel> create(modelClass: Class<T>): T {
8986
@Suppress("UNCHECKED_CAST")
90-
return PaymentsViewModel(paymentsManager, contactsManager) as T
87+
return PaymentsViewModel(paymentsManager) as T
9188
}
9289
}
9390
}

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/AmountHeroInput.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ fun AmountHeroInput(
132132
),
133133
keyboardOptions = KeyboardOptions(
134134
capitalization = KeyboardCapitalization.None,
135-
autoCorrect = false,
135+
autoCorrectEnabled = false,
136136
keyboardType = KeyboardType.Number,
137137
imeAction = ImeAction.Done
138138
),

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/contact/ContactDetailsView.kt

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import androidx.compose.foundation.verticalScroll
3535
import androidx.compose.material.MaterialTheme
3636
import androidx.compose.material.Text
3737
import androidx.compose.runtime.Composable
38+
import androidx.compose.runtime.collectAsState
3839
import androidx.compose.runtime.getValue
3940
import androidx.compose.runtime.mutableStateOf
4041
import androidx.compose.runtime.remember
@@ -66,6 +67,7 @@ import fr.acinq.phoenix.android.utils.invisibleOutlinedTextFieldColors
6667
import fr.acinq.phoenix.android.utils.mutedTextColor
6768
import fr.acinq.phoenix.android.utils.negativeColor
6869
import fr.acinq.phoenix.data.ContactInfo
70+
import fr.acinq.phoenix.data.ContactOffer
6971
import kotlinx.coroutines.launch
7072

7173

@@ -103,8 +105,8 @@ fun ContactDetailsView(
103105
scope.launch { pagerState.animateScrollToPage(1) }
104106
}
105107
)
106-
1 -> ContactOffers(
107-
offers = contact.offers,
108+
1 -> ListOffersForContact(
109+
contactOffers = contact.offers,
108110
onBackClick = {
109111
scope.launch { pagerState.animateScrollToPage(0) }
110112
}
@@ -122,7 +124,7 @@ private fun ContactNameAndPhoto(
122124
onDismiss: () -> Unit,
123125
onOffersClick: () -> Unit,
124126
) {
125-
val contactsManager = business.contactsManager
127+
val contactsManager by business.databaseManager.contactsDb.collectAsState(null)
126128
val scope = rememberCoroutineScope()
127129

128130
var name by remember(contact) { mutableStateOf(contact.name) }
@@ -170,9 +172,11 @@ private fun ContactNameAndPhoto(
170172
iconTint = negativeColor,
171173
onClick = {
172174
scope.launch {
173-
contactsManager.deleteContact(contact.id)
174-
onContactChange(null)
175-
onDismiss()
175+
contactsManager?.run {
176+
deleteContact(contact.id)
177+
onContactChange(null)
178+
onDismiss()
179+
}
176180
}
177181
},
178182
padding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
@@ -185,9 +189,12 @@ private fun ContactNameAndPhoto(
185189
enabled = contact.name != name || contact.photoUri != photoUri || contact.useOfferKey != useOfferKey,
186190
onClick = {
187191
scope.launch {
188-
val newContact = contactsManager.updateContact(contact.id, name, photoUri, useOfferKey, contact.offers)
189-
onContactChange(newContact)
190-
onDismiss()
192+
contactsManager?.run {
193+
val newContact = contact.copy(name = name, photoUri = photoUri, useOfferKey = useOfferKey, offers = contact.offers)
194+
saveContact(newContact)
195+
onContactChange(newContact)
196+
onDismiss()
197+
}
191198
}
192199
},
193200
)
@@ -224,18 +231,18 @@ private fun ContactNameAndPhoto(
224231
}
225232

226233
@Composable
227-
private fun ContactOffers(
234+
private fun ListOffersForContact(
235+
contactOffers: List<ContactOffer>,
228236
onBackClick: () -> Unit,
229-
offers: List<OfferTypes.Offer>,
230237
) {
231238
val navController = navController
232239
Column(modifier = Modifier.fillMaxSize()) {
233240
DefaultScreenHeader(title = stringResource(id = R.string.contact_offers_list), onBackClick = onBackClick)
234241
HSeparator()
235242
Column(modifier = Modifier.fillMaxWidth()) {
236-
offers.forEach { offer ->
243+
contactOffers.forEach { contactOffer ->
237244
OfferAttachedToContactRow(
238-
offer = offer,
245+
contactOffer = contactOffer,
239246
onOfferClick = { navController.navigate("${Screen.Send.route}?input=${it.encode()}") },
240247
)
241248
}
@@ -245,19 +252,19 @@ private fun ContactOffers(
245252

246253
@Composable
247254
private fun OfferAttachedToContactRow(
248-
offer: OfferTypes.Offer,
255+
contactOffer: ContactOffer,
249256
onOfferClick: (OfferTypes.Offer) -> Unit
250257
) {
251258
val context = LocalContext.current
252-
val encoded = remember(offer) { offer.encode() }
253-
Clickable(onClick = { onOfferClick(offer) }) {
259+
val encoded = remember(contactOffer) { contactOffer.offer.encode() }
260+
Clickable(onClick = { onOfferClick(contactOffer.offer) }) {
254261
Row(
255262
modifier = Modifier.height(IntrinsicSize.Min),
256263
verticalAlignment = Alignment.CenterVertically,
257264
) {
258265
Spacer(modifier = Modifier.width(24.dp))
259266
Text(
260-
text = offer.encode(),
267+
text = encoded,
261268
maxLines = 1,
262269
overflow = TextOverflow.Ellipsis,
263270
modifier = Modifier.weight(1f)

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/contact/ContactOrOfferView.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.compose.material.MaterialTheme
2424
import androidx.compose.material.Text
2525
import androidx.compose.runtime.Composable
2626
import androidx.compose.runtime.LaunchedEffect
27+
import androidx.compose.runtime.collectAsState
2728
import androidx.compose.runtime.getValue
2829
import androidx.compose.runtime.mutableStateOf
2930
import androidx.compose.runtime.remember
@@ -53,10 +54,10 @@ sealed class OfferContactState {
5354
*/
5455
@Composable
5556
fun ContactOrOfferView(offer: OfferTypes.Offer) {
56-
val contactsManager = business.contactsManager
57+
val contactsDb by business.databaseManager.contactsDb.collectAsState(null)
5758
val contactState = remember { mutableStateOf<OfferContactState>(OfferContactState.Init) }
5859
LaunchedEffect(Unit) {
59-
contactState.value = contactsManager.getContactForOffer(offer)?.let { OfferContactState.Found(it) } ?: OfferContactState.NotFound
60+
contactState.value = contactsDb?.contactForOffer(offer)?.let { OfferContactState.Found(it) } ?: OfferContactState.NotFound
6061
}
6162

6263
when (val state = contactState.value) {

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/contact/ContactsListView.kt

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,9 @@ fun ContactsListView(
6363
onEditContact: (ContactInfo) -> Unit,
6464
isOnSurface: Boolean,
6565
) {
66-
val contactsState = if (canEditContact) {
67-
business.contactsManager.contactsList.collectAsState(null)
68-
} else {
69-
business.contactsManager.contactsWithOfferList.collectAsState(null)
70-
}
66+
val contactsList by business.databaseManager.contactsList.collectAsState(null)
7167

72-
contactsState.value?.let { contacts ->
68+
contactsList?.let { contacts ->
7369
var nameFilterInput by remember { mutableStateOf("") }
7470
TextInput(
7571
text = nameFilterInput,
@@ -118,8 +114,8 @@ private fun ContactsList(
118114
LazyColumn(state = listState) {
119115
itemsIndexed(contacts) { index, contact ->
120116
val onClick = {
121-
contact.mostRelevantOffer?.let {
122-
navController.navigate("${Screen.Send.route}?input=${it.encode()}")
117+
contact.offers.firstOrNull()?.let {
118+
navController.navigate("${Screen.Send.route}?input=${it.offer.encode()}")
123119
} ?: run { if (canEditContact) { onEditContact(contact) } }
124120
}
125121
if (isOnSurface) {

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/components/contact/SaveNewContactView.kt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,15 @@
1717
package fr.acinq.phoenix.android.components.contact
1818

1919
import androidx.compose.foundation.layout.Box
20-
import androidx.compose.foundation.layout.Column
2120
import androidx.compose.foundation.layout.PaddingValues
2221
import androidx.compose.foundation.layout.Spacer
2322
import androidx.compose.foundation.layout.fillMaxSize
2423
import androidx.compose.foundation.layout.height
25-
import androidx.compose.foundation.layout.padding
26-
import androidx.compose.foundation.rememberScrollState
2724
import androidx.compose.foundation.shape.CircleShape
28-
import androidx.compose.foundation.verticalScroll
2925
import androidx.compose.material.MaterialTheme
3026
import androidx.compose.material.Text
3127
import androidx.compose.runtime.Composable
28+
import androidx.compose.runtime.collectAsState
3229
import androidx.compose.runtime.getValue
3330
import androidx.compose.runtime.mutableStateOf
3431
import androidx.compose.runtime.remember
@@ -41,6 +38,8 @@ import androidx.compose.ui.res.stringResource
4138
import androidx.compose.ui.text.style.TextAlign
4239
import androidx.compose.ui.unit.dp
4340
import fr.acinq.bitcoin.utils.Try
41+
import fr.acinq.lightning.utils.UUID
42+
import fr.acinq.lightning.utils.currentTimestampMillis
4443
import fr.acinq.lightning.wire.OfferTypes
4544
import fr.acinq.phoenix.android.R
4645
import fr.acinq.phoenix.android.business
@@ -51,11 +50,12 @@ import fr.acinq.phoenix.android.components.TextInput
5150
import fr.acinq.phoenix.android.components.dialogs.ModalBottomSheet
5251
import fr.acinq.phoenix.android.components.scanner.ScannerView
5352
import fr.acinq.phoenix.data.ContactInfo
53+
import fr.acinq.phoenix.data.ContactOffer
5454
import kotlinx.coroutines.launch
5555

5656

5757
/**
58-
* A contact detail is a bottom sheet dialog that display the contact's name, photo, and
58+
* A contact detail is a bottom sheet dialog that displays the contact's name, photo, and
5959
* associated offers/lnids.
6060
*
6161
* The contact can be edited and deleted from that screen.
@@ -75,7 +75,7 @@ fun SaveNewContactDialog(
7575
var photoUri by remember { mutableStateOf<String?>(null) }
7676
var useOfferKey by remember { mutableStateOf(true) }
7777

78-
val contactsManager = business.contactsManager
78+
val contactsDb by business.databaseManager.contactsDb.collectAsState(null)
7979

8080
ModalBottomSheet(
8181
onDismiss = onDismiss,
@@ -137,31 +137,31 @@ fun SaveNewContactDialog(
137137
text = stringResource(id = R.string.contact_add_contact_button),
138138
icon = R.drawable.ic_check,
139139
onClick = {
140-
scope.launch {
141-
if (initialOffer == null) {
140+
contactsDb?.let { db ->
141+
scope.launch {
142142
offerErrorMessage = ""
143143
when (val res = OfferTypes.Offer.decode(offer)) {
144144
is Try.Success -> {
145145
val decodedOffer = res.result
146-
val existingContact = contactsManager.getContactForOffer(decodedOffer)
146+
val existingContact = db.contactForOffer(decodedOffer)
147147
if (existingContact != null) {
148148
offerErrorMessage = context.getString(R.string.contact_error_offer_known, existingContact.name)
149149
} else {
150-
val contact = contactsManager.saveNewContact(name, photoUri, useOfferKey, res.result)
151-
onSaved(contact)
150+
val contactOffer = ContactOffer(res.result, label = null, createdAt = currentTimestampMillis())
151+
val newContact = ContactInfo(
152+
id = UUID.randomUUID(),
153+
name = name,
154+
photoUri = photoUri,
155+
useOfferKey = useOfferKey,
156+
offers = listOf(contactOffer),
157+
addresses = emptyList()
158+
)
159+
db.saveContact(newContact)
160+
onSaved(newContact)
152161
}
153162
}
154163
is Try.Failure -> { offerErrorMessage = context.getString(R.string.contact_error_offer_invalid) }
155164
}
156-
} else {
157-
val existingContact = contactsManager.getContactForOffer(initialOffer)
158-
if (existingContact != null) {
159-
offerErrorMessage = context.getString(R.string.contact_error_offer_known, existingContact.name)
160-
} else {
161-
val contact = contactsManager.saveNewContact(name, photoUri, useOfferKey, initialOffer)
162-
onSaved(contact)
163-
}
164-
165165
}
166166
}
167167
},

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/init/RestoreWalletView.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ private fun WordInputView(
288288
},
289289
keyboardOptions = KeyboardOptions(
290290
capitalization = KeyboardCapitalization.None,
291-
autoCorrect = false,
291+
autoCorrectEnabled = false,
292292
keyboardType = KeyboardType.Password,
293293
imeAction = ImeAction.None
294294
),

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/details/splash/SplashIncomingBolt12.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import androidx.compose.material.MaterialTheme
2222
import androidx.compose.material.Text
2323
import androidx.compose.runtime.Composable
2424
import androidx.compose.runtime.LaunchedEffect
25+
import androidx.compose.runtime.collectAsState
26+
import androidx.compose.runtime.getValue
2527
import androidx.compose.runtime.mutableStateOf
2628
import androidx.compose.runtime.remember
2729
import androidx.compose.ui.Modifier
@@ -76,11 +78,11 @@ fun SplashOfferPayerNote(payerNote: String) {
7678

7779
@Composable
7880
private fun OfferSentBy(payerPubkey: PublicKey?, hasPayerNote: Boolean) {
79-
val contactsManager = business.contactsManager
81+
val contactsDb by business.databaseManager.contactsDb.collectAsState(null)
8082
val contactState = remember { mutableStateOf<OfferContactState>(OfferContactState.Init) }
81-
LaunchedEffect(Unit) {
83+
LaunchedEffect(contactsDb) {
8284
contactState.value = payerPubkey?.let {
83-
contactsManager.getContactForPayerPubkey(it)
85+
contactsDb?.contactForPayerPubKey(it)
8486
}?.let { OfferContactState.Found(it) } ?: OfferContactState.NotFound
8587
}
8688

phoenix-android/src/main/kotlin/fr/acinq/phoenix/android/payments/send/PrepareSendSmartInput.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ fun SendSmartInput(
132132
.background(colors.backgroundColor(true).value, shape),
133133
keyboardOptions = KeyboardOptions(
134134
capitalization = KeyboardCapitalization.None,
135-
autoCorrect = false,
135+
autoCorrectEnabled = false,
136136
keyboardType = KeyboardType.Email,
137137
imeAction = ImeAction.Send,
138138
),

0 commit comments

Comments
 (0)