Reverts to the non-Google kotlin style.

This commit is contained in:
Vitor Pamplona
2024-01-06 11:32:41 -05:00
parent 0be1f89368
commit f8e7dd78d9
415 changed files with 64333 additions and 64218 deletions
@@ -28,7 +28,6 @@ import com.vitorpamplona.amethyst.service.Nip96Retriever
import com.vitorpamplona.amethyst.service.Nip96Uploader
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
import com.vitorpamplona.quartz.crypto.KeyPair
import java.util.Base64
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.fail
@@ -36,6 +35,7 @@ import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import java.util.Base64
@RunWith(AndroidJUnit4::class)
class ImageUploadTesting {
@@ -111,32 +111,38 @@ class ImageUploadTesting {
}
@Test()
fun testNostrCheck() = runBlocking {
fun testNostrCheck() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostrcheck.me", "https://nostrcheck.me"))
}
@Test()
fun testNostrage() = runBlocking {
fun testNostrage() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostrage", "https://nostrage.com"))
}
@Test()
fun testSove() = runBlocking {
fun testSove() =
runBlocking {
testBase(Nip96MediaServers.ServerName("sove", "https://sove.rent"))
}
@Test()
fun testNostrBuild() = runBlocking {
fun testNostrBuild() =
runBlocking {
testBase(Nip96MediaServers.ServerName("nostr.build", "https://nostr.build"))
}
@Test()
fun testSovbit() = runBlocking {
fun testSovbit() =
runBlocking {
testBase(Nip96MediaServers.ServerName("sovbit", "https://files.sovbit.host"))
}
@Test()
fun testVoidCat() = runBlocking {
fun testVoidCat() =
runBlocking {
testBase(Nip96MediaServers.ServerName("void.cat", "https://void.cat"))
}
}
@@ -115,7 +115,8 @@ class ThreadAssemblerTest {
.trimIndent()
@Test
fun threadOrderTest() = runBlocking {
fun threadOrderTest() =
runBlocking {
val eventArray =
Event.mapper.readValue<ArrayList<Event>>(db) as List<Event> + Event.fromJson(header)
@@ -151,12 +151,11 @@ fun SelectNotificationProvider(sharedPreferencesViewModel: SharedPreferencesView
}
@Composable
fun LoadDistributors(
onInner: @Composable (String, ImmutableList<String>, ImmutableList<TitleExplainer>) -> Unit
) {
fun LoadDistributors(onInner: @Composable (String, ImmutableList<String>, ImmutableList<TitleExplainer>) -> Unit) {
val currentDistributor = PushDistributorHandler.getSavedDistributor().ifBlank { null } ?: "None"
val list = remember {
val list =
remember {
PushDistributorHandler.getInstalledDistributors().plus("None").toImmutableList()
}
@@ -36,8 +36,7 @@ fun TranslatableRichTextViewer(
backgroundColor: MutableState<Color>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) =
ExpandableRichTextViewer(
) = ExpandableRichTextViewer(
content,
canPreview,
modifier,
@@ -45,4 +44,4 @@ fun TranslatableRichTextViewer(
backgroundColor,
accountViewModel,
nav,
)
)
@@ -44,17 +44,16 @@ import androidx.compose.ui.unit.Velocity
fun Modifier.pullRefresh(
state: PullRefreshState,
enabled: Boolean = true,
) =
inspectable(
) = inspectable(
inspectorInfo =
debugInspectorInfo {
name = "pullRefresh"
properties["state"] = state
properties["enabled"] = enabled
},
) {
) {
Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
}
}
/**
* A nested scroll modifier that provides [onPull] and [onRelease] callbacks to aid building custom
@@ -81,8 +80,7 @@ fun Modifier.pullRefresh(
onPull: (pullDelta: Float) -> Float,
onRelease: suspend (flingVelocity: Float) -> Float,
enabled: Boolean = true,
) =
inspectable(
) = inspectable(
inspectorInfo =
debugInspectorInfo {
name = "pullRefresh"
@@ -90,9 +88,9 @@ fun Modifier.pullRefresh(
properties["onRelease"] = onRelease
properties["enabled"] = enabled
},
) {
) {
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
}
}
private class PullRefreshNestedScrollConnection(
private val onPull: (pullDelta: Float) -> Float,
@@ -39,15 +39,14 @@ import androidx.compose.ui.platform.inspectable
fun Modifier.pullRefreshIndicatorTransform(
state: PullRefreshState,
scale: Boolean = false,
) =
inspectable(
) = inspectable(
inspectorInfo =
debugInspectorInfo {
name = "pullRefreshIndicatorTransform"
properties["state"] = state
properties["scale"] = scale
},
) {
) {
Modifier
// Essentially we only want to clip the at the top, so the indicator will not appear when
// the position is 0. It is preferable to clip the indicator as opposed to the layout that
@@ -75,4 +74,4 @@ fun Modifier.pullRefreshIndicatorTransform(
scaleY = scaleFraction
}
}
}
}
@@ -35,10 +35,10 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.pow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.pow
/**
* Creates a [PullRefreshState] that is remembered across compositions.
@@ -98,12 +98,12 @@ fun rememberPullRefreshState(
* Should be created using [rememberPullRefreshState].
*/
class PullRefreshState
internal constructor(
internal constructor(
private val animationScope: CoroutineScope,
private val onRefreshState: State<() -> Unit>,
refreshingOffset: Float,
threshold: Float,
) {
) {
/**
* A float representing how far the user has pulled as a percentage of the refreshThreshold.
*
@@ -211,7 +211,7 @@ internal constructor(
threshold + extraOffset
}
}
}
}
/** Default parameter values for [rememberPullRefreshState]. */
object PullRefreshDefaults {
@@ -29,14 +29,14 @@ import android.util.Log
import coil.ImageLoader
import coil.disk.DiskCache
import com.vitorpamplona.amethyst.service.playback.VideoCache
import java.io.File
import kotlin.time.measureTimedValue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.io.File
import kotlin.time.measureTimedValue
class Amethyst : Application() {
val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@@ -53,13 +53,13 @@ import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.signers.ExternalSignerLauncher
import com.vitorpamplona.quartz.signers.NostrSignerExternal
import com.vitorpamplona.quartz.signers.NostrSignerInternal
import java.io.File
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Locale
// Release mode (!BuildConfig.DEBUG) always uses encrypted preferences
// To use plaintext SharedPreferences for debugging, set this to true
@@ -201,8 +201,7 @@ object LocalPreferences {
addAccount(accInfo)
}
suspend fun switchToAccount(accountInfo: AccountInfo) =
withContext(Dispatchers.IO) { updateCurrentAccount(accountInfo.npub) }
suspend fun switchToAccount(accountInfo: AccountInfo) = withContext(Dispatchers.IO) { updateCurrentAccount(accountInfo.npub) }
/** Removes the account from the app level shared preferences */
private suspend fun removeAccount(accountInfo: AccountInfo) {
@@ -93,11 +93,6 @@ import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.signers.NostrSignerExternal
import com.vitorpamplona.quartz.signers.NostrSignerInternal
import com.vitorpamplona.quartz.utils.DualCase
import java.math.BigDecimal
import java.net.Proxy
import java.util.Locale
import java.util.UUID
import kotlin.coroutines.resume
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableSet
@@ -117,6 +112,11 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import java.math.BigDecimal
import java.net.Proxy
import java.util.Locale
import java.util.UUID
import kotlin.coroutines.resume
val DefaultChannels =
setOf(
@@ -2233,9 +2233,13 @@ class Account(
fun isAcceptable(note: Note): Boolean {
return note.author?.let { isAcceptable(it) } ?: true && // if user hasn't hided this author
isAcceptableDirect(note) &&
((note.event !is RepostEvent && note.event !is GenericRepostEvent) ||
(note.replyTo?.firstOrNull { isAcceptableDirect(it) } !=
null)) // is not a reaction about a blocked post
(
(note.event !is RepostEvent && note.event !is GenericRepostEvent) ||
(
note.replyTo?.firstOrNull { isAcceptableDirect(it) } !=
null
)
) // is not a reaction about a blocked post
}
fun getRelevantReports(note: Note): Set<Note> {
@@ -2248,9 +2252,11 @@ class Account(
emptyList()
}
return (note.reportsBy(followsPlusMe) +
return (
note.reportsBy(followsPlusMe) +
(note.author?.reportsBy(followsPlusMe) ?: emptyList()) +
innerReports)
innerReports
)
.toSet()
}
@@ -32,8 +32,8 @@ import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toNote
import com.vitorpamplona.quartz.events.ChannelCreateEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import java.util.concurrent.ConcurrentHashMap
import kotlinx.coroutines.Dispatchers
import java.util.concurrent.ConcurrentHashMap
@Stable
class PublicChatChannel(idHex: String) : Channel(idHex) {
@@ -36,7 +36,8 @@ fun checkForHashtagWithIcon(
"btc",
"timechain",
"bitcoiner",
"bitcoiners", ->
"bitcoiners",
->
HashtagIcon(
R.drawable.ht_btc,
"Bitcoin",
@@ -46,7 +47,8 @@ fun checkForHashtagWithIcon(
"nostr",
"nostrich",
"nostriches",
"thenostr", ->
"thenostr",
->
HashtagIcon(
R.drawable.ht_nostr,
"Nostr",
@@ -54,7 +56,8 @@ fun checkForHashtagWithIcon(
Modifier.padding(1.dp, 2.dp, 0.dp, 0.dp),
)
"lightning",
"lightningnetwork", ->
"lightningnetwork",
->
HashtagIcon(
R.drawable.ht_lightning,
"Lightning",
@@ -70,7 +73,8 @@ fun checkForHashtagWithIcon(
"zapathon",
"zapraiser",
"zaplife",
"zapchain", ->
"zapchain",
->
HashtagIcon(
R.drawable.zap,
"Zap",
@@ -95,7 +99,8 @@ fun checkForHashtagWithIcon(
"ecash",
"nut",
"nuts",
"deeznuts", ->
"deeznuts",
->
HashtagIcon(
R.drawable.cashu,
"Cashu",
@@ -104,7 +109,8 @@ fun checkForHashtagWithIcon(
)
"plebs",
"pleb",
"plebchain", ->
"plebchain",
->
HashtagIcon(
R.drawable.plebs,
"Pleb",
@@ -113,7 +119,8 @@ fun checkForHashtagWithIcon(
)
"coffee",
"coffeechain",
"cafe", ->
"cafe",
->
HashtagIcon(
R.drawable.coffee,
"Coffee",
@@ -129,7 +136,8 @@ fun checkForHashtagWithIcon(
)
"grownostr",
"gardening",
"garden", ->
"garden",
->
HashtagIcon(
R.drawable.grownostr,
"GrowNostr",
@@ -145,13 +153,15 @@ fun checkForHashtagWithIcon(
)
"tunestr",
"music",
"nowplaying", ->
"nowplaying",
->
HashtagIcon(R.drawable.tunestr, "Tunestr", primary, Modifier.padding(0.dp, 3.dp, 0.dp, 1.dp))
"weed",
"weedstr",
"420",
"cannabis",
"marijuana", ->
"marijuana",
->
HashtagIcon(
R.drawable.weed,
"Weed",
@@ -94,13 +94,6 @@ import com.vitorpamplona.quartz.events.StatusEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableList
@@ -109,6 +102,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import java.util.concurrent.ConcurrentHashMap
object LocalCache {
val antiSpam = AntiSpamFilter()
@@ -1059,9 +1059,11 @@ object LocalCache {
refreshObservers(note)
}
@Suppress("UNUSED_PARAMETER") fun consume(event: ChannelHideMessageEvent) {}
@Suppress("UNUSED_PARAMETER")
fun consume(event: ChannelHideMessageEvent) {}
@Suppress("UNUSED_PARAMETER") fun consume(event: ChannelMuteUserEvent) {}
@Suppress("UNUSED_PARAMETER")
fun consume(event: ChannelMuteUserEvent) {}
fun consume(event: LnZapEvent) {
val note = getOrCreateNote(event.id)
@@ -1080,10 +1082,12 @@ object LocalCache {
val repliesTo =
event.zappedPost().mapNotNull { checkGetOrCreateNote(it) } +
event.taggedAddresses().map { getOrCreateAddressableNote(it) } +
((zapRequest.event as? LnZapRequestEvent)?.taggedAddresses()?.map {
(
(zapRequest.event as? LnZapRequestEvent)?.taggedAddresses()?.map {
getOrCreateAddressableNote(it)
}
?: emptySet<Note>())
?: emptySet<Note>()
)
note.loadEvent(event, author, repliesTo)
@@ -1401,29 +1405,37 @@ object LocalCache {
}
return notes.values.filter {
(it.event !is GenericRepostEvent &&
(
it.event !is GenericRepostEvent &&
it.event !is RepostEvent &&
it.event !is CommunityPostApprovalEvent &&
it.event !is ReactionEvent &&
it.event !is GiftWrapEvent &&
it.event !is LnZapEvent &&
it.event !is LnZapRequestEvent) &&
(it.event?.content()?.contains(text, true)
it.event !is LnZapRequestEvent
) &&
(
it.event?.content()?.contains(text, true)
?: false ||
it.event?.matchTag1With(text) ?: false ||
it.idHex.startsWith(text, true) ||
it.idNote().startsWith(text, true))
it.idNote().startsWith(text, true)
)
} +
addressables.values.filter {
(it.event !is GenericRepostEvent &&
(
it.event !is GenericRepostEvent &&
it.event !is RepostEvent &&
it.event !is CommunityPostApprovalEvent &&
it.event !is ReactionEvent &&
it.event !is GiftWrapEvent &&
it.event !is LnZapEvent &&
it.event !is LnZapRequestEvent) &&
(it.event?.content()?.contains(text, true)
?: false || it.event?.matchTag1With(text) ?: false || it.idHex.startsWith(text, true))
it.event !is LnZapRequestEvent
) &&
(
it.event?.content()?.contains(text, true)
?: false || it.event?.matchTag1With(text) ?: false || it.idHex.startsWith(text, true)
)
}
}
@@ -1454,10 +1466,12 @@ object LocalCache {
return addressables
.filter {
val noteEvent = it.value.event
(noteEvent is StatusEvent &&
(
noteEvent is StatusEvent &&
noteEvent.pubKey == user.pubkeyHex &&
!noteEvent.isExpired() &&
noteEvent.content.isNotBlank())
noteEvent.content.isNotBlank()
)
}
.values
.sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex }))
@@ -1558,12 +1572,14 @@ object LocalCache {
val toBeRemoved =
notes
.filter {
((it.value.event is TextNoteEvent && !it.value.isNewThread()) ||
(
(it.value.event is TextNoteEvent && !it.value.isNewThread()) ||
it.value.event is ReactionEvent ||
it.value.event is LnZapEvent ||
it.value.event is LnZapRequestEvent ||
it.value.event is ReportEvent ||
it.value.event is GenericRepostEvent) &&
it.value.event is GenericRepostEvent
) &&
it.value.replyTo?.any { it.liveSet?.isInUse() == true } != true &&
it.value.liveSet?.isInUse() != true && // don't delete if observing.
it.value.author?.pubkeyHex !in
@@ -1662,8 +1678,10 @@ object LocalCache {
account.liveHiddenUsers.value
?.hiddenUsers
?.map { userHex ->
(notes.values.filter { it.event?.pubKey() == userHex } +
addressables.values.filter { it.event?.pubKey() == userHex })
(
notes.values.filter { it.event?.pubKey() == userHex } +
addressables.values.filter { it.event?.pubKey() == userHex }
)
.toSet()
}
?.flatten()
@@ -63,12 +63,12 @@ import com.vitorpamplona.quartz.events.WrappedEvent
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.containsAny
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import java.math.BigDecimal
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@Stable
class AddressableNote(val address: ATag) : Note(address.toTag()) {
@@ -223,7 +223,8 @@ open class Note(val idHex: String) {
}
val parent =
(replyTo
(
replyTo
.filter {
it.idHex in eventsToConsider
} // This forces the signature to be based on a branch, avoiding two roots
@@ -239,7 +240,8 @@ open class Note(val idHex: String) {
)
.apply { cachedSignatures.put(it, this) }
}
.maxByOrNull { it.signature.length })
.maxByOrNull { it.signature.length }
)
val parentSignature = parent?.signature?.removeSuffix(";") ?: ""
@@ -717,15 +719,19 @@ open class Note(val idHex: String) {
fun hasAnyReports(): Boolean {
val dayAgo = TimeUtils.oneDayAgo()
return reports.isNotEmpty() ||
(author?.reports?.any { it.value.firstOrNull { (it.createdAt() ?: 0) > dayAgo } != null }
?: false)
(
author?.reports?.any { it.value.firstOrNull { (it.createdAt() ?: 0) > dayAgo } != null }
?: false
)
}
fun isNewThread(): Boolean {
return (event is RepostEvent ||
return (
event is RepostEvent ||
event is GenericRepostEvent ||
replyTo == null ||
replyTo?.size == 0) &&
replyTo?.size == 0
) &&
event !is ChannelMessageEvent &&
event !is LiveActivitiesChatMessageEvent
}
@@ -47,8 +47,7 @@ class RelayInformation(
val mapper =
jacksonObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
fun fromJson(json: String): RelayInformation =
mapper.readValue(json, RelayInformation::class.java)
fun fromJson(json: String): RelayInformation = mapper.readValue(json, RelayInformation::class.java)
}
}
@@ -37,8 +37,7 @@ object UrlCachedPreviewer {
suspend fun previewInfo(
url: String,
onReady: suspend (UrlPreviewState) -> Unit,
) =
withContext(Dispatchers.IO) {
) = withContext(Dispatchers.IO) {
cache[url]?.let {
onReady(it)
return@withContext
@@ -43,10 +43,10 @@ import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.ReportEvent
import com.vitorpamplona.quartz.events.UserMetadata
import com.vitorpamplona.quartz.events.toImmutableListOfLists
import java.math.BigDecimal
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import java.math.BigDecimal
@Stable
class User(val pubkeyHex: String) {
@@ -187,8 +187,7 @@ object BlurHashDecoder {
calculate: Boolean,
height: Int,
numCompY: Int,
) =
when {
) = when {
calculate -> {
DoubleArray(height * numCompY).also { cacheCosinesY[height * numCompY] = it }
}
@@ -201,8 +200,7 @@ object BlurHashDecoder {
calculate: Boolean,
width: Int,
numCompX: Int,
) =
when {
) = when {
calculate -> {
DoubleArray(width * numCompX).also { cacheCosinesX[width * numCompX] = it }
}
@@ -35,7 +35,6 @@ import com.vitorpamplona.amethyst.ui.components.removeQueryParamsForExtensionCom
import com.vitorpamplona.amethyst.ui.components.tagIndex
import com.vitorpamplona.amethyst.ui.components.videoExtensions
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import java.util.regex.Pattern
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.ImmutableSet
@@ -43,6 +42,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet
import java.util.regex.Pattern
@Immutable
data class RichTextViewerState(
@@ -28,10 +28,10 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import com.vitorpamplona.quartz.events.Event
import java.util.Base64
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.Base64
@Immutable
data class CashuToken(
@@ -29,9 +29,11 @@ fun String.isUTF16Char(pos: Int): Boolean {
fun String.firstFullCharOld(): String {
return when (this.length) {
0,
1, -> return this
1,
-> return this
2,
3, -> return if (isUTF16Char(0)) this.take(2) else this.take(1)
3,
-> return if (isUTF16Char(0)) this.take(2) else this.take(1)
else -> {
val first = isUTF16Char(0)
val second = isUTF16Char(2)
@@ -65,7 +67,8 @@ fun String.firstFullChar(): String {
start = next
} else {
// If join, searches for the next char
if (codePoint == 0xFE0F) {} else if (codePoint == 0x200D) {
if (codePoint == 0xFE0F) {
} else if (codePoint == 0x200D) {
isInJoin = true
} else {
// stops when two chars are not joined together
@@ -260,5 +260,6 @@ class ByteArrayMediaDataSource(var imageData: ByteArray) : MediaDataSource() {
return newSize
}
@Throws(IOException::class) override fun close() {}
@Throws(IOException::class)
override fun close() {}
}
@@ -22,15 +22,15 @@ package com.vitorpamplona.amethyst.service
import android.util.Log
import com.vitorpamplona.amethyst.BuildConfig
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.time.Duration
import kotlin.properties.Delegates
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
object HttpClient {
val DEFAULT_TIMEOUT_ON_WIFI = Duration.ofSeconds(10L)
@@ -47,8 +47,7 @@ class Nip05NostrAddressVerifier() {
nip05: String,
onSuccess: (String) -> Unit,
onError: (String) -> Unit,
) =
withContext(Dispatchers.IO) {
) = withContext(Dispatchers.IO) {
checkNotInMainThread()
val url = assembleUrl(nip05)
@@ -23,11 +23,11 @@ package com.vitorpamplona.amethyst.service
import android.util.Log
import android.util.LruCache
import com.vitorpamplona.amethyst.model.RelayInformation
import java.io.IOException
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import java.io.IOException
object Nip11CachedRetriever {
val relayInformationDocumentCache = LruCache<String, RelayInformation>(100)
@@ -30,9 +30,6 @@ import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.model.Account
import java.io.InputStream
import java.util.Base64
import kotlin.coroutines.resume
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withTimeoutOrNull
@@ -42,6 +39,9 @@ import okhttp3.Request
import okhttp3.RequestBody
import okio.BufferedSink
import okio.source
import java.io.InputStream
import java.util.Base64
import kotlin.coroutines.resume
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
@@ -214,7 +214,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
),
)
val accountChannel = requestNewChannel { time, relayUrl ->
val accountChannel =
requestNewChannel { time, relayUrl ->
if (hasLoadedTheBasics[account.userProfile()] != null) {
latestEOSEs.addOrUpdate(
account.userProfile(),
@@ -93,7 +93,8 @@ object NostrChatroomDataSource : NostrDataSource("ChatroomFeed") {
latestEOSEs.removeDataFor(account.userProfile())
}
val inandoutChannel = requestNewChannel { time, relayUrl ->
val inandoutChannel =
requestNewChannel { time, relayUrl ->
latestEOSEs.addOrUpdate(account.userProfile(), withRoom.hashCode().toString(), relayUrl, time)
}
@@ -131,7 +131,8 @@ object NostrChatroomListDataSource : NostrDataSource("MailBoxFeed") {
}
}
val chatroomListChannel = requestNewChannel { time, relayUrl ->
val chatroomListChannel =
requestNewChannel { time, relayUrl ->
latestEOSEs.addOrUpdate(account.userProfile(), chatRoomList, relayUrl, time)
}
@@ -28,14 +28,14 @@ import com.vitorpamplona.amethyst.service.relays.Subscription
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
abstract class NostrDataSource(val debugName: String) {
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
@@ -392,7 +392,8 @@ object NostrDiscoveryDataSource : NostrDataSource("DiscoveryFeed") {
)
}
val discoveryFeedChannel = requestNewChannel { time, relayUrl ->
val discoveryFeedChannel =
requestNewChannel { time, relayUrl ->
latestEOSEs.addOrUpdate(
account.userProfile(),
account.defaultDiscoveryFollowList.value,
@@ -212,7 +212,8 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
)
}
val followAccountChannel = requestNewChannel { time, relayUrl ->
val followAccountChannel =
requestNewChannel { time, relayUrl ->
latestEOSEs.addOrUpdate(
account.userProfile(),
account.defaultHomeFollowList.value,
@@ -43,8 +43,10 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
private fun createReactionsToWatchInAddressFilter(): List<TypedFilter>? {
val addressesToWatch =
(eventsToWatch.filter { it.address() != null } +
addressesToWatch.filter { it.address() != null })
(
eventsToWatch.filter { it.address() != null } +
addressesToWatch.filter { it.address() != null }
)
.toSet()
if (addressesToWatch.isEmpty()) {
@@ -168,7 +170,8 @@ object NostrSingleEventDataSource : NostrDataSource("SingleEventFeed") {
)
}
val singleEventChannel = requestNewChannel { time, relayUrl ->
val singleEventChannel =
requestNewChannel { time, relayUrl ->
// Ignores EOSE if it is in the middle of a filter change.
if (changingFilters.get()) return@requestNewChannel
@@ -86,7 +86,8 @@ object NostrSingleUserDataSource : NostrDataSource("SingleUserFeed") {
.flatten()
}
val userChannel = requestNewChannel { time, relayUrl ->
val userChannel =
requestNewChannel { time, relayUrl ->
checkNotInMainThread()
usersToWatch.forEach {
@@ -51,7 +51,8 @@ object NostrThreadDataSource : NostrDataSource("SingleThreadFeed") {
)
}
val loadEventsChannel = requestNewChannel { _, _ ->
val loadEventsChannel =
requestNewChannel { _, _ ->
// Many relays operate with limits in the amount of filters.
// As information comes, the filters will be rotated to get more data.
invalidateFilters()
@@ -131,7 +131,8 @@ object NostrVideoDataSource : NostrDataSource("VideoFeed") {
)
}
val videoFeedChannel = requestNewChannel { time, relayUrl ->
val videoFeedChannel =
requestNewChannel { time, relayUrl ->
latestEOSEs.addOrUpdate(
account.userProfile(),
account.defaultStoriesFollowList.value,
@@ -32,12 +32,12 @@ import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse
import com.vitorpamplona.quartz.events.ZapSplitSetup
import kotlin.math.round
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.round
class ZapPaymentHandler(val account: Account) {
@Immutable
@@ -58,8 +58,7 @@ class ZapPaymentHandler(val account: Account) {
onProgress: (percent: Float) -> Unit,
onPayViaIntent: (ImmutableList<Payable>) -> Unit,
zapType: LnZapEvent.ZapType,
) =
withContext(Dispatchers.IO) {
) = withContext(Dispatchers.IO) {
val zapSplitSetup = note.event?.zapSplitSetup()
val noteEvent = note.event
@@ -29,10 +29,10 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
import com.vitorpamplona.quartz.encoders.Lud06
import com.vitorpamplona.quartz.encoders.toLnUrl
import okhttp3.Request
import java.math.BigDecimal
import java.math.RoundingMode
import java.net.URLEncoder
import okhttp3.Request
class LightningAddressResolver() {
val client = HttpClient.getHttpClient()
@@ -28,9 +28,11 @@ object UserZaps {
fun forProfileFeed(zaps: Map<Note, Note?>?): List<ZapReqResponse> {
if (zaps == null) return emptyList()
return (zaps
return (
zaps
.mapNotNull { entry -> entry.value?.let { ZapReqResponse(entry.key, it) } }
.sortedBy { (it.zapEvent.event as? LnZapEventInterface)?.amount() }
.reversed())
.reversed()
)
}
}
@@ -40,8 +40,8 @@ import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.SealedGossipEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import java.math.BigDecimal
import kotlinx.collections.immutable.persistentSetOf
import java.math.BigDecimal
class EventNotificationConsumer(private val applicationContext: Context) {
suspend fun consume(event: GiftWrapEvent) {
@@ -118,8 +118,10 @@ class EventNotificationConsumer(private val applicationContext: Context) {
val followingKeySet = acc.followingKeySet()
val isKnownRoom =
(acc.userProfile().privateChatrooms[chatRoom]?.senderIntersects(followingKeySet) == true ||
acc.userProfile().hasSentMessagesTo(chatRoom)) && !acc.isAllHidden(chatRoom.users)
(
acc.userProfile().privateChatrooms[chatRoom]?.senderIntersects(followingKeySet) == true ||
acc.userProfile().hasSentMessagesTo(chatRoom)
) && !acc.isAllHidden(chatRoom.users)
if (isKnownRoom) {
val content = chatNote.event?.content() ?: ""
@@ -157,8 +159,10 @@ class EventNotificationConsumer(private val applicationContext: Context) {
.privateChatrooms
.keys
.filter {
(acc.userProfile().privateChatrooms[it]?.senderIntersects(followingKeySet) == true ||
acc.userProfile().hasSentMessagesTo(it)) && !acc.isAllHidden(it.users)
(
acc.userProfile().privateChatrooms[it]?.senderIntersects(followingKeySet) == true ||
acc.userProfile().hasSentMessagesTo(it)
) && !acc.isAllHidden(it.users)
}
.toSet()
@@ -32,11 +32,11 @@ import androidx.media3.common.Player.STATE_READY
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.MediaSession
import com.vitorpamplona.amethyst.ui.MainActivity
import kotlin.math.abs
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.math.abs
class MultiPlayerPlaybackManager(
private val dataSourceFactory: androidx.media3.exoplayer.source.MediaSource.Factory? = null,
@@ -27,8 +27,8 @@ import androidx.media3.datasource.cache.CacheDataSource
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
import androidx.media3.datasource.cache.SimpleCache
import androidx.media3.datasource.okhttp.OkHttpDataSource
import java.io.File
import okhttp3.OkHttpClient
import java.io.File
@SuppressLint("UnsafeOptInUsageError")
class VideoCache {
@@ -21,8 +21,8 @@
package com.vitorpamplona.amethyst.service.previews
import androidx.compose.runtime.Immutable
import java.net.URL
import okhttp3.MediaType
import java.net.URL
@Immutable
class UrlInfoItem(
@@ -24,12 +24,12 @@ import android.util.Log
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventInterface
import java.util.UUID
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.UUID
/**
* The Nostr Client manages multiple personae the user may switch between. Events are received and
@@ -31,12 +31,12 @@ import com.vitorpamplona.quartz.events.EventInterface
import com.vitorpamplona.quartz.events.RelayAuthEvent
import com.vitorpamplona.quartz.events.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.TimeUtils
import java.lang.StringBuilder
import java.util.concurrent.atomic.AtomicBoolean
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import java.lang.StringBuilder
import java.util.concurrent.atomic.AtomicBoolean
enum class FeedType {
FOLLOWS,
@@ -156,8 +156,7 @@ inline fun TextToSpeech.setListener(
crossinline onError: (String?) -> Unit = {},
crossinline onRange: (Int, Int) -> Unit = { _, _ -> },
crossinline onDone: (String?) -> Unit,
) =
this.apply {
) = this.apply {
setOnUtteranceProgressListener(
object : UtteranceProgressListener() {
override fun onStart(p0: String?) {
@@ -184,4 +183,4 @@ inline fun TextToSpeech.setListener(
}
},
)
}
}
@@ -174,4 +174,4 @@ class TextToSpeechHelper private constructor(private val context: WeakReference<
destroy { customActionForDestroy?.invoke() }
}
}
}
}
@@ -64,14 +64,14 @@ import com.vitorpamplona.quartz.events.ChannelMetadataEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import com.vitorpamplona.quartz.events.PrivateDmEvent
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.Timer
import kotlin.concurrent.schedule
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.util.Timer
import kotlin.concurrent.schedule
class MainActivity : AppCompatActivity() {
private val isOnMobileDataState = mutableStateOf(false)
@@ -20,9 +20,9 @@
*/
package com.vitorpamplona.amethyst.ui.actions
import kotlinx.coroutines.delay
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.coroutines.delay
class ImageDownloader {
suspend fun waitAndGetImage(imageUrl: String): ByteArray? {
@@ -31,8 +31,6 @@ import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.service.HttpClient
import java.io.File
import java.util.UUID
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
@@ -42,6 +40,8 @@ import okio.IOException
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.util.UUID
object ImageSaver {
/**
@@ -223,7 +223,8 @@ private fun RenderSearch(
}
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
val observer =
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Join Start")
NostrSearchEventOrUserDataSource.start()
@@ -323,7 +324,8 @@ private fun RenderSearchResults(
val users by searchBarViewModel.searchResultsUsers.collectAsStateWithLifecycle()
val channels by searchBarViewModel.searchResultsChannels.collectAsStateWithLifecycle()
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -199,7 +199,8 @@ fun ImageVideoPost(
),
)
val fileServerOptions = remember {
val fileServerOptions =
remember {
fileServers.map { TitleExplainer(it.server.name, it.server.baseUrl) }.toImmutableList()
}
val resolver = LocalContext.current.contentResolver
@@ -47,7 +47,7 @@ fun NewPollRecipientsField(
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = pollViewModel.zapRecipients[0],
onValueChange = { /* TODO */},
onValueChange = { /* TODO */ },
enabled = false,
label = {
Text(
@@ -177,7 +177,6 @@ import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.toImmutableListOfLists
import java.lang.Math.round
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
@@ -185,6 +184,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.Math.round
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -911,7 +911,8 @@ fun SellProduct(postViewModel: NewPostViewModel) {
),
)
val conditionOptions = remember {
val conditionOptions =
remember {
conditionTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList()
}
@@ -970,7 +971,8 @@ fun SellProduct(postViewModel: NewPostViewModel) {
val categoryTypes = categoryList.map { Triple(it, stringResource(id = it), null) }
val categoryOptions = remember {
val categoryOptions =
remember {
categoryTypes.map { TitleExplainer(it.second, null) }.toImmutableList()
}
TextSpinner(
@@ -1587,7 +1589,8 @@ fun ImageVideoDescription(
),
)
val fileServerOptions = remember {
val fileServerOptions =
remember {
fileServers.map { TitleExplainer(it.server.name, it.server.baseUrl) }.toImmutableList()
}
@@ -63,7 +63,6 @@ import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.ZapSplitSetup
import com.vitorpamplona.quartz.events.findURLs
import java.net.URLEncoder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.BufferOverflow
@@ -71,6 +70,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import java.net.URLEncoder
enum class UserSuggestionAnchor {
MAIN_MESSAGE,
@@ -682,14 +682,22 @@ open class NewPostViewModel() : ViewModel() {
!wantsInvoice &&
(!wantsZapraiser || zapRaiserAmount != null) &&
(!wantsDirectMessage || !toUsers.text.isNullOrBlank()) &&
(!wantsPoll ||
(pollOptions.values.all { it.isNotEmpty() } &&
(
!wantsPoll ||
(
pollOptions.values.all { it.isNotEmpty() } &&
isValidvalueMinimum.value &&
isValidvalueMaximum.value)) &&
(!wantsProduct ||
(!title.text.isNullOrBlank() &&
isValidvalueMaximum.value
)
) &&
(
!wantsProduct ||
(
!title.text.isNullOrBlank() &&
!price.text.isNullOrBlank() &&
!category.text.isNullOrBlank())) &&
!category.text.isNullOrBlank()
)
) &&
contentToAddUrl == null
}
@@ -935,7 +943,8 @@ open class NewPostViewModel() : ViewModel() {
} else {
valueMinimum = int
}
} catch (e: Exception) {}
} catch (e: Exception) {
}
} else {
valueMinimum = null
}
@@ -952,7 +961,8 @@ open class NewPostViewModel() : ViewModel() {
} else {
valueMaximum = int
}
} catch (e: Exception) {}
} catch (e: Exception) {
}
} else {
valueMaximum = null
}
@@ -101,8 +101,8 @@ import com.vitorpamplona.amethyst.ui.theme.allGoodColor
import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.warningColor
import java.lang.Math.round
import kotlinx.coroutines.launch
import java.lang.Math.round
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -331,7 +331,8 @@ fun ServerConfig(
)
}
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -930,10 +931,9 @@ fun countToHumanReadableBytes(counter: Int) =
fun countToHumanReadable(
counter: Int,
str: String,
) =
when {
) = when {
counter >= 1000000000 -> "${round(counter / 1000000000f)}G $str"
counter >= 1000000 -> "${round(counter / 1000000f)}M $str"
counter >= 1000 -> "${round(counter / 1000f)}K $str"
else -> "$counter $str"
}
}
@@ -35,12 +35,12 @@ import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.quartz.events.GitHubIdentity
import com.vitorpamplona.quartz.events.MastodonIdentity
import com.vitorpamplona.quartz.events.TwitterIdentity
import java.io.ByteArrayInputStream
import java.io.StringWriter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.StringWriter
class NewUserMetadataViewModel : ViewModel() {
private lateinit var account: Account
@@ -69,7 +69,8 @@ fun RelayInformationDialog(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -34,8 +34,8 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import java.io.File
import kotlinx.coroutines.launch
import java.io.File
/**
* A button to save the remote image to the gallery. May require a storage permission.
@@ -58,9 +58,9 @@ import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.GetMediaActivityResultContract
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import java.util.concurrent.atomic.AtomicBoolean
@OptIn(ExperimentalPermissionsApi::class)
@Composable
@@ -49,7 +49,8 @@ fun buildAnnotatedStringWithUrlHighlighting(
): TransformedText {
val substitutions = mutableListOf<RangesChanges>()
val newText = buildAnnotatedString {
val newText =
buildAnnotatedString {
val builderBefore = StringBuilder() // important to correctly measure Tag start and end
val builderAfter = StringBuilder() // important to correctly measure Tag start and end
append(
@@ -22,8 +22,6 @@ package com.vitorpamplona.amethyst.ui.components
import androidx.compose.runtime.Stable
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -33,6 +31,8 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean
/** This class is designed to have a waiting time between two calls of invalidate */
@Stable
@@ -625,7 +625,8 @@ fun ClickableInLineIconRenderer(
}
.associate { it.first to it.second }
val annotatedText = buildAnnotatedString {
val annotatedText =
buildAnnotatedString {
wordsInOrder.forEachIndexed { idx, value ->
withStyle(
style,
@@ -700,7 +701,8 @@ fun InLineIconRenderer(
}
.associate { it.first to it.second }
val annotatedText = remember {
val annotatedText =
remember {
buildAnnotatedString {
wordsInOrder.forEachIndexed { idx, value ->
withStyle(
@@ -58,9 +58,9 @@ import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import com.vitorpamplona.quartz.encoders.LnInvoiceUtil
import java.text.NumberFormat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.text.NumberFormat
@Stable data class InvoiceAmount(val invoice: String, val amount: String?)
@@ -104,12 +104,12 @@ import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.amethyst.ui.uriToRoute
import com.vitorpamplona.quartz.encoders.Nip19
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.MalformedURLException
import java.net.URISyntaxException
import java.net.URL
import java.util.regex.Pattern
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg")
val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov", "mp3", "m3u8")
@@ -393,7 +393,8 @@ private fun RenderContentAsMarkdown(
nav: (String) -> Unit,
) {
val uri = LocalUriHandler.current
val onClick = remember {
val onClick =
remember {
{ link: String ->
val route = uriToRoute(link)
if (route != null) {
@@ -641,7 +642,8 @@ private fun RenderHashtag(
val regularText = remember { SpanStyle(color = background) }
val clickableTextStyle = remember { SpanStyle(color = primary) }
val annotatedTermsString = remember {
val annotatedTermsString =
remember {
buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToHashtag", "")
@@ -32,8 +32,8 @@ import coil.request.ImageRequest
import coil.request.Options
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.quartz.utils.Robohash
import java.nio.charset.Charset
import okio.Buffer
import java.nio.charset.Charset
@Stable
class HashImageFetcher(
@@ -51,7 +51,8 @@ class HashImageFetcher(
Charset.defaultCharset(),
)
buffer
} finally {}
} finally {
}
return SourceResult(
source = ImageSource(source, context),
@@ -105,8 +105,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size50Modifier
import com.vitorpamplona.amethyst.ui.theme.Size75dp
import com.vitorpamplona.amethyst.ui.theme.VolumeBottomIconSize
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import java.util.UUID
import kotlin.math.abs
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -114,6 +112,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import java.util.UUID
import kotlin.math.abs
public val DEFAULT_MUTED_SETTING = mutableStateOf(true)
@@ -195,7 +195,8 @@ fun VideoView(
) {
val defaultToStart by remember(videoUri) { mutableStateOf(DEFAULT_MUTED_SETTING.value) }
val automaticallyStartPlayback = remember {
val automaticallyStartPlayback =
remember {
mutableStateOf<Boolean>(
if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback.value,
)
@@ -488,7 +489,8 @@ fun GetVideoController(
// User pauses and resumes the app. What to do with videos?
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
val observer =
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
// if the controller is null, restarts the controller with a new one
// if the controller is not null, just continue playing what the controller was playing
@@ -654,7 +656,8 @@ private fun RenderVideoPlayer(
) {
val borders = MaterialTheme.colorScheme.imageModifier
val myModifier = remember {
val myModifier =
remember {
if (roundedCorner) {
modifier.then(
borders.defaultMinSize(minHeight = 75.dp).align(Alignment.Center),
@@ -721,7 +724,8 @@ private fun RenderVideoPlayer(
val startingMuteState = remember(controller) { controller.volume < 0.001 }
val topPadding = remember {
val topPadding =
remember {
derivedStateOf {
if (topPaddingForControllers.isSpecified && videoPlaybackHeight.value.value > 0) {
val space = (abs(this.maxHeight.value - videoPlaybackHeight.value.value) / 2).dp
@@ -898,13 +902,14 @@ fun ControlWhenPlayerIsActive(
}
}
fun Modifier.onVisiblePositionChanges(onVisiblePosition: (Float?) -> Unit): Modifier = composed {
fun Modifier.onVisiblePositionChanges(onVisiblePosition: (Float?) -> Unit): Modifier =
composed {
val view = LocalView.current
onGloballyPositioned { coordinates ->
onVisiblePosition(coordinates.getDistanceToVertCenterIfVisible(view))
}
}
}
fun LayoutCoordinates.getDistanceToVertCenterIfVisible(view: View): Float? {
if (!isAttached) return null
@@ -941,7 +946,8 @@ private fun MuteButton(
topPadding: State<Dp>,
toggle: (Boolean) -> Unit,
) {
val holdOn = remember {
val holdOn =
remember {
mutableStateOf<Boolean>(
true,
)
@@ -129,7 +129,6 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.imageModifier
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.encoders.toHexKey
import java.io.File
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
@@ -137,6 +136,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.engawapg.lib.zoomable.rememberZoomState
import net.engawapg.lib.zoomable.zoomable
import java.io.File
@Immutable
abstract class ZoomableContent(
@@ -311,13 +311,15 @@ private fun LocalImageView(
) {
if (content.localFile != null && content.localFile.exists()) {
BoxWithConstraints(contentAlignment = Alignment.Center) {
val showImage = remember {
val showImage =
remember {
mutableStateOf(
if (alwayShowImage) true else accountViewModel.settings.showImages.value,
)
}
val myModifier = remember {
val myModifier =
remember {
mainImageModifier.widthIn(max = maxWidth).heightIn(max = maxHeight)
/*
.run {
@@ -328,7 +330,8 @@ private fun LocalImageView(
*/
}
val contentScale = remember {
val contentScale =
remember {
if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth
}
@@ -374,13 +377,15 @@ private fun UrlImageView(
alwayShowImage: Boolean = false,
) {
BoxWithConstraints(contentAlignment = Alignment.Center) {
val showImage = remember {
val showImage =
remember {
mutableStateOf<Boolean>(
if (alwayShowImage) true else accountViewModel.settings.showImages.value,
)
}
val myModifier = remember {
val myModifier =
remember {
mainImageModifier.widthIn(max = maxWidth).heightIn(max = maxHeight)
/* Is this necessary? It makes images bleed into other pages
.run {
@@ -391,7 +396,8 @@ private fun UrlImageView(
*/
}
val contentScale = remember {
val contentScale =
remember {
if (maxHeight.isFinite) ContentScale.Fit else ContentScale.FillWidth
}
@@ -438,7 +444,8 @@ fun ImageUrlWithDownloadButton(
val regularText = remember { SpanStyle(color = background) }
val clickableTextStyle = remember { SpanStyle(color = primary) }
val annotatedTermsString = remember {
val annotatedTermsString =
remember {
buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToImage", "")
@@ -514,7 +521,8 @@ private fun AddedImageFeatures(
} else {
when (painter.value) {
null,
is AsyncImagePainter.State.Loading, -> {
is AsyncImagePainter.State.Loading,
-> {
if (content.blurhash != null) {
if (ratio != null) {
DisplayBlurHash(
@@ -577,7 +585,8 @@ private fun AddedImageFeatures(
when (painter.value) {
null,
is AsyncImagePainter.State.Loading, -> {
is AsyncImagePainter.State.Loading,
-> {
if (content.blurhash != null) {
if (ratio != null) {
DisplayBlurHash(
@@ -663,7 +672,8 @@ private fun DisplayUrlWithLoadingSymbolWait(content: ZoomableContent) {
val regularText = remember { SpanStyle(color = background) }
val clickableTextStyle = remember { SpanStyle(color = primary) }
val annotatedTermsString = remember {
val annotatedTermsString =
remember {
buildAnnotatedString {
if (content is ZoomableUrlContent) {
withStyle(clickableTextStyle) {
@@ -685,7 +695,8 @@ private fun DisplayUrlWithLoadingSymbolWait(content: ZoomableContent) {
val inlineContent = mapOf("inlineContent" to InlineLoadingIcon())
val pressIndicator = remember {
val pressIndicator =
remember {
if (content is ZoomableUrlContent) {
Modifier.clickable { runCatching { uri.openUri(content.url) } }
} else {
@@ -176,9 +176,11 @@ class ChatroomListKnownFeedFilter(val account: Account) : AdditiveFeedFilter<Not
if (roomKey != null && room != null) {
if (
(newNote.author?.pubkeyHex == me.pubkeyHex ||
(
newNote.author?.pubkeyHex == me.pubkeyHex ||
room.senderIntersects(followingKeySet) ||
me.hasSentMessagesTo(roomKey)) && !account.isAllHidden(roomKey.users)
me.hasSentMessagesTo(roomKey)
) && !account.isAllHidden(roomKey.users)
) {
val lastNote = newRelevantPrivateMessages.get(roomKey)
if (lastNote != null) {
@@ -117,9 +117,11 @@ class ChatroomListNewFeedFilter(val account: Account) : AdditiveFeedFilter<Note>
if (
roomKey != null &&
room != null &&
(newNote.author?.pubkeyHex != me.pubkeyHex &&
(
newNote.author?.pubkeyHex != me.pubkeyHex &&
room.senderIntersects(followingKeySet) &&
!me.hasSentMessagesTo(roomKey)) &&
!me.hasSentMessagesTo(roomKey)
) &&
!account.isAllHidden(roomKey.users)
) {
val lastNote = newRelevantPrivateMessages.get(roomKey)
@@ -49,12 +49,14 @@ class GeoHashFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
return collection
.asSequence()
.filter {
(it.event is TextNoteEvent ||
(
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent) && it.event?.isTaggedGeoHash(myTag) == true
it.event is AudioHeaderEvent
) && it.event?.isTaggedGeoHash(myTag) == true
}
.filter { account.isAcceptable(it) }
.toSet()
@@ -49,12 +49,14 @@ class HashtagFeedFilter(val tag: String, val account: Account) : AdditiveFeedFil
return collection
.asSequence()
.filter {
(it.event is TextNoteEvent ||
(
it.event is TextNoteEvent ||
it.event is LongTextNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is PrivateDmEvent ||
it.event is PollNoteEvent ||
it.event is AudioHeaderEvent) && it.event?.isTaggedHash(myTag) == true
it.event is AudioHeaderEvent
) && it.event?.isTaggedHash(myTag) == true
}
.filter { account.isAcceptable(it) }
.toSet()
@@ -65,14 +65,18 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
return collection
.asSequence()
.filter {
(it.event is TextNoteEvent ||
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent) &&
(isGlobal ||
it.event is LiveActivitiesChatMessageEvent
) &&
(
isGlobal ||
it.author?.pubkeyHex in followingKeySet ||
it.event?.isTaggedHashes(followingTagSet) ?: false ||
it.event?.isTaggedGeoHashes(followingGeohashSet) ?: false) &&
it.event?.isTaggedGeoHashes(followingGeohashSet) ?: false
) &&
// && account.isAcceptable(it) // This filter follows only. No need to check if
// acceptable
(isHiddenList || it.author?.let { !account.isHidden(it) } ?: true) &&
@@ -81,7 +81,8 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
.filter { it ->
val noteEvent = it.event
val isGlobalRelay = it.relays.any { gRelays.contains(it.url) }
(noteEvent is TextNoteEvent ||
(
noteEvent is TextNoteEvent ||
noteEvent is ClassifiedsEvent ||
noteEvent is RepostEvent ||
noteEvent is GenericRepostEvent ||
@@ -89,23 +90,33 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
noteEvent is PollNoteEvent ||
noteEvent is HighlightEvent ||
noteEvent is AudioTrackEvent ||
noteEvent is AudioHeaderEvent) &&
noteEvent is AudioHeaderEvent
) &&
(!ignoreAddressables || noteEvent.kind() < 10000) &&
((isGlobal && isGlobalRelay) ||
(
(isGlobal && isGlobalRelay) ||
it.author?.pubkeyHex in followingKeySet ||
noteEvent.isTaggedHashes(followingTagSet) ||
noteEvent.isTaggedGeoHashes(followingGeohashSet) ||
noteEvent.isTaggedAddressableNotes(followingCommunities)) &&
noteEvent.isTaggedAddressableNotes(followingCommunities)
) &&
// && account.isAcceptable(it) // This filter follows only. No need to check if
// acceptable
(isHiddenList || it.author?.let { !account.isHidden(it.pubkeyHex) } ?: true) &&
((it.event?.createdAt() ?: 0) < oneMinuteInTheFuture) &&
it.isNewThread() &&
((noteEvent !is RepostEvent && noteEvent !is GenericRepostEvent) || // not a repost
(it.replyTo?.lastOrNull()?.author?.pubkeyHex !in followingKeySet ||
(noteEvent.createdAt() >
(it.replyTo?.lastOrNull()?.createdAt()
?: 0) + oneHr)) // or a repost of by a non-follower's post (likely not seen yet)
(
(noteEvent !is RepostEvent && noteEvent !is GenericRepostEvent) || // not a repost
(
it.replyTo?.lastOrNull()?.author?.pubkeyHex !in followingKeySet ||
(
noteEvent.createdAt() >
(
it.replyTo?.lastOrNull()?.createdAt()
?: 0
) + oneHr
)
) // or a repost of by a non-follower's post (likely not seen yet)
)
}
.toSet()
@@ -103,8 +103,10 @@ class NotificationFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
}
return isAuthoredPostCited ||
(event.citedUsers().contains(authorHex) ||
note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true)
(
event.citedUsers().contains(authorHex) ||
note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true
)
}
if (event is ReactionEvent) {
@@ -47,10 +47,12 @@ class UserProfileConversationsFeedFilter(val user: User, val account: Account) :
return collection
.filter {
it.author == user &&
(it.event is TextNoteEvent ||
(
it.event is TextNoteEvent ||
it.event is PollNoteEvent ||
it.event is ChannelMessageEvent ||
it.event is LiveActivitiesChatMessageEvent) &&
it.event is LiveActivitiesChatMessageEvent
) &&
!it.isNewThread() &&
account.isAcceptable(it) == true
}
@@ -55,7 +55,8 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
return collection
.filter {
it.author == user &&
(it.event is TextNoteEvent ||
(
it.event is TextNoteEvent ||
it.event is ClassifiedsEvent ||
it.event is RepostEvent ||
it.event is GenericRepostEvent ||
@@ -63,7 +64,8 @@ class UserProfileNewThreadFeedFilter(val user: User, val account: Account) :
it.event is PollNoteEvent ||
it.event is HighlightEvent ||
it.event is AudioTrackEvent ||
it.event is AudioHeaderEvent) &&
it.event is AudioHeaderEvent
) &&
it.isNewThread() &&
account.isAcceptable(it) == true
}
@@ -69,9 +69,9 @@ import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import java.math.BigDecimal
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.math.BigDecimal
@Stable data class Reward(val amount: BigDecimal)
@@ -191,7 +191,8 @@ fun DisplayAccount(
Box(
modifier = Modifier.width(55.dp).padding(0.dp),
) {
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -93,7 +93,8 @@ fun AppNavigation(
sharedPreferencesViewModel: SharedPreferencesViewModel,
) {
val scope = rememberCoroutineScope()
val nav = remember {
val nav =
remember {
{ route: String ->
scope.launch {
if (getRouteWithArguments(navController) != route) {
@@ -545,7 +545,8 @@ private fun LoggedInUserPictureDrawer(
accountViewModel.account.userProfile().live().profilePictureChanges.observeAsState()
val pubkeyHex = remember { accountViewModel.userProfile().pubkeyHex }
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -728,9 +729,11 @@ class FollowListViewModel(val account: Account) : ViewModel() {
if (
newNotes.any {
it.event?.pubKey() == account.userProfile().pubkeyHex &&
(it.event is PeopleListEvent ||
(
it.event is PeopleListEvent ||
it.event is MuteListEvent ||
it.event is ContactListEvent)
it.event is ContactListEvent
)
}
) {
refresh()
@@ -746,9 +749,7 @@ class FollowListViewModel(val account: Account) : ViewModel() {
}
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <FollowListViewModel : ViewModel> create(
modelClass: Class<FollowListViewModel>
): FollowListViewModel {
override fun <FollowListViewModel : ViewModel> create(modelClass: Class<FollowListViewModel>): FollowListViewModel {
return FollowListViewModel(account) as FollowListViewModel
}
}
@@ -126,7 +126,8 @@ fun DrawerContent(
Unit
}
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -33,8 +33,8 @@ import com.vitorpamplona.quartz.events.ChatroomKeyable
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import java.net.URLEncoder
import kotlinx.collections.immutable.persistentSetOf
import java.net.URLEncoder
fun routeFor(
note: Note,
@@ -309,7 +309,8 @@ private fun CheckNewAndRenderChannelCard(
) {
val newItemColor = MaterialTheme.colorScheme.newItemBackgroundColor
val defaultBackgroundColor = MaterialTheme.colorScheme.background
val backgroundColor = remember {
val backgroundColor =
remember {
mutableStateOf<Color>(
parentBackgroundColor?.value ?: defaultBackgroundColor,
)
@@ -177,7 +177,8 @@ private fun ChannelRoomCompose(
val hasNewMessages = remember { mutableStateOf<Boolean>(false) }
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -261,7 +261,8 @@ fun NormalChatNote(
val otherColors = MaterialTheme.colorScheme.subtleBorder
val defaultBackground = MaterialTheme.colorScheme.background
val backgroundBubbleColor = remember {
val backgroundBubbleColor =
remember {
if (accountViewModel.isLoggedUser(note.author)) {
mutableStateOf(
loggedInColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground),
@@ -270,14 +271,16 @@ fun NormalChatNote(
mutableStateOf(otherColors.compositeOver(parentBackgroundColor?.value ?: defaultBackground))
}
}
val alignment: Arrangement.Horizontal = remember {
val alignment: Arrangement.Horizontal =
remember {
if (accountViewModel.isLoggedUser(note.author)) {
Arrangement.End
} else {
Arrangement.Start
}
}
val shape: Shape = remember {
val shape: Shape =
remember {
if (accountViewModel.isLoggedUser(note.author)) {
ChatBubbleShapeMe
} else {
@@ -301,7 +304,8 @@ fun NormalChatNote(
val modif2 = if (innerQuote) Modifier else ChatBubbleMaxSizeModifier
val clickableModifier = remember {
val clickableModifier =
remember {
Modifier.combinedClickable(
onClick = {
if (note.event is ChannelCreateEvent) {
@@ -366,7 +370,8 @@ private fun RenderBubble(
) {
val bubbleSize = remember { mutableIntStateOf(0) }
val bubbleModifier = remember {
val bubbleModifier =
remember {
Modifier.padding(start = 10.dp, end = 5.dp, bottom = 5.dp).onSizeChanged {
if (bubbleSize.intValue != it.width) {
bubbleSize.intValue = it.width
@@ -91,12 +91,12 @@ import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.overPictureBackground
import com.vitorpamplona.amethyst.ui.theme.profile35dpModifier
import com.vitorpamplona.quartz.events.EmptyTagList
import kotlin.time.ExperimentalTime
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.time.ExperimentalTime
@OptIn(ExperimentalFoundationApi::class, ExperimentalTime::class)
@Composable
@@ -364,7 +364,8 @@ private fun ParseAuthorCommentAndAmount(
accountViewModel: AccountViewModel,
onReady: @Composable (MutableState<ZapAmountCommentNotification>) -> Unit,
) {
val content = remember {
val content =
remember {
mutableStateOf(
ZapAmountCommentNotification(
user = zapRequest.author,
@@ -541,7 +542,8 @@ fun WatchUserMetadataAndFollowsAndRenderUserProfilePicture(
author: User,
accountViewModel: AccountViewModel,
) {
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -73,9 +73,9 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.UserMetadata
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.time.Duration.Companion.seconds
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
@Composable
fun nip05VerificationAsAState(
@@ -228,14 +228,14 @@ import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent
import com.vitorpamplona.quartz.events.toImmutableListOfLists
import java.io.File
import java.net.URL
import java.util.Locale
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
import java.net.URL
import java.util.Locale
@OptIn(ExperimentalFoundationApi::class)
@Composable
@@ -483,7 +483,8 @@ fun NormalNote(
if (isQuotedNote || isBoostedNote) {
when (baseNote.event) {
is ChannelCreateEvent,
is ChannelMetadataEvent, ->
is ChannelMetadataEvent,
->
ChannelHeader(
channelNote = baseNote,
showVideo = !makeItShort,
@@ -527,7 +528,8 @@ fun NormalNote(
} else {
when (baseNote.event) {
is ChannelCreateEvent,
is ChannelMetadataEvent, ->
is ChannelMetadataEvent,
->
ChannelHeader(
channelNote = baseNote,
showVideo = !makeItShort,
@@ -775,7 +777,8 @@ fun ShortCommunityHeader(
val noteEvent =
remember(noteState) { noteState?.note?.event as? CommunityDefinitionEvent } ?: return
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -1038,7 +1041,8 @@ fun InnerNoteWithReactions(
if (isNotRepost) {
if (makeItShort) {
if (isBoostedNote) {} else {
if (isBoostedNote) {
} else {
Spacer(modifier = DoubleVertSpacer)
}
} else {
@@ -2296,7 +2300,8 @@ private fun RenderReport(
.toSet()
.joinToString(", ")
val content = remember {
val content =
remember {
reportType + (note.event?.content()?.ifBlank { null }?.let { ": $it" } ?: "")
}
@@ -2394,7 +2399,8 @@ private fun ReplyRow(
Spacer(modifier = StdVertSpacer)
} else if (showChannelInfo != null) {
val replies = remember { note.replyTo?.toImmutableList() }
val mentions = remember {
val mentions =
remember {
(note.event as? BaseTextNoteEvent)?.mentions()?.toImmutableList() ?: persistentListOf()
}
@@ -2771,7 +2777,8 @@ fun DisplayHighlight(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
) {
val quote = remember {
val quote =
remember {
highlight.split("\n").joinToString("\n") { "> *${it.removeSuffix(" ")}*" }
}
@@ -2846,7 +2853,8 @@ private fun LoadAndDisplayPost(
@Composable
private fun LoadAndDisplayUrl(url: String) {
val validatedUrl = remember {
val validatedUrl =
remember {
try {
URL(url)
} catch (e: Exception) {
@@ -442,8 +442,7 @@ private fun BlockAlertDialog(
note: Note,
accountViewModel: AccountViewModel,
onDismiss: () -> Unit,
) =
QuickActionAlertDialog(
) = QuickActionAlertDialog(
title = stringResource(R.string.report_dialog_block_hide_user_btn),
textContent = stringResource(R.string.report_dialog_blocking_a_user),
buttonIcon = Icons.Default.Block,
@@ -463,7 +462,7 @@ private fun BlockAlertDialog(
onDismiss()
},
onDismiss = onDismiss,
)
)
@Composable
fun QuickActionAlertDialog(
@@ -87,11 +87,11 @@ import com.vitorpamplona.quartz.events.EmptyTagList
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.toImmutableListOfLists
import kotlin.math.roundToInt
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Composable
fun PollNote(
@@ -35,12 +35,12 @@ import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.VALUE_MAXIMUM
import com.vitorpamplona.quartz.events.VALUE_MINIMUM
import com.vitorpamplona.quartz.utils.TimeUtils
import java.math.BigDecimal
import java.math.RoundingMode
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.math.RoundingMode
@Immutable
data class PollOption(
@@ -134,8 +134,7 @@ class PollNoteViewModel : ViewModel() {
return account.userProfile() != note.author && !wasZappedByLoggedInAccount
}
fun isVoteAmountAtomic() =
valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
fun isPollClosed(): Boolean =
closedAt?.let { // allow 2 minute leeway for zap to propagate
@@ -128,10 +128,6 @@ import com.vitorpamplona.amethyst.ui.theme.TinyBorders
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import kotlin.math.roundToInt
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentListOf
@@ -140,6 +136,10 @@ import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import kotlin.math.roundToInt
@Composable
fun ReactionsRow(
@@ -597,10 +597,12 @@ private fun <S> AnimatedContentTransitionScope<S>.transitionSpec(): ContentTrans
@ExperimentalAnimationApi
val slideAnimation: ContentTransform =
(slideInVertically(animationSpec = tween(durationMillis = 100)) { height -> height } +
(
slideInVertically(animationSpec = tween(durationMillis = 100)) { height -> height } +
fadeIn(
animationSpec = tween(durationMillis = 100),
))
)
)
.togetherWith(
slideOutVertically(animationSpec = tween(durationMillis = 100)) { height -> -height } +
fadeOut(
@@ -149,7 +149,8 @@ fun RenderRelay(
val interactionSource = remember { MutableInteractionSource() }
val ripple = rememberRipple(bounded = false, radius = Size15dp)
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -135,9 +135,7 @@ class UpdateReactionTypeViewModel(val account: Account) : ViewModel() {
}
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <UpdateReactionTypeViewModel : ViewModel> create(
modelClass: Class<UpdateReactionTypeViewModel>
): UpdateReactionTypeViewModel {
override fun <UpdateReactionTypeViewModel : ViewModel> create(modelClass: Class<UpdateReactionTypeViewModel>): UpdateReactionTypeViewModel {
return UpdateReactionTypeViewModel(account) as UpdateReactionTypeViewModel
}
}
@@ -194,11 +194,13 @@ class UpdateZapAmountViewModel(val account: Account) : ViewModel() {
}
fun hasChanged(): Boolean {
return (selectedZapType != account?.defaultZapType ||
return (
selectedZapType != account?.defaultZapType ||
amountSet != account?.zapAmountChoices ||
walletConnectPubkey.text != (account?.zapPaymentRequest?.pubKeyHex ?: "") ||
walletConnectRelay.text != (account?.zapPaymentRequest?.relayUri ?: "") ||
walletConnectSecret.text != (account?.zapPaymentRequest?.secret ?: ""))
walletConnectSecret.text != (account?.zapPaymentRequest?.secret ?: "")
)
}
fun updateNIP47(uri: String) {
@@ -211,9 +213,7 @@ class UpdateZapAmountViewModel(val account: Account) : ViewModel() {
}
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <UpdateZapAmountViewModel : ViewModel> create(
modelClass: Class<UpdateZapAmountViewModel>
): UpdateZapAmountViewModel {
override fun <UpdateZapAmountViewModel : ViewModel> create(modelClass: Class<UpdateZapAmountViewModel>): UpdateZapAmountViewModel {
return UpdateZapAmountViewModel(account) as UpdateZapAmountViewModel
}
}
@@ -261,7 +261,8 @@ fun UpdateZapAmountDialog(
),
)
val zapOptions = remember {
val zapOptions =
remember {
zapTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList()
}
@@ -114,7 +114,8 @@ fun DisplayBlankAuthor(
) {
val backgroundColor = MaterialTheme.colorScheme.background
val nullModifier = remember {
val nullModifier =
remember {
modifier.size(size).clip(shape = CircleShape).background(backgroundColor)
}
@@ -182,7 +183,8 @@ fun ClickableUserPicture(
val ripple = rememberRipple(bounded = false, radius = size)
// BaseUser is the same reference as accountState.user
val myModifier = remember {
val myModifier =
remember {
if (onClick != null && onLongClick != null) {
Modifier.size(size)
.combinedClickable(
@@ -371,11 +373,13 @@ fun InnerUserPicture(
accountViewModel: AccountViewModel,
) {
val backgroundColor = MaterialTheme.colorScheme.background
val myImageModifier = remember {
val myImageModifier =
remember {
modifier.size(size).clip(shape = CircleShape).background(backgroundColor)
}
val automaticallyShowProfilePicture = remember {
val automaticallyShowProfilePicture =
remember {
accountViewModel.settings.showProfilePictures.value
}
@@ -73,11 +73,6 @@ import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.ReactionEvent
import com.vitorpamplona.quartz.events.RepostEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import java.math.BigDecimal
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -85,6 +80,11 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@Composable
fun UserReactionsRow(
@@ -433,9 +433,7 @@ class UserReactionsViewModel(val account: Account) : ViewModel() {
}
class Factory(val account: Account) : ViewModelProvider.Factory {
override fun <UserReactionsViewModel : ViewModel> create(
modelClass: Class<UserReactionsViewModel>
): UserReactionsViewModel {
override fun <UserReactionsViewModel : ViewModel> create(modelClass: Class<UserReactionsViewModel>): UserReactionsViewModel {
return UserReactionsViewModel(account) as UserReactionsViewModel
}
}
@@ -152,7 +152,8 @@ fun ZapCustomDialog(
),
)
val zapOptions = remember {
val zapOptions =
remember {
zapTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList()
}
var selectedZapType by
@@ -39,7 +39,6 @@ import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.signers.ExternalSignerLauncher
import com.vitorpamplona.quartz.signers.NostrSignerExternal
import com.vitorpamplona.quartz.signers.NostrSignerInternal
import java.util.regex.Pattern
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -48,6 +47,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.regex.Pattern
val EMAIL_PATTERN = Pattern.compile(".+@.+\\.[a-z]+")
@@ -81,8 +81,7 @@ class AccountStateViewModel() : ViewModel() {
proxyPort: Int,
loginWithExternalSigner: Boolean = false,
packageName: String = "",
) =
withContext(Dispatchers.IO) {
) = withContext(Dispatchers.IO) {
val parsed = Nip19.uriToRoute(key)
val pubKeyParsed = parsed?.hex?.hexToByteArray()
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)

Some files were not shown because too many files have changed in this diff Show More