diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e59bc909f..6839f39cd7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,10 +22,16 @@ android:theme="@style/Theme.Amethyst"> - + + + + + + + diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt new file mode 100644 index 0000000000..f186ceb743 --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip19.kt @@ -0,0 +1,62 @@ +package com.vitorpamplona.amethyst.service + +import com.vitorpamplona.amethyst.model.toHexKey +import nostr.postr.bechToBytes + +class Nip19 { + + enum class Type { + USER, NOTE + } + data class Return(val type: Type, val hex: String) + + fun uriToRoute(uri: String?): Return? { + try { + val key = uri?.removePrefix("nostr:") + + if (key != null) { + val bytes = key.bechToBytes() + if (key.startsWith("npub")) { + return Return(Type.USER, bytes.toHexKey()) + } + if (key.startsWith("note")) { + return Return(Type.NOTE, bytes.toHexKey()) + } + if (key.startsWith("nprofile")) { + val tlv = parseTLV(bytes) + val hex = tlv.get(0)?.get(0)?.toHexKey() + if (hex != null) + return Return(Type.USER, hex) + } + if (key.startsWith("nevent")) { + val tlv = parseTLV(bytes) + val hex = tlv.get(0)?.get(0)?.toHexKey() + if (hex != null) + return Return(Type.USER, hex) + } + } + } catch (e: Throwable) { + e.printStackTrace() + } + + return null + } + + fun parseTLV(data: ByteArray): Map> { + var result = mutableMapOf>() + var rest = data + while (rest.isNotEmpty()) { + val t = rest[0] + val l = rest[1] + val v = rest.sliceArray(IntRange(2, (2 + l) - 1)) + rest = rest.sliceArray(IntRange(2 + l, rest.size-1)) + if (v.size < l) continue + + if (!result.containsKey(t)) { + result.put(t, mutableListOf()) + } + result.get(t)?.add(v) + } + return result + } +} \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt index c7066c43f0..81cd6bb03e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrThreadDataSource.kt @@ -57,20 +57,25 @@ object NostrThreadDataSource: NostrDataSource("SingleThreadFeed") { } fun loadThread(noteId: String) { - val note = LocalCache.notes[noteId] ?: return + val note = LocalCache.notes[noteId] - val thread = mutableListOf() - val threadSet = mutableSetOf() + if (note != null) { + val thread = mutableListOf() + val threadSet = mutableSetOf() - val threadRoot = note.replyTo?.firstOrNull() ?: note + val threadRoot = note.replyTo?.firstOrNull() ?: note - loadDown(threadRoot, thread, threadSet) + loadDown(threadRoot, thread, threadSet) - // Currently orders by date of each event, descending, at each level of the reply stack - val order = compareByDescending { it.replyLevelSignature() } + // Currently orders by date of each event, descending, at each level of the reply stack + val order = compareByDescending { it.replyLevelSignature() } - eventsToWatch.clear() - eventsToWatch.addAll(thread.sortedWith(order).map { it.idHex }) + eventsToWatch.clear() + eventsToWatch.addAll(thread.sortedWith(order).map { it.idHex }) + } else { + eventsToWatch.clear() + eventsToWatch.add(noteId) + } resetFilters() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt index 94f37508b5..dccdb91e39 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrUserProfileDataSource.kt @@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.model.toByteArray import nostr.postr.JsonFilter import nostr.postr.events.MetadataEvent import nostr.postr.events.TextNoteEvent @@ -13,7 +14,7 @@ object NostrUserProfileDataSource: NostrDataSource("UserProfileFeed") { var user: User? = null fun loadUserProfile(userId: String) { - user = LocalCache.users[userId] + user = LocalCache.getOrCreateUser(userId.toByteArray()) resetFilters() } 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 6c113cfc32..b6a4e8ad89 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/MainActivity.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui +import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.Bundle import androidx.activity.ComponentActivity @@ -17,15 +18,28 @@ import coil.decode.SvgDecoder import com.vitorpamplona.amethyst.EncryptedStorage import com.vitorpamplona.amethyst.LocalPreferences import com.vitorpamplona.amethyst.ServiceManager +import com.vitorpamplona.amethyst.model.decodePublicKey +import com.vitorpamplona.amethyst.model.toHexKey +import com.vitorpamplona.amethyst.service.Nip19 import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.theme.AmethystTheme +import fr.acinq.secp256k1.Hex +import nostr.postr.Persona +import nostr.postr.bechToBytes class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + val nip19 = Nip19().uriToRoute(intent?.data?.toString()) + val startingPage = when (nip19?.type) { + Nip19.Type.USER -> "User/${nip19.hex}" + Nip19.Type.NOTE -> "Note/${nip19.hex}" + else -> null + } + Coil.setImageLoader { ImageLoader.Builder(this).components { if (SDK_INT >= 28) { @@ -39,6 +53,7 @@ class MainActivity : ComponentActivity() { .build() } + setContent { AmethystTheme { // A surface container using the 'background' color from the theme @@ -48,7 +63,7 @@ class MainActivity : ComponentActivity() { AccountStateViewModel(LocalPreferences(applicationContext)) } - AccountScreen(accountViewModel) + AccountScreen(accountViewModel, startingPage) } } } @@ -67,4 +82,4 @@ class MainActivity : ComponentActivity() { super.onPause() } -} \ No newline at end of file +} 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 8db00572c6..1d97af2a97 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 @@ -11,11 +11,16 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel fun AppNavigation( navController: NavHostController, accountViewModel: AccountViewModel, - accountStateViewModel: AccountStateViewModel + accountStateViewModel: AccountStateViewModel, + nextPage: String? = null ) { NavHost(navController, startDestination = Route.Home.route) { Routes.forEach { composable(it.route, it.arguments, content = it.buildScreen(accountViewModel, accountStateViewModel, navController)) } } + + if (nextPage != null) { + navController.navigate(nextPage) + } } \ No newline at end of file diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt index f12d7f04f4..c39834823f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/AccountScreen.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun AccountScreen(accountStateViewModel: AccountStateViewModel) { +fun AccountScreen(accountStateViewModel: AccountStateViewModel, startingPage: String?) { val accountState by accountStateViewModel.accountContent.collectAsStateWithLifecycle() Column() { @@ -18,10 +18,10 @@ fun AccountScreen(accountStateViewModel: AccountStateViewModel) { LoginPage(accountStateViewModel) } is AccountState.LoggedIn -> { - MainScreen(AccountViewModel(state.account), accountStateViewModel) + MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) } is AccountState.LoggedInViewOnly -> { - MainScreen(AccountViewModel(state.account), accountStateViewModel) + MainScreen(AccountViewModel(state.account), accountStateViewModel, startingPage) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 4a0dd486e0..b13fd024e5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.ui.navigation.currentRoute import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable -fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel) { +fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) { val navController = rememberNavController() val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed)) @@ -50,7 +50,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun scaffoldState = scaffoldState ) { Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) { - AppNavigation(navController, accountViewModel, accountStateViewModel) + AppNavigation(navController, accountViewModel, accountStateViewModel, startingPage) } } }