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