diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt index eef4955ce..b4bdda203 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/nip19/Nip19.kt @@ -7,7 +7,7 @@ import java.util.regex.Pattern object Nip19 { enum class Type { - USER, NOTE, RELAY, ADDRESS + USER, NOTE, EVENT, RELAY, ADDRESS } val nip19regex = Pattern.compile("(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)(.*)", Pattern.CASE_INSENSITIVE) @@ -76,7 +76,7 @@ object Nip19 { ?.get(0) ?.toString(Charsets.UTF_8) - return Return(Type.USER, hex, relay) + return Return(Type.EVENT, hex, relay) } private fun nrelay(bytes: ByteArray): Return? { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt index 3cc32af87..3afecdcb7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -37,6 +37,8 @@ class MainActivity : FragmentActivity() { val startingPage = when (nip19?.type) { Nip19.Type.USER -> "User/${nip19.hex}" Nip19.Type.NOTE -> "Note/${nip19.hex}" + Nip19.Type.EVENT -> "Event/${nip19.hex}" + Nip19.Type.ADDRESS -> "Note/${nip19.hex}" else -> null } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index b8a37000c..881947e14 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -87,6 +87,8 @@ class NewPostViewModel : ViewModel() { addUserToMentions(LocalCache.getOrCreateUser(results.key.hex)) } else if (results?.key?.type == Nip19.Type.NOTE) { addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex)) + } else if (results?.key?.type == Nip19.Type.EVENT) { + addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex)) } else if (results?.key?.type == Nip19.Type.ADDRESS) { val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex) if (note != null) { @@ -107,6 +109,10 @@ class NewPostViewModel : ViewModel() { } else if (results?.key?.type == Nip19.Type.NOTE) { val note = LocalCache.getOrCreateNote(results.key.hex) + "#[${tagIndex(note)}]${results.restOfWord}" + } else if (results?.key?.type == Nip19.Type.EVENT) { + val note = LocalCache.getOrCreateNote(results.key.hex) + "#[${tagIndex(note)}]${results.restOfWord}" } else if (results?.key?.type == Nip19.Type.ADDRESS) { val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt index 0e02d1e9f..f9117a13e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ClickableRoute.kt @@ -12,6 +12,7 @@ import androidx.compose.ui.text.withStyle import androidx.navigation.NavController import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent +import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.nip19.Nip19 @Composable @@ -47,11 +48,28 @@ fun ClickableRoute( if (note.event is ChannelCreateEvent) { CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController) + } else if (note.event is PrivateDmEvent) { + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController) } else if (channel != null) { CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController) } else { CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Note/${nip19.hex}", navController) } + } else if (nip19.type == Nip19.Type.EVENT) { + val noteBase = LocalCache.getOrCreateNote(nip19.hex) + val noteState by noteBase.live().metadata.observeAsState() + val note = noteState?.note ?: return + val channel = note.channel() + + if (note.event is ChannelCreateEvent) { + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Channel/${nip19.hex}", navController) + } else if (note.event is PrivateDmEvent) { + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Room/${note.author?.pubkeyHex}", navController) + } else if (channel != null) { + CreateClickableText(channel.toBestDisplayName(), nip19.additionalChars, "Channel/${note.channel()?.idHex}", navController) + } else { + CreateClickableText(note.idDisplayNote(), nip19.additionalChars, "Event/${nip19.hex}", navController) + } } else { Text( "@${nip19.hex}${nip19.additionalChars} " diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index a4565e4bc..f22e0ddda 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -23,6 +23,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen @@ -126,6 +127,16 @@ fun AppNavigation( ) }) } + + Route.Event.let { route -> + composable(route.route, route.arguments, content = { + LoadRedirectScreen( + eventId = it.arguments?.getString("id"), + accountViewModel = accountViewModel, + navController = navController + ) + }) + } } if (nextPage != null) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 2062c2005..99ea241af 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -87,6 +87,12 @@ sealed class Route( icon = R.drawable.ic_moments, arguments = listOf(navArgument("id") { type = NavType.StringType }) ) + + object Event : Route( + route = "Event/{id}", + icon = R.drawable.ic_moments, + arguments = listOf(navArgument("id") { type = NavType.StringType }) + ) } // ** diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt index 3fe48a5df..c1f31f99c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.qrcode +import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -19,6 +20,8 @@ fun NIP19QrCodeScanner(onScan: (String?) -> Unit) { val startingPage = when (nip19?.type) { Nip19.Type.USER -> "User/${nip19.hex}" Nip19.Type.NOTE -> "Note/${nip19.hex}" + Nip19.Type.EVENT -> "Event/${nip19.hex}" + Nip19.Type.ADDRESS -> "Note/${nip19.hex}" else -> null } @@ -28,6 +31,7 @@ fun NIP19QrCodeScanner(onScan: (String?) -> Unit) { onScan(null) } } catch (e: Throwable) { + Log.e("NIP19 Scanner", "Error parsing $it", e) // QR can be anything, do not throw errors. onScan(null) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt new file mode 100644 index 000000000..1cac914e9 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/LoadRedirectScreen.kt @@ -0,0 +1,63 @@ +package com.vitorpamplona.amethyst.ui.screen.loggedIn + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent +import com.vitorpamplona.amethyst.service.model.PrivateDmEvent + +@Composable +fun LoadRedirectScreen(eventId: String?, accountViewModel: AccountViewModel, navController: NavController) { + if (eventId == null) return + + val baseNote = LocalCache.checkGetOrCreateNote(eventId) ?: return + + val noteState by baseNote.live().metadata.observeAsState() + val note = noteState?.note + + LaunchedEffect(key1 = noteState) { + val event = note?.event + val channel = note?.channel() + + if (event == null) { + // stay here, loading + } else if (event is ChannelCreateEvent) { + navController.backQueue.removeLast() + navController.navigate("Channel/${note.idHex}") + } else if (event is PrivateDmEvent) { + navController.backQueue.removeLast() + navController.navigate("Room/${note.author?.pubkeyHex}") + } else if (channel != null) { + navController.backQueue.removeLast() + navController.navigate("Channel/${channel.idHex}") + } else { + navController.backQueue.removeLast() + navController.navigate("Note/${note.idHex}") + } + } + + Column( + Modifier + .fillMaxHeight() + .fillMaxWidth() + .padding(horizontal = 50.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + Text(stringResource(R.string.looking_for_event, eventId)) + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index eb30c0bdc..4e0428a04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -263,4 +263,6 @@ Wallet Connect Relay Pledge Amount in Sats + + "Looking for Event %1$s" diff --git a/app/src/test/java/com/vitorpamplona/amethyst/NIP19ParserTest.kt b/app/src/test/java/com/vitorpamplona/amethyst/NIP19ParserTest.kt index d7ee9db44..488cb27d2 100644 --- a/app/src/test/java/com/vitorpamplona/amethyst/NIP19ParserTest.kt +++ b/app/src/test/java/com/vitorpamplona/amethyst/NIP19ParserTest.kt @@ -69,4 +69,12 @@ class NIP19ParserTest { assertEquals("30023:46fcbe3065eaf1ae7811465924e48923363ff3f526bd6f73d7c184b16bd8ce4d:1679509418", result?.hex) assertEquals(null, result?.relay) } + + @Test + fun nEventParserTest() { + val result = Nip19.uriToRoute("nostr:nevent1qqs0tsw8hjacs4fppgdg7f5yhgwwfkyua4xcs3re9wwkpkk2qeu6mhql22rcy") + assertEquals(Nip19.Type.EVENT, result?.type) + assertEquals("f5c1c7bcbb8855210a1a8f2684ba1ce4d89ced4d8844792b9d60daca0679addc", result?.hex) + assertEquals(null, result?.relay) + } }