Reverts to the non-Google kotlin style.
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
+3
-4
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -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,
|
||||
|
||||
+3
-4
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+9
-5
@@ -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()
|
||||
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+4
-2
@@ -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
|
||||
}
|
||||
|
||||
+4
-2
@@ -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)
|
||||
|
||||
|
||||
+2
-1
@@ -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
Reference in New Issue
Block a user