From 9839f9cd38f51751c87137650b01279d18df47f5 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 23 Jun 2026 09:22:51 +0200 Subject: [PATCH] feat(edit): pick a location from the contact picker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reuse the attendee contact-picker mechanism for the location field: a Contacts button beside the location input opens the system picker scoped to postal-address rows, and the chosen contact's formatted address is dropped into the field (multi-line addresses collapsed to one line). Same no-permission guarantee as the guest picker — ACTION_PICK grants temporary read access, so no READ_CONTACTS is required. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../calendula/ui/edit/EventEditScreen.kt | 74 +++++++++++++++++-- app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 1e9416d..88fb610 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -461,6 +461,21 @@ private fun EventEditContent( } } + // Pick a postal address from contacts: the same one-shot picker, scoped to + // address rows, dropping the chosen contact's formatted address into the + // location field. Same no-permission guarantee as the guest picker above. + val pickContactAddress = rememberLauncherForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { result -> + if (result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let { uri -> + readContactAddress(context, uri)?.let { address -> + viewModel.setLocation(address) + } + } + } + } + val selectedCalendar = state.calendars.firstOrNull { it.id == form.calendarId } // The accent ties the form to the detail screen's design language: the // bar under the title takes the target calendar's colour. @@ -592,11 +607,39 @@ private fun EventEditContent( icon = Icons.Default.Place, iconContentDescription = stringResource(R.string.event_detail_location), ) { - InlineField( - value = form.location, - onValueChange = viewModel::setLocation, - placeholder = stringResource(R.string.event_detail_location), - ) + Row(verticalAlignment = Alignment.CenterVertically) { + InlineField( + value = form.location, + onValueChange = viewModel::setLocation, + placeholder = stringResource(R.string.event_detail_location), + modifier = Modifier + .weight(1f) + .padding(vertical = 4.dp), + ) + IconButton( + onClick = { + runCatching { + pickContactAddress.launch( + Intent( + Intent.ACTION_PICK, + ContactsContract.CommonDataKinds.StructuredPostal + .CONTENT_URI, + ), + ) + } + }, + modifier = Modifier.size(40.dp), + ) { + Icon( + imageVector = Icons.Default.Contacts, + contentDescription = stringResource( + R.string.event_edit_location_from_contacts, + ), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.size(22.dp), + ) + } + } } } @@ -1578,6 +1621,27 @@ private fun readContactEmail(context: Context, uri: Uri): Pair? email.takeIf { it.isNotEmpty() }?.let { it to name } } +/** + * Read the formatted postal address from a contact-pick result URI. No + * READ_CONTACTS needed: ACTION_PICK grants this URI temporary read access. + * The provider's formatted address is multi-line; collapse it to one line so + * it sits cleanly in the single-line location field. + */ +private fun readContactAddress(context: Context, uri: Uri): String? = + context.contentResolver.query( + uri, + arrayOf(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS), + null, null, null, + )?.use { cursor -> + if (!cursor.moveToFirst()) return@use null + cursor.getString(0).orEmpty() + .lines() + .map { it.trim() } + .filter { it.isNotEmpty() } + .joinToString(", ") + .takeIf { it.isNotEmpty() } + } + private fun fieldLabel(field: EventFormField): Int = when (field) { EventFormField.Location -> R.string.event_detail_location diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 037c717..d3a43d2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -81,6 +81,7 @@ Gast hinzufügen Gast per E-Mail hinzufügen… Aus Kontakten hinzufügen + Adresse aus Kontakten wählen Gast entfernen Erforderlich Optional diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21e31be..434de7f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,6 +82,7 @@ Add guest Add a guest by email… Add from contacts + Pick address from contacts Remove guest Required Optional -- 2.49.1