Skip to content

Commit

Permalink
Merge branch 'main' into feature/deep-linking-for-legends-screen
Browse files Browse the repository at this point in the history
  • Loading branch information
mehmedalijaK authored Feb 21, 2025
2 parents c1cef5d + f073bf6 commit 66f9313
Show file tree
Hide file tree
Showing 25 changed files with 567 additions and 167 deletions.
4 changes: 3 additions & 1 deletion app/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
<ID>LongParameterList:NostrResources.kt$( eventId: String, eventIdToNostrEvent: Map&lt;String, NostrEvent&gt;, postIdToPostDataMap: Map&lt;String, PostData&gt;, articleIdToArticle: Map&lt;String, ArticleData&gt;, profileIdToProfileDataMap: Map&lt;String, ProfileData&gt;, cdnResources: Map&lt;String, CdnResource&gt;, linkPreviews: Map&lt;String, LinkPreviewData&gt;, videoThumbnails: Map&lt;String, String&gt;, )</ID>
<ID>LongParameterList:NostrResources.kt$( eventIdToNostrEvent: Map&lt;String, NostrEvent&gt;, postIdToPostDataMap: Map&lt;String, PostData&gt;, articleIdToArticle: Map&lt;String, ArticleData&gt;, profileIdToProfileDataMap: Map&lt;String, ProfileData&gt;, cdnResources: Map&lt;String, CdnResource&gt;, linkPreviews: Map&lt;String, LinkPreviewData&gt;, videoThumbnails: Map&lt;String, String&gt;, )</ID>
<ID>LongParameterList:NostrResources.kt$( refNote: PostData?, refPostAuthor: ProfileData?, cdnResources: Map&lt;String, CdnResource&gt;, linkPreviews: Map&lt;String, LinkPreviewData&gt;, videoThumbnails: Map&lt;String, String&gt;, eventIdToNostrEvent: Map&lt;String, NostrEvent&gt;, postIdToPostDataMap: Map&lt;String, PostData&gt;, articleIdToArticle: Map&lt;String, ArticleData&gt;, profileIdToProfileDataMap: Map&lt;String, ProfileData&gt;, )</ID>
<ID>LongParameterList:NoteEditorViewModel.kt$NoteEditorViewModel$( @Assisted private val args: NoteEditorArgs, private val dispatcherProvider: CoroutineDispatcherProvider, private val fileAnalyser: FileAnalyser, private val activeAccountStore: ActiveAccountStore, private val feedRepository: FeedRepository, private val notePublishHandler: NotePublishHandler, private val attachmentRepository: AttachmentsRepository, private val highlightRepository: HighlightRepository, private val exploreRepository: ExploreRepository, private val profileRepository: ProfileRepository, private val articleRepository: ArticleRepository, )</ID>
<ID>LongParameterList:NoteEditorViewModel.kt$NoteEditorViewModel$( @Assisted private val args: NoteEditorArgs, private val fileAnalyser: FileAnalyser, private val activeAccountStore: ActiveAccountStore, private val feedRepository: FeedRepository, private val notePublishHandler: NotePublishHandler, private val attachmentRepository: AttachmentsRepository, private val highlightRepository: HighlightRepository, private val exploreRepository: ExploreRepository, private val profileRepository: ProfileRepository, private val articleRepository: ArticleRepository, private val relayRepository: RelayRepository, private val relayHintsRepository: RelayHintsRepository, )</ID>
<ID>LongParameterList:ProfileDetailsViewModel.kt$ProfileDetailsViewModel$( savedStateHandle: SavedStateHandle, private val dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val feedsRepository: FeedsRepository, private val profileRepository: ProfileRepository, private val mutedUserRepository: MutedUserRepository, private val zapHandler: ZapHandler, )</ID>
<ID>LongParameterList:SubscriptionsManager.kt$SubscriptionsManager$( dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val userRepository: UserRepository, private val nostrNotary: NostrNotary, private val appConfigProvider: AppConfigProvider, @PrimalCacheApiClient private val cacheApiClient: PrimalApiClient, @PrimalWalletApiClient private val walletApiClient: PrimalApiClient, )</ID>
<ID>LongParameterList:TransactionDetailsViewModel.kt$TransactionDetailsViewModel$( savedStateHandle: SavedStateHandle, private val dispatcherProvider: CoroutineDispatcherProvider, private val activeAccountStore: ActiveAccountStore, private val walletRepository: WalletRepository, private val feedRepository: FeedRepository, private val articleRepository: ArticleRepository, private val exchangeRateHandler: ExchangeRateHandler, )</ID>
Expand Down Expand Up @@ -132,6 +132,8 @@
<ID>TooManyFunctions:ProfileRepository.kt$ProfileRepository</ID>
<ID>TooManyFunctions:Tags.kt$net.primal.android.nostr.ext.Tags.kt</ID>
<ID>TooManyFunctions:UserRepository.kt$UserRepository</ID>
<ID>TooManyFunctions:UsersApi.kt$UsersApi</ID>
<ID>TooManyFunctions:UsersApiImpl.kt$UsersApiImpl : UsersApi</ID>
<ID>TooManyFunctions:WalletApi.kt$WalletApi</ID>
<ID>TooManyFunctions:WalletApiImpl.kt$WalletApiImpl : WalletApi</ID>
<ID>TooManyFunctions:WalletRepository.kt$WalletRepository</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ enum class NoteAttachmentType {
Rumble,
Spotify,
Tidal,
GitHub,
Other,
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,35 @@ fun List<DirectMessageData>.flatMapMessagesAsNoteAttachmentPO() =
}

private fun detectNoteAttachmentType(url: String, mimeType: String?): NoteAttachmentType {
return when {
mimeType?.startsWith("image") == true -> NoteAttachmentType.Image
mimeType?.startsWith("video") == true -> NoteAttachmentType.Video
mimeType?.startsWith("audio") == true -> NoteAttachmentType.Audio
mimeType?.endsWith("pdf") == true -> NoteAttachmentType.Pdf
else -> {
when {
url.contains(".youtube.com") -> NoteAttachmentType.YouTube
url.contains("/youtube.com") -> NoteAttachmentType.YouTube
url.contains("/youtu.be") -> NoteAttachmentType.YouTube
url.contains(".rumble.com") -> NoteAttachmentType.Rumble
url.contains("/rumble.com") -> NoteAttachmentType.Rumble
url.contains("/open.spotify.com/") -> NoteAttachmentType.Spotify
url.contains("/listen.tidal.com/") -> NoteAttachmentType.Tidal
else -> NoteAttachmentType.Other
}
mimeType?.let {
val mimeTypeAttachment = detectMimeTypeAttachment(mimeType)
if (mimeTypeAttachment != NoteAttachmentType.Other) {
return mimeTypeAttachment
}
}

return detectUrlAttachmentType(url)
}

private fun detectMimeTypeAttachment(mimeType: String): NoteAttachmentType {
return when {
mimeType.startsWith("image") -> NoteAttachmentType.Image
mimeType.startsWith("video") -> NoteAttachmentType.Video
mimeType.startsWith("audio") -> NoteAttachmentType.Audio
mimeType.endsWith("pdf") -> NoteAttachmentType.Pdf
else -> NoteAttachmentType.Other
}
}

private fun detectUrlAttachmentType(url: String): NoteAttachmentType {
return when {
url.contains(".youtube.com") -> NoteAttachmentType.YouTube
url.contains("/youtube.com") -> NoteAttachmentType.YouTube
url.contains("/youtu.be") -> NoteAttachmentType.YouTube
url.contains(".rumble.com") || url.contains("/rumble.com") -> NoteAttachmentType.Rumble
url.contains("/open.spotify.com/") -> NoteAttachmentType.Spotify
url.contains("/listen.tidal.com/") -> NoteAttachmentType.Tidal
url.contains("/github.com/") -> NoteAttachmentType.GitHub
else -> NoteAttachmentType.Other
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package net.primal.android.attachments.repository

import java.util.*
import javax.inject.Inject
import kotlinx.coroutines.withContext
import net.primal.android.attachments.db.NoteAttachment
import net.primal.android.attachments.domain.NoteAttachmentType
import net.primal.android.core.coroutines.CoroutineDispatcherProvider
import net.primal.android.db.PrimalDatabase
import net.primal.android.networking.primal.upload.PrimalFileUploader
import net.primal.android.networking.primal.upload.UnsuccessfulFileUpload
Expand All @@ -14,6 +15,7 @@ class AttachmentsRepository @Inject constructor(
private val activeAccountStore: ActiveAccountStore,
private val fileUploader: PrimalFileUploader,
private val database: PrimalDatabase,
private val dispatchers: CoroutineDispatcherProvider,
) {

fun loadAttachments(noteId: String, types: List<NoteAttachmentType>): List<NoteAttachment> {
Expand All @@ -25,15 +27,16 @@ class AttachmentsRepository @Inject constructor(
attachment: net.primal.android.editor.domain.NoteAttachment,
uploadId: String,
onProgress: ((uploadedBytes: Int, totalBytes: Int) -> Unit)? = null,
): UploadResult {
val userId = activeAccountStore.activeUserId()
return fileUploader.uploadFile(
uri = attachment.localUri,
userId = userId,
uploadId = uploadId,
onProgress = onProgress,
)
}
): UploadResult =
withContext(dispatchers.io()) {
val userId = activeAccountStore.activeUserId()
fileUploader.uploadFile(
uri = attachment.localUri,
userId = userId,
uploadId = uploadId,
onProgress = onProgress,
)
}

suspend fun cancelNoteAttachmentUpload(uploadId: String) {
val userId = activeAccountStore.activeUserId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fun String.parseUris(includeNostrUris: Boolean = true): List<String> {
.filterInvalidTLDs()
.map { it.originalUrl }
val customUrls = this.detectUrls()
val mergedUrls = mergeUrls(emptyList(), customUrls)
val mergedUrls = mergeUrls(libUrls, customUrls)

return if (includeNostrUris) {
val nostr = this.parseNostrUris()
Expand Down
119 changes: 66 additions & 53 deletions app/src/main/kotlin/net/primal/android/editor/NoteEditorViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,12 @@ import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.primal.android.articles.ArticleRepository
import net.primal.android.articles.feed.ui.generateNaddr
import net.primal.android.articles.feed.ui.mapAsFeedArticleUi
import net.primal.android.attachments.repository.AttachmentsRepository
import net.primal.android.core.compose.profile.model.mapAsUserProfileUi
import net.primal.android.core.coroutines.CoroutineDispatcherProvider
import net.primal.android.core.files.FileAnalyser
import net.primal.android.crypto.hexToNoteHrp
import net.primal.android.crypto.hexToNpubHrp
import net.primal.android.editor.NoteEditorContract.SideEffect
import net.primal.android.editor.NoteEditorContract.UiEvent
import net.primal.android.editor.NoteEditorContract.UiState
Expand All @@ -53,20 +49,26 @@ import net.primal.android.networking.relays.errors.MissingRelaysException
import net.primal.android.networking.relays.errors.NostrPublishException
import net.primal.android.networking.sockets.errors.WssException
import net.primal.android.nostr.model.NostrEventKind
import net.primal.android.nostr.repository.RelayHintsRepository
import net.primal.android.nostr.utils.MAX_RELAY_HINTS
import net.primal.android.nostr.utils.Naddr
import net.primal.android.nostr.utils.Nevent
import net.primal.android.nostr.utils.Nip19TLV
import net.primal.android.nostr.utils.Nip19TLV.toNeventString
import net.primal.android.nostr.utils.Nip19TLV.toNprofileString
import net.primal.android.nostr.utils.Nprofile
import net.primal.android.notes.feed.model.FeedPostUi
import net.primal.android.notes.feed.model.asFeedPostUi
import net.primal.android.notes.repository.FeedRepository
import net.primal.android.premium.legend.domain.asLegendaryCustomization
import net.primal.android.profile.repository.ProfileRepository
import net.primal.android.user.accounts.active.ActiveAccountStore
import net.primal.android.user.accounts.active.ActiveUserAccountState
import net.primal.android.user.repository.RelayRepository
import timber.log.Timber

class NoteEditorViewModel @AssistedInject constructor(
@Assisted private val args: NoteEditorArgs,
private val dispatcherProvider: CoroutineDispatcherProvider,
private val fileAnalyser: FileAnalyser,
private val activeAccountStore: ActiveAccountStore,
private val feedRepository: FeedRepository,
Expand All @@ -76,6 +78,8 @@ class NoteEditorViewModel @AssistedInject constructor(
private val exploreRepository: ExploreRepository,
private val profileRepository: ProfileRepository,
private val articleRepository: ArticleRepository,
private val relayRepository: RelayRepository,
private val relayHintsRepository: RelayHintsRepository,
) : ViewModel() {

private val referencedNoteId = args.referencedNoteId
Expand Down Expand Up @@ -232,9 +236,7 @@ class NoteEditorViewModel @AssistedInject constructor(
private fun fetchNoteThreadFromNetwork(replyToNoteId: String) =
viewModelScope.launch {
try {
withContext(dispatcherProvider.io()) {
feedRepository.fetchReplies(noteId = replyToNoteId)
}
feedRepository.fetchReplies(noteId = replyToNoteId)
} catch (error: WssException) {
Timber.w(error)
}
Expand All @@ -243,12 +245,10 @@ class NoteEditorViewModel @AssistedInject constructor(
private fun fetchArticleDetailsFromNetwork(replyToArticleNaddr: Naddr) =
viewModelScope.launch {
try {
withContext(dispatcherProvider.io()) {
articleRepository.fetchArticleAndComments(
articleId = replyToArticleNaddr.identifier,
articleAuthorId = replyToArticleNaddr.userId,
)
}
articleRepository.fetchArticleAndComments(
articleId = replyToArticleNaddr.identifier,
articleAuthorId = replyToArticleNaddr.userId,
)
} catch (error: WssException) {
Timber.w(error)
}
Expand All @@ -274,20 +274,8 @@ class NoteEditorViewModel @AssistedInject constructor(
userId = activeAccountStore.activeUserId(),
content = noteContent,
attachments = _state.value.attachments,
rootNoteNevent = rootPost?.let {
Nevent(
kind = NostrEventKind.ShortTextNote.value,
userId = rootPost.authorId,
eventId = rootPost.postId,
)
},
replyToNoteNevent = replyToPost?.let {
Nevent(
kind = NostrEventKind.ShortTextNote.value,
userId = replyToPost.authorId,
eventId = replyToPost.postId,
)
},
rootNoteNevent = rootPost?.asNevent(),
replyToNoteNevent = replyToPost?.asNevent(),
rootArticleNaddr = referencedArticleNaddr
?: _state.value.referencedArticle?.generateNaddr(),
rootHighlightNevent = referencedHighlightNevent
Expand Down Expand Up @@ -329,12 +317,26 @@ class NoteEditorViewModel @AssistedInject constructor(
fetchNoteReplies()
}

private fun String.replaceUserMentionsWithUserIds(users: List<NoteTaggedUser>): String {
private suspend fun String.replaceUserMentionsWithUserIds(users: List<NoteTaggedUser>): String {
var content = this
val userRelaysMap = try {
relayRepository
.fetchAndUpdateUserRelays(userIds = users.map { it.userId })
.associateBy { it.pubkey }
} catch (error: WssException) {
Timber.w(error)
emptyMap()
}

users.forEach { user ->
val nprofile = Nprofile(
pubkey = user.userId,
relays = userRelaysMap[user.userId]?.relays
?.filter { it.write }?.map { it.url }?.take(MAX_RELAY_HINTS) ?: emptyList(),
)
content = content.replace(
oldValue = user.displayUsername,
newValue = "nostr:${user.userId.hexToNpubHrp()}",
newValue = "nostr:${nprofile.toNprofileString()}",
)
}
return content
Expand Down Expand Up @@ -380,19 +382,17 @@ class NoteEditorViewModel @AssistedInject constructor(
updatedAttachment = updatedAttachment.copy(uploadError = null)
updateNoteAttachmentState(attachment = updatedAttachment)

val uploadResult = withContext(dispatcherProvider.io()) {
attachmentRepository.uploadNoteAttachment(
attachment = attachment,
uploadId = uploadId,
onProgress = { uploadedBytes, totalBytes ->
updatedAttachment = updatedAttachment.copy(
originalUploadedInBytes = uploadedBytes,
originalSizeInBytes = totalBytes,
)
updateNoteAttachmentState(attachment = updatedAttachment)
},
)
}
val uploadResult = attachmentRepository.uploadNoteAttachment(
attachment = attachment,
uploadId = uploadId,
onProgress = { uploadedBytes, totalBytes ->
updatedAttachment = updatedAttachment.copy(
originalUploadedInBytes = uploadedBytes,
originalSizeInBytes = totalBytes,
)
updateNoteAttachmentState(attachment = updatedAttachment)
},
)

updatedAttachment = updatedAttachment.copy(
remoteUrl = uploadResult.remoteUrl,
Expand Down Expand Up @@ -503,7 +503,7 @@ class NoteEditorViewModel @AssistedInject constructor(
private fun fetchPopularUsers() =
viewModelScope.launch {
try {
val popularUsers = withContext(dispatcherProvider.io()) { exploreRepository.fetchPopularUsers() }
val popularUsers = exploreRepository.fetchPopularUsers()
setState { copy(popularUsers = popularUsers.map { it.mapAsUserProfileUi() }) }
} catch (error: WssException) {
Timber.w(error)
Expand All @@ -514,9 +514,7 @@ class NoteEditorViewModel @AssistedInject constructor(
viewModelScope.launch {
if (query.isNotEmpty()) {
try {
val result = withContext(dispatcherProvider.io()) {
exploreRepository.searchUsers(query = query, limit = 10)
}
val result = exploreRepository.searchUsers(query = query, limit = 10)
setState { copy(users = result.map { it.mapAsUserProfileUi() }) }
} catch (error: WssException) {
Timber.w(error)
Expand Down Expand Up @@ -545,16 +543,31 @@ class NoteEditorViewModel @AssistedInject constructor(

private fun markProfileInteraction(profileId: String) {
viewModelScope.launch {
withContext(dispatcherProvider.io()) {
profileRepository.markAsInteracted(profileId = profileId)
}
profileRepository.markAsInteracted(profileId = profileId)
}
}

private fun String.concatenateReferencedEvents() =
this + listOfNotNull(
args.referencedNoteId?.hexToNoteHrp(),
private suspend fun FeedPostUi.asNevent(): Nevent {
val relayHints = runCatching { relayHintsRepository.findRelaysByIds(listOf(this.postId)) }.getOrNull()

return Nevent(
kind = NostrEventKind.ShortTextNote.value,
userId = this.authorId,
eventId = this.postId,
relays = relayHints?.firstOrNull { it.eventId == this.postId }?.relays?.take(MAX_RELAY_HINTS)
?: emptyList(),
)
}

private suspend fun String.concatenateReferencedEvents(): String {
val referencedNoteNevent = referencedNoteId?.let { refNote ->
state.value.conversation.first { it.postId == refNote }
}?.asNevent()

return this + listOfNotNull(
referencedNoteNevent?.toNeventString(),
args.referencedHighlightNevent,
args.referencedArticleNaddr,
).joinToString(separator = " \n\n", prefix = " \n\n") { "nostr:$it" }
}
}
Loading

0 comments on commit 66f9313

Please sign in to comment.