From 25111552eb74a3d3bae765b3db04dc9bb983054a Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Thu, 3 Jul 2025 12:42:04 +0000 Subject: [PATCH] feat: Add support for sharing contacts via deeplink (#2336) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- app/src/main/java/com/geeksville/mesh/MainActivity.kt | 7 +++++++ app/src/main/java/com/geeksville/mesh/model/UIState.kt | 9 +++++++++ .../com/geeksville/mesh/ui/sharing/ContactSharing.kt | 9 +++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 1a3e4fefc..dd86e5a22 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -69,6 +69,7 @@ import com.geeksville.mesh.ui.MainMenuAction import com.geeksville.mesh.ui.MainScreen import com.geeksville.mesh.ui.common.theme.AppTheme import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC +import com.geeksville.mesh.ui.sharing.toSharedContact import com.geeksville.mesh.ui.intro.AppIntroductionScreen import com.geeksville.mesh.util.Exceptions import com.geeksville.mesh.util.LanguageUtils @@ -186,6 +187,12 @@ class MainActivity : AppCompatActivity(), Logging { ) { debug("App link data is a channel set") model.requestChannelUrl(it) + } else if (it.path?.startsWith("/v/") == true || + it.path?.startsWith("/V/") == true + ) { + val sharedContact = it.toSharedContact() + debug("App link data is a shared contact: ${sharedContact.user.longName}") + model.setSharedContactRequested(sharedContact) } else { debug("App link data is not a channel set") } diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 768f24a48..2ff35864b 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -623,6 +623,15 @@ class UIViewModel @Inject constructor( radioConfigRepository.onServiceAction(ServiceAction.Reaction(emoji, replyId, contactKey)) } + private val _sharedContactRequested: MutableStateFlow = + MutableStateFlow(null) + val sharedContactRequested: StateFlow get() = _sharedContactRequested.asStateFlow() + fun setSharedContactRequested(sharedContact: AdminProtos.SharedContact?) { + viewModelScope.launch { + _sharedContactRequested.value = sharedContact + } + } + fun addSharedContact(sharedContact: AdminProtos.SharedContact) = viewModelScope.launch { radioConfigRepository.onServiceAction(ServiceAction.AddSharedContact(sharedContact)) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/sharing/ContactSharing.kt b/app/src/main/java/com/geeksville/mesh/ui/sharing/ContactSharing.kt index 86b7cd1b2..7806d4922 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/sharing/ContactSharing.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/sharing/ContactSharing.kt @@ -53,6 +53,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.geeksville.mesh.AdminProtos import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.R @@ -82,7 +83,7 @@ fun AddContactFAB( onSharedContactImport: (AdminProtos.SharedContact) -> Unit = {}, ) { val context = LocalContext.current - var contactToImport: AdminProtos.SharedContact? by remember { mutableStateOf(null) } + val contactToImport: AdminProtos.SharedContact? by model.sharedContactRequested.collectAsStateWithLifecycle(null) val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result -> if (result.contents != null) { @@ -94,7 +95,7 @@ fun AddContactFAB( null } if (sharedContact != null) { - contactToImport = sharedContact + model.setSharedContactRequested(sharedContact) } } } @@ -134,12 +135,12 @@ fun AddContactFAB( }, dismissText = stringResource(R.string.cancel), onDismiss = { - contactToImport = null + model.setSharedContactRequested(null) }, confirmText = stringResource(R.string.import_label), onConfirm = { onSharedContactImport(contactToImport!!) - contactToImport = null + model.setSharedContactRequested(null) } ) }