Skip to content

Commit

Permalink
Merge branch 'main' into feature/improve-gallery-rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
mehmedalijaK authored Mar 4, 2025
2 parents 2002b04 + 134465a commit 481b425
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 52 deletions.
2 changes: 1 addition & 1 deletion app/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
<ID>LongMethod:WalletDashboardScreen.kt$@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun WalletDashboardScreen( state: WalletDashboardContract.UiState, onPrimaryDestinationChanged: (PrimalTopLevelDestination) -&gt; Unit, onDrawerDestinationClick: (DrawerScreenDestination) -&gt; Unit, onDrawerQrCodeClick: () -&gt; Unit, onWalletActivateClick: () -&gt; Unit, onProfileClick: (String) -&gt; Unit, onTransactionClick: (String) -&gt; Unit, onSendClick: () -&gt; Unit, onScanClick: () -&gt; Unit, onReceiveClick: () -&gt; Unit, eventPublisher: (UiEvent) -&gt; Unit, accountSwitcherCallbacks: AccountSwitcherCallbacks, )</ID>
<ID>LongMethod:WalletTransactionsMediator.kt$WalletTransactionsMediator$override suspend fun load(loadType: LoadType, state: PagingState&lt;Int, WalletTransaction&gt;): MediatorResult</ID>
<ID>LongMethod:ZapSettingsScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ZapSettingsScreen( uiState: ZapSettingsContract.UiState, onClose: () -&gt; Unit, eventPublisher: (ZapSettingsContract.UiEvent) -&gt; Unit, )</ID>
<ID>LongParameterList:ArticleDetailsViewModel.kt$ArticleDetailsViewModel$( savedStateHandle: SavedStateHandle, private val activeAccountStore: ActiveAccountStore, private val articleRepository: ArticleRepository, private val feedRepository: FeedRepository, private val highlightRepository: HighlightRepository, private val profileRepository: ProfileRepository, private val eventRepository: EventRepository, private val zapHandler: ZapHandler, )</ID>
<ID>LongParameterList:ArticleDetailsViewModel.kt$ArticleDetailsViewModel$( private val savedStateHandle: SavedStateHandle, private val activeAccountStore: ActiveAccountStore, private val articleRepository: ArticleRepository, private val feedRepository: FeedRepository, private val highlightRepository: HighlightRepository, private val profileRepository: ProfileRepository, private val eventRepository: EventRepository, private val zapHandler: ZapHandler, )</ID>
<ID>LongParameterList:ContentAppearance.kt$ContentAppearance$( val noteBodyFontSize: TextUnit, val noteBodyLineHeight: TextUnit, val noteUsernameSize: TextUnit, val noteAvatarSize: Dp, val articleTextFontSize: TextUnit, val articleTextLineHeight: TextUnit, val tweetFontSize: TextUnit, val tweetLineHeight: TextUnit, )</ID>
<ID>LongParameterList:CreateAccountHandler.kt$CreateAccountHandler$( private val authRepository: AuthRepository, private val relayRepository: RelayRepository, private val userRepository: UserRepository, private val profileRepository: ProfileRepository, private val settingsRepository: SettingsRepository, private val credentialsStore: CredentialsStore, private val dispatchers: CoroutineDispatcherProvider, )</ID>
<ID>LongParameterList:CreateTransactionViewModel.kt$CreateTransactionViewModel$( savedStateHandle: SavedStateHandle, private val dispatchers: CoroutineDispatcherProvider, private val activeUserStore: ActiveAccountStore, private val profileRepository: ProfileRepository, private val walletRepository: WalletRepository, private val walletTextParser: WalletTextParser, private val exchangeRateHandler: ExchangeRateHandler, )</ID>
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/kotlin/net/primal/android/nostr/ext/Tags.kt
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ fun String.asPubkeyTag(relayHint: String? = null, optional: String? = null): Jso
add(optional ?: "")
}.removeTrailingEmptyStrings()

fun Nevent.asPubkeyTag(marker: String? = null): JsonArray =
this.userId.asPubkeyTag(
fun Nevent.asPubkeyTag(marker: String? = null): JsonArray? =
this.userId?.asPubkeyTag(
relayHint = this.relays.firstOrNull(),
optional = marker,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package net.primal.android.nostr.utils

data class Nevent(
val kind: Int?,
val userId: String,
val userId: String?,
val eventId: String,
val relays: List<String> = emptyList(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -76,20 +76,19 @@ object Nip19TLV {
String(bytes = it, charset = Charsets.US_ASCII)
} ?: emptyList()

val profileId = tlv[Type.AUTHOR.id]?.first()?.toHex()
val profileId = tlv[Type.AUTHOR.id]?.firstOrNull()?.toHex()

val kind = tlv[Type.KIND.id]?.firstOrNull()?.let {
toInt32(it)
}
return if (eventId != null && profileId != null) {

return eventId?.let {
Nevent(
kind = kind,
eventId = eventId,
userId = profileId,
relays = relays,
)
} else {
null
}
}

Expand Down Expand Up @@ -155,7 +154,7 @@ object Nip19TLV {
}

// Add AUTHOR type
tlv.addAll(this.userId.constructAuthorBytes())
this.userId?.let { tlv.addAll(this.userId.constructAuthorBytes()) }

// Add KIND type
if (this.kind != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import net.primal.android.thread.articles.details.ui.ArticleDetailsUi
interface ArticleDetailsContract {
data class UiState(
val naddr: Naddr? = null,
val isResolvingNaddr: Boolean = true,
val showHighlights: Boolean = true,
val isAuthorFollowed: Boolean = false,
val article: ArticleDetailsUi? = null,
Expand All @@ -34,6 +35,7 @@ interface ArticleDetailsContract {
}

sealed class UiEvent {
data object RequestResolveNaddr : UiEvent()
data object UpdateContent : UiEvent()
data object DismissErrors : UiEvent()
data class ZapArticle(val zapAmount: ULong? = null, val zapDescription: String? = null) : UiEvent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import net.primal.android.R
import net.primal.android.articles.feed.ui.ArticleDropdownMenuIcon
import net.primal.android.core.compose.AppBarIcon
import net.primal.android.core.compose.IconText
import net.primal.android.core.compose.ListNoContent
import net.primal.android.core.compose.PrimalDivider
import net.primal.android.core.compose.PrimalLoadingSpinner
import net.primal.android.core.compose.PrimalTopAppBar
Expand Down Expand Up @@ -323,8 +324,15 @@ private fun ArticleDetailsScreen(
)
},
content = { paddingValues ->
if (detailsState.article == null) {
if (detailsState.isResolvingNaddr) {
PrimalLoadingSpinner()
}
if (detailsState.article == null) {
ListNoContent(
modifier = Modifier.fillMaxSize(),
noContentText = stringResource(id = R.string.article_details_error_resolving_naddr),
onRefresh = { detailsEventPublisher(UiEvent.RequestResolveNaddr) },
)
} else {
ArticleContentWithComments(
state = detailsState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.primal.android.articles.ArticleRepository
import net.primal.android.core.errors.UiError
import net.primal.android.core.utils.authorNameUiFriendly
Expand Down Expand Up @@ -60,7 +59,7 @@ import timber.log.Timber

@HiltViewModel
class ArticleDetailsViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val savedStateHandle: SavedStateHandle,
private val activeAccountStore: ActiveAccountStore,
private val articleRepository: ArticleRepository,
private val feedRepository: FeedRepository,
Expand All @@ -70,25 +69,7 @@ class ArticleDetailsViewModel @Inject constructor(
private val zapHandler: ZapHandler,
) : ViewModel() {

private val naddr = savedStateHandle.naddr?.let { Nip19TLV.parseUriAsNaddrOrNull(it) }
?: runBlocking {
val identifier = savedStateHandle.articleId
val userId = savedStateHandle.primalName?.let {
runCatching { profileRepository.fetchProfileId(it) }.getOrNull()
}

if (identifier != null && userId != null) {
Naddr(
identifier = identifier,
userId = userId,
kind = NostrEventKind.LongFormContent.value,
)
} else {
null
}
}

private val _state = MutableStateFlow(UiState(naddr = naddr))
private val _state = MutableStateFlow(UiState())
val state = _state.asStateFlow()
private fun setState(reducer: UiState.() -> UiState) = _state.getAndUpdate { it.reducer() }

Expand All @@ -102,21 +83,30 @@ class ArticleDetailsViewModel @Inject constructor(
init {
observeEvents()
observeActiveAccount()
resolveNaddr()
}

private fun resolveNaddr() =
viewModelScope.launch {
setState { copy(isResolvingNaddr = true, error = null) }
val naddr = parseAndResolveNaddr()

if (naddr == null) {
setState { copy(error = UiError.InvalidNaddr) }
} else {
observeArticle(naddr)
observeArticleComments(naddr = naddr)
}

if (naddr == null) {
setState { copy(error = UiError.InvalidNaddr) }
} else {
observeArticle(naddr)
observeArticleComments(naddr = naddr)
setState { copy(isResolvingNaddr = false) }
}
}

@Suppress("CyclomaticComplexMethod")
private fun observeEvents() =
viewModelScope.launch {
events.collect {
when (it) {
UiEvent.UpdateContent -> fetchData(naddr)
UiEvent.UpdateContent -> fetchData()
UiEvent.DismissErrors -> dismissErrors()
is UiEvent.ZapArticle -> zapArticle(zapAction = it)
UiEvent.LikeArticle -> likeArticle()
Expand All @@ -138,13 +128,15 @@ class ArticleDetailsViewModel @Inject constructor(
articleAuthorId = selectedHighlight.referencedEventAuthorId,
)
}

UiEvent.RequestResolveNaddr -> resolveNaddr()
}
}
}

private fun fetchData(naddr: Naddr?) =
private fun fetchData() =
viewModelScope.launch {
if (naddr != null) {
state.value.naddr?.let { naddr ->
try {
articleRepository.fetchArticleAndComments(
userId = activeAccountStore.activeUserId(),
Expand Down Expand Up @@ -373,6 +365,8 @@ class ArticleDetailsViewModel @Inject constructor(
}

private fun publishNewHighlight(event: UiEvent.PublishHighlight) {
val naddr = state.value.naddr

publishAndSaveHighlight(
content = event.content,
context = event.context,
Expand Down Expand Up @@ -467,6 +461,25 @@ class ArticleDetailsViewModel @Inject constructor(
}
}

private suspend fun parseAndResolveNaddr() =
savedStateHandle.naddr?.let { Nip19TLV.parseUriAsNaddrOrNull(it) }
?: run {
val identifier = savedStateHandle.articleId
val userId = savedStateHandle.primalName?.let {
runCatching { profileRepository.fetchProfileId(it) }.getOrNull()
}

if (identifier != null && userId != null) {
Naddr(
identifier = identifier,
userId = userId,
kind = NostrEventKind.LongFormContent.value,
)
} else {
null
}
}

private fun toggleHighlightsVisibility() {
setState { copy(showHighlights = !showHighlights) }
}
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1178,6 +1178,9 @@
<item quantity="one">and 1 other highlighted</item>
<item quantity="other">and %1$s others highlighted</item>
</plurals>
<string name="article_details_error_resolving_naddr">
We couldn’t locate the article.\nMake sure you’ve got the right one.
</string>

<string name="feed_note_render_unknown_audio_title">Unknown title</string>
<string name="feed_note_render_play_button">Play</string>
Expand Down
17 changes: 10 additions & 7 deletions app/src/test/kotlin/net/primal/android/nostr/ext/TagsTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -246,16 +246,19 @@ class TagsTest {

@Test
fun `NeventAsPubkeyTag returns proper JsonArray tag`() {
val userId = "userId"

val nevent = Nevent(
userId = "userId",
userId = userId,
eventId = "eventId",
kind = NostrEventKind.Highlight.value,
relays = listOf("wss://relay.primal.net"),
)
val expectedRecommendedRelay = "wss://relay.primal.net"
val expectedOptional = "marker"
val actual = nevent.asPubkeyTag(
marker = expectedOptional,
val actual = userId.asPubkeyTag(
relayHint = expectedRecommendedRelay,
optional = expectedOptional,
)
actual shouldBe instanceOf(JsonArray::class)
actual.size shouldBe 4
Expand All @@ -267,15 +270,15 @@ class TagsTest {

@Test
fun `NeventAsPubkeyTag returns proper JsonArray tag if optional args null`() {
val userId = "userId"
val nevent = Nevent(
userId = "userId",
userId = userId,
eventId = "eventId",
kind = NostrEventKind.Highlight.value,
relays = emptyList(),
)
val actual = nevent.asPubkeyTag(
marker = null,
)
val actual = userId.asPubkeyTag()

actual shouldBe instanceOf(JsonArray::class)
actual.size shouldBe 2
actual[0].jsonPrimitive.content shouldBe "p"
Expand Down
37 changes: 32 additions & 5 deletions app/src/test/kotlin/net/primal/android/nostr/utils/Nip19TLVTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class Nip19TLVTest {

val tlv = Nip19TLV.parse(nevent1)

println(tlv[0]?.first()?.toString())

tlv shouldBe instanceOf(Map::class)
tlv.size shouldBe 2
val actualEventId = tlv[Nip19TLV.Type.SPECIAL.id]?.first()?.toHex()
Expand Down Expand Up @@ -152,6 +150,24 @@ class Nip19TLVTest {
naddr.toNaddrString() shouldBe expectedNaddr
}

@Test
fun parseUriAsNeventOrNull_returnsProperValuesForNoAuthorAndNeventNoUris() {
val nevent = "nostr:nevent1qvzqqqpxfgqzq4zfn02zu4cdg2xwrt8atpp2v8sfu4c2xwwxsx2llnx0d8xekj8vvs3qvg"

val expectedEventId = "54499bd42e570d428ce1acfd5842a61e09e570a339c68195ffcccf69cd9b48ec"
val expectedRelays = emptyList<String>()
val expectedUserId = null
val expectedKind = 9802

val result = Nip19TLV.parseUriAsNeventOrNull(nevent)
result.shouldNotBeNull()

result.eventId shouldBe expectedEventId
result.relays shouldBe expectedRelays
result.userId shouldBe expectedUserId
result.kind shouldBe expectedKind
}

@Test
fun parseUriAsNeventOrNull_returnsProperValuesForNeventNoUris() {
val nevent = "nostr:nevent1qqs9gjvm6sh9wr2z3ns6el2cg2npuz09wz3nn35pjhluenmfekd53mqzyrtp7w" +
Expand All @@ -164,7 +180,6 @@ class Nip19TLVTest {

val result = Nip19TLV.parseUriAsNeventOrNull(nevent)
result.shouldNotBeNull()
println(result)

result.eventId shouldBe expectedEventId
result.relays shouldBe expectedRelays
Expand All @@ -185,7 +200,6 @@ class Nip19TLVTest {

val result = Nip19TLV.parseUriAsNeventOrNull(nevent)
result.shouldNotBeNull()
println(result)

result.eventId shouldBe expectedEventId
result.relays shouldBe expectedRelays
Expand All @@ -206,14 +220,27 @@ class Nip19TLVTest {

val result = Nip19TLV.parseUriAsNeventOrNull(nevent)
result.shouldNotBeNull()
println(result)

result.eventId shouldBe expectedEventId
result.relays shouldBe expectedRelays
result.userId shouldBe expectedUserId
result.kind shouldBe expectedKind
}

@Test
fun toNeventString_createsProperNevent_forGivenNeventStructureWithoutRelaysAndNoAuthor() {
val expectedNevent = "nevent1qqs9gjvm6sh9wr2z3ns6el2cg2npuz09wz3nn35pjhluenmfekd53mqrqsqqqfj27mfm7c"

val nevent = Nevent(
eventId = "54499bd42e570d428ce1acfd5842a61e09e570a339c68195ffcccf69cd9b48ec",
relays = emptyList(),
userId = null,
kind = 9802,
)

nevent.toNeventString() shouldBe expectedNevent
}

@Test
fun toNeventString_createsProperNevent_forGivenNeventStructureWithoutRelays() {
val expectedNevent = "nevent1qqs9gjvm6sh9wr2z3ns6el2cg2npuz09wz3nn35pjhluenmfekd53mqzyrtp7w79k045g" +
Expand Down

0 comments on commit 481b425

Please sign in to comment.