Merge remote-tracking branch 'origin/HEAD' into less_memory_test_branch

# Conflicts:
#	app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt
#	app/src/main/java/com/vitorpamplona/amethyst/model/User.kt
This commit is contained in:
Vitor Pamplona
2023-03-13 14:51:44 -04:00
58 changed files with 683 additions and 785 deletions
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst package com.vitorpamplona.amethyst
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -14,7 +13,7 @@ import java.util.concurrent.atomic.AtomicBoolean
object NotificationCache { object NotificationCache {
val lastReadByRoute = mutableMapOf<String, Long>() val lastReadByRoute = mutableMapOf<String, Long>()
fun markAsRead(route: String, timestampInSecs: Long, context: Context) { fun markAsRead(route: String, timestampInSecs: Long) {
val lastTime = lastReadByRoute[route] val lastTime = lastReadByRoute[route]
if (lastTime == null || timestampInSecs > lastTime) { if (lastTime == null || timestampInSecs > lastTime) {
lastReadByRoute.put(route, timestampInSecs) lastReadByRoute.put(route, timestampInSecs)
@@ -27,7 +26,7 @@ object NotificationCache {
} }
} }
fun load(route: String, context: Context): Long { fun load(route: String): Long {
var lastTime = lastReadByRoute[route] var lastTime = lastReadByRoute[route]
if (lastTime == null) { if (lastTime == null) {
lastTime = LocalPreferences.loadLastRead(route) lastTime = LocalPreferences.loadLastRead(route)
@@ -23,6 +23,7 @@ import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.RelayPool import com.vitorpamplona.amethyst.service.relays.RelayPool
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -48,6 +49,7 @@ fun getLanguagesSpokenByUser(): Set<String> {
return codedList return codedList
} }
@OptIn(DelicateCoroutinesApi::class)
class Account( class Account(
val loggedIn: Persona, val loggedIn: Persona,
var followingChannels: Set<String> = DefaultChannels, var followingChannels: Set<String> = DefaultChannels,
@@ -283,8 +283,9 @@ object LocalCache {
refreshObservers() refreshObservers()
} }
@Suppress("UNUSED_PARAMETER")
fun consume(event: RecommendRelayEvent) { fun consume(event: RecommendRelayEvent) {
// Log.d("RR", event.toJson()) // // Log.d("RR", event.toJson())
} }
fun consume(event: ContactListEvent) { fun consume(event: ContactListEvent) {
@@ -542,9 +543,11 @@ object LocalCache {
refreshObservers() refreshObservers()
} }
@Suppress("UNUSED_PARAMETER")
fun consume(event: ChannelHideMessageEvent) { fun consume(event: ChannelHideMessageEvent) {
} }
@Suppress("UNUSED_PARAMETER")
fun consume(event: ChannelMuteUserEvent) { fun consume(event: ChannelMuteUserEvent) {
} }
@@ -7,6 +7,7 @@ import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
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
@@ -18,6 +19,7 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
invalidateFilters() invalidateFilters()
} }
@OptIn(DelicateCoroutinesApi::class)
override fun start() { override fun start() {
if (this::account.isInitialized) { if (this::account.isInitialized) {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
@@ -27,6 +29,7 @@ object NostrHomeDataSource : NostrDataSource("HomeFeed") {
super.start() super.start()
} }
@OptIn(DelicateCoroutinesApi::class)
override fun stop() { override fun stop() {
super.stop() super.stop()
if (this::account.isInitialized) { if (this::account.isInitialized) {
@@ -133,9 +133,9 @@ class LightningAddressResolver {
fetchLightningAddressJson( fetchLightningAddressJson(
lnaddress, lnaddress,
onSuccess = { onSuccess = { lnAddressJson ->
val lnurlp = try { val lnurlp = try {
mapper.readTree(it) mapper.readTree(lnAddressJson)
} catch (t: Throwable) { } catch (t: Throwable) {
onError("Error Parsing JSON from Lightning Address. Check the user's lightning setup") onError("Error Parsing JSON from Lightning Address. Check the user's lightning setup")
null null
@@ -149,9 +149,9 @@ class LightningAddressResolver {
val allowsNostr = lnurlp?.get("allowsNostr")?.asBoolean() ?: false val allowsNostr = lnurlp?.get("allowsNostr")?.asBoolean() ?: false
callback?.let { callback -> callback?.let { cb ->
fetchLightningInvoice( fetchLightningInvoice(
callback, cb,
milliSats, milliSats,
message, message,
if (allowsNostr) nostrRequest else null, if (allowsNostr) nostrRequest else null,
@@ -18,17 +18,16 @@ class ChannelHideMessageEvent(
companion object { companion object {
const val kind = 43 const val kind = 43
fun create(reason: String, messagesToHide: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelHideMessageEvent { fun create(reason: String, messagesToHide: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelHideMessageEvent {
val content = reason
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
val tags = val tags =
messagesToHide?.map { messagesToHide?.map {
listOf("e", it) listOf("e", it)
} ?: emptyList() } ?: emptyList()
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, reason)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return ChannelHideMessageEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) return ChannelHideMessageEvent(id.toHexKey(), pubKey, createdAt, tags, reason, sig.toHexKey())
} }
} }
} }
@@ -28,9 +28,7 @@ abstract class IdentityClaim(
companion object { companion object {
fun create(platformIdentity: String, proof: String): IdentityClaim? { fun create(platformIdentity: String, proof: String): IdentityClaim? {
val platformIdentity = platformIdentity.split(':') val (platform, identity) = platformIdentity.split(':')
val platform = platformIdentity[0]
val identity = platformIdentity[1]
return when (platform.lowercase()) { return when (platform.lowercase()) {
GitHubIdentity.platform -> GitHubIdentity(identity, proof) GitHubIdentity.platform -> GitHubIdentity(identity, proof)
@@ -169,16 +167,16 @@ class MetadataEvent(
} }
fun create(contactMetaData: String, identities: List<IdentityClaim>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent { fun create(contactMetaData: String, identities: List<IdentityClaim>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
val content = contactMetaData
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey() val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
val tags = mutableListOf<List<String>>() val tags = mutableListOf<List<String>>()
identities?.forEach {
identities.forEach {
tags.add(listOf("i", it.platformIdentity(), it.proof)) tags.add(listOf("i", it.platformIdentity(), it.proof))
} }
val id = generateId(pubKey, createdAt, kind, tags, content) val id = generateId(pubKey, createdAt, kind, tags, contactMetaData)
val sig = Utils.sign(id, privateKey) val sig = Utils.sign(id, privateKey)
return MetadataEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey()) return MetadataEvent(id.toHexKey(), pubKey, createdAt, tags, contactMetaData, sig.toHexKey())
} }
} }
} }
@@ -19,9 +19,9 @@ class ReportEvent(
private fun defaultReportType(): ReportType { private fun defaultReportType(): ReportType {
// Works with old and new structures for report. // Works with old and new structures for report.
var reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.toUpperCase()) }.firstOrNull() var reportType = tags.filter { it.firstOrNull() == "report" }.mapNotNull { it.getOrNull(1) }.map { ReportType.valueOf(it.uppercase()) }.firstOrNull()
if (reportType == null) { if (reportType == null) {
reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.toUpperCase()) }.firstOrNull() reportType = tags.mapNotNull { it.getOrNull(2) }.map { ReportType.valueOf(it.uppercase()) }.firstOrNull()
} }
if (reportType == null) { if (reportType == null) {
reportType = ReportType.SPAM reportType = ReportType.SPAM
@@ -34,7 +34,7 @@ class ReportEvent(
.map { .map {
ReportedKey( ReportedKey(
it[1], it[1],
it.getOrNull(2)?.toUpperCase()?.let { it1 -> ReportType.valueOf(it1) } ?: defaultReportType() it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) } ?: defaultReportType()
) )
} }
@@ -43,7 +43,7 @@ class ReportEvent(
.map { .map {
ReportedKey( ReportedKey(
it[1], it[1],
it.getOrNull(2)?.toUpperCase()?.let { it1 -> ReportType.valueOf(it1) } ?: defaultReportType() it.getOrNull(2)?.uppercase()?.let { it1 -> ReportType.valueOf(it1) } ?: defaultReportType()
) )
} }
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service.relays
import com.vitorpamplona.amethyst.service.model.Event import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.EventInterface import com.vitorpamplona.amethyst.service.model.EventInterface
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
@@ -78,6 +79,7 @@ object Client : RelayPool.Listener {
RelayPool.unloadRelays() RelayPool.unloadRelays()
} }
@OptIn(DelicateCoroutinesApi::class)
override fun onEvent(event: Event, subscriptionId: String, relay: Relay) { override fun onEvent(event: Event, subscriptionId: String, relay: Relay) {
// Releases the Web thread for the new payload. // Releases the Web thread for the new payload.
// May need to add a processing queue if processing new events become too costly. // May need to add a processing queue if processing new events become too costly.
@@ -86,6 +88,7 @@ object Client : RelayPool.Listener {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
override fun onError(error: Error, subscriptionId: String, relay: Relay) { override fun onError(error: Error, subscriptionId: String, relay: Relay) {
// Releases the Web thread for the new payload. // Releases the Web thread for the new payload.
// May need to add a processing queue if processing new events become too costly. // May need to add a processing queue if processing new events become too costly.
@@ -94,6 +97,7 @@ object Client : RelayPool.Listener {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
override fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) { override fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) {
// Releases the Web thread for the new payload. // Releases the Web thread for the new payload.
// May need to add a processing queue if processing new events become too costly. // May need to add a processing queue if processing new events become too costly.
@@ -102,6 +106,7 @@ object Client : RelayPool.Listener {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) { override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) {
// Releases the Web thread for the new payload. // Releases the Web thread for the new payload.
// May need to add a processing queue if processing new events become too costly. // May need to add a processing queue if processing new events become too costly.
@@ -27,13 +27,13 @@ class TypedFilter(
fun filterToJson(filter: JsonFilter): JsonObject { fun filterToJson(filter: JsonFilter): JsonObject {
val jsonObject = JsonObject() val jsonObject = JsonObject()
filter.ids?.run { filter.ids?.run {
jsonObject.add("ids", JsonArray().apply { filter.ids?.forEach { add(it) } }) jsonObject.add("ids", JsonArray().apply { filter.ids.forEach { add(it) } })
} }
filter.authors?.run { filter.authors?.run {
jsonObject.add("authors", JsonArray().apply { filter.authors?.forEach { add(it) } }) jsonObject.add("authors", JsonArray().apply { filter.authors.forEach { add(it) } })
} }
filter.kinds?.run { filter.kinds?.run {
jsonObject.add("kinds", JsonArray().apply { filter.kinds?.forEach { add(it) } }) jsonObject.add("kinds", JsonArray().apply { filter.kinds.forEach { add(it) } })
} }
filter.tags?.run { filter.tags?.run {
entries.forEach { kv -> entries.forEach { kv ->
@@ -21,6 +21,7 @@ import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.screen.AccountScreen import com.vitorpamplona.amethyst.ui.screen.AccountScreen
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
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
@@ -67,6 +68,7 @@ class MainActivity : FragmentActivity() {
Client.lenient = true Client.lenient = true
} }
@OptIn(DelicateCoroutinesApi::class)
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@@ -3,12 +3,11 @@ package com.vitorpamplona.amethyst.ui.actions
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.media.MediaScannerConnection
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.net.toUri
import okhttp3.* import okhttp3.*
import okio.BufferedSource import okio.BufferedSource
import okio.IOException import okio.IOException
@@ -135,12 +134,7 @@ object ImageSaver {
// Call the media scanner manually, so the image // Call the media scanner manually, so the image
// appears in the gallery faster. // appears in the gallery faster.
context.sendBroadcast( MediaScannerConnection.scanFile(context, arrayOf(outputFile.toString()), null, null)
Intent(
Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
outputFile.toUri()
)
)
} }
private const val PICTURES_SUBDIRECTORY = "Amethyst" private const val PICTURES_SUBDIRECTORY = "Amethyst"
@@ -16,7 +16,6 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextDirection import androidx.compose.ui.text.style.TextDirection
@@ -31,8 +30,6 @@ import com.vitorpamplona.amethyst.model.Channel
@Composable @Composable
fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = null) { fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = null) {
val postViewModel: NewChannelViewModel = viewModel() val postViewModel: NewChannelViewModel = viewModel()
val context = LocalContext.current.applicationContext
postViewModel.load(account, channel) postViewModel.load(account, channel)
Dialog( Dialog(
@@ -58,7 +55,7 @@ fun NewChannelView(onClose: () -> Unit, account: Account, channel: Channel? = nu
PostButton( PostButton(
onPost = { onPost = {
postViewModel.create(context) postViewModel.create()
onClose() onClose()
}, },
postViewModel.channelName.value.text.isNotBlank() postViewModel.channelName.value.text.isNotBlank()
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.actions package com.vitorpamplona.amethyst.ui.actions
import android.content.Context
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -25,7 +24,7 @@ class NewChannelViewModel : ViewModel() {
} }
} }
fun create(context: Context) { fun create() {
this.account?.let { account -> this.account?.let { account ->
if (originalChannel == null) { if (originalChannel == null) {
account.sendCreateNewChannel( account.sendCreateNewChannel(
@@ -208,7 +208,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
itemsIndexed( itemsIndexed(
userSuggestions, userSuggestions,
key = { _, item -> item.pubkeyHex } key = { _, item -> item.pubkeyHex }
) { index, item -> ) { _, item ->
UserLine(item, account) { UserLine(item, account) {
postViewModel.autocompleteWithUser(item) postViewModel.autocompleteWithUser(item)
} }
@@ -40,7 +40,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -58,12 +57,10 @@ import java.lang.Math.round
@Composable @Composable
fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String = "") { fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String = "") {
val postViewModel: NewRelayListViewModel = viewModel() val postViewModel: NewRelayListViewModel = viewModel()
val ctx = LocalContext.current.applicationContext
val feedState by postViewModel.relays.collectAsState() val feedState by postViewModel.relays.collectAsState()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
postViewModel.load(account, ctx) postViewModel.load(account)
} }
Dialog( Dialog(
@@ -83,13 +80,13 @@ fun NewRelayListView(onClose: () -> Unit, account: Account, relayToAdd: String =
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
CloseButton(onCancel = { CloseButton(onCancel = {
postViewModel.clear(ctx) postViewModel.clear()
onClose() onClose()
}) })
PostButton( PostButton(
onPost = { onPost = {
postViewModel.create(ctx) postViewModel.create()
onClose() onClose()
}, },
true true
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.actions package com.vitorpamplona.amethyst.ui.actions
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.RelaySetupInfo import com.vitorpamplona.amethyst.model.RelaySetupInfo
@@ -18,20 +17,20 @@ class NewRelayListViewModel : ViewModel() {
private val _relays = MutableStateFlow<List<RelaySetupInfo>>(emptyList()) private val _relays = MutableStateFlow<List<RelaySetupInfo>>(emptyList())
val relays = _relays.asStateFlow() val relays = _relays.asStateFlow()
fun load(account: Account, ctx: Context) { fun load(account: Account) {
this.account = account this.account = account
clear(ctx) clear()
} }
fun create(ctx: Context) { fun create() {
relays.let { relays.let {
account.saveRelayList(it.value) account.saveRelayList(it.value)
} }
clear(ctx) clear()
} }
fun clear(ctx: Context) { fun clear() {
_relays.update { _relays.update {
var relayFile = account.userProfile().latestContactList?.relays() var relayFile = account.userProfile().latestContactList?.relays()
@@ -218,7 +218,7 @@ fun TagLink(word: String, tags: List<List<String>>, canPreview: Boolean, backgro
val index = try { val index = try {
matcher.find() matcher.find()
matcher.group(1).toInt() matcher.group(1)?.toInt()
} catch (e: Exception) { } catch (e: Exception) {
println("Couldn't link tag $word") println("Couldn't link tag $word")
null null
@@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
@@ -60,8 +59,6 @@ fun TranslateableRichTextViewer(
var showOriginal by remember { mutableStateOf(false) } var showOriginal by remember { mutableStateOf(false) }
var langSettingsPopupExpanded by remember { mutableStateOf(false) } var langSettingsPopupExpanded by remember { mutableStateOf(false) }
val context = LocalContext.current
val accountState by accountViewModel.accountLanguagesLiveData.observeAsState() val accountState by accountViewModel.accountLanguagesLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -154,7 +151,7 @@ fun TranslateableRichTextViewer(
onDismissRequest = { langSettingsPopupExpanded = false } onDismissRequest = { langSettingsPopupExpanded = false }
) { ) {
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.dontTranslateFrom(source, context) accountViewModel.dontTranslateFrom(source)
langSettingsPopupExpanded = false langSettingsPopupExpanded = false
}) { }) {
if (source in account.dontTranslateFrom) { if (source in account.dontTranslateFrom) {
@@ -169,7 +166,7 @@ fun TranslateableRichTextViewer(
Spacer(modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.size(10.dp))
Text(stringResource(R.string.never_translate_from) + "${Locale(source).displayName}") Text(stringResource(R.string.never_translate_from) + Locale(source).displayName)
} }
Divider() Divider()
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
@@ -223,11 +220,11 @@ fun TranslateableRichTextViewer(
Divider() Divider()
val languageList = val languageList =
ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()) ConfigurationCompat.getLocales(Resources.getSystem().configuration)
for (i in 0 until languageList.size()) { for (i in 0 until languageList.size()) {
languageList.get(i)?.let { lang -> languageList.get(i)?.let { lang ->
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.translateTo(lang, context) accountViewModel.translateTo(lang)
langSettingsPopupExpanded = false langSettingsPopupExpanded = false
}) { }) {
if (lang.language in account.translateTo) { if (lang.language in account.translateTo) {
@@ -18,14 +18,14 @@ import kotlinx.coroutines.withContext
@Composable @Composable
fun UrlPreview(url: String, urlText: String) { fun UrlPreview(url: String, urlText: String) {
val default = UrlCachedPreviewer.cache[url]?.let { // val default = UrlCachedPreviewer.cache[url]?.let {
if (it.url == url) { // if (it.url == url) {
UrlPreviewState.Loaded(it) // UrlPreviewState.Loaded(it)
} else { // } else {
UrlPreviewState.Empty // UrlPreviewState.Empty
} // }
} ?: UrlPreviewState.Loading // } ?: UrlPreviewState.Loading
var context = LocalContext.current val context = LocalContext.current
var urlPreviewState by remember { mutableStateOf<UrlPreviewState>(UrlPreviewState.Loading) } var urlPreviewState by remember { mutableStateOf<UrlPreviewState>(UrlPreviewState.Loading) }
@@ -1,9 +1,9 @@
package com.vitorpamplona.amethyst.ui.components package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculatePan import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.gestures.forEachGesture
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@@ -32,8 +32,7 @@ fun ZoomableAsyncImage(imageUrl: String) {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.pointerInput(Unit) { .pointerInput(Unit) {
forEachGesture { awaitEachGesture {
awaitPointerEventScope {
awaitFirstDown() awaitFirstDown()
do { do {
val event = awaitPointerEvent() val event = awaitPointerEvent()
@@ -44,7 +43,6 @@ fun ZoomableAsyncImage(imageUrl: String) {
} while (event.changes.any { it.pressed }) } while (event.changes.any { it.pressed })
} }
} }
}
) { ) {
AsyncImage( AsyncImage(
model = imageUrl, model = imageUrl,
@@ -38,7 +38,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -60,9 +59,7 @@ fun AccountSwitchBottomSheet(
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel accountStateViewModel: AccountStateViewModel
) { ) {
val context = LocalContext.current
val accounts = LocalPreferences.allSavedAccounts() val accounts = LocalPreferences.allSavedAccounts()
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -30,7 +30,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
@@ -141,7 +140,7 @@ private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: Ac
Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) { Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) {
Icon( Icon(
painter = painterResource(id = route.icon), painter = painterResource(id = route.icon),
null, contentDescription = null,
modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp), modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp),
tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified
) )
@@ -158,17 +157,15 @@ private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: Ac
var hasNewItems by remember { mutableStateOf<Boolean>(false) } var hasNewItems by remember { mutableStateOf<Boolean>(false) }
val context = LocalContext.current.applicationContext
LaunchedEffect(key1 = notif) { LaunchedEffect(key1 = notif) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, context) hasNewItems = route.hasNewItems(account, notif.cache)
} }
} }
LaunchedEffect(key1 = db) { LaunchedEffect(key1 = db) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, context) hasNewItems = route.hasNewItems(account, notif.cache)
} }
} }
@@ -12,7 +12,6 @@ import com.google.accompanist.pager.rememberPagerState
import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
@@ -32,7 +31,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen
fun AppNavigation( fun AppNavigation(
navController: NavHostController, navController: NavHostController,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel,
nextPage: String? = null nextPage: String? = null
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
@@ -111,7 +109,6 @@ fun AppNavigation(
ChannelScreen( ChannelScreen(
channelId = it.arguments?.getString("id"), channelId = it.arguments?.getString("id"),
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
accountStateViewModel = accountStateViewModel,
navController = navController navController = navController
) )
}) })
@@ -71,6 +71,7 @@ fun AppTopBar(navController: NavHostController, scaffoldState: ScaffoldState, ac
} }
} }
@OptIn(coil.annotation.ExperimentalCoilApi::class)
@Composable @Composable
fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
@@ -86,7 +87,6 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current val context = LocalContext.current
val ctx = LocalContext.current.applicationContext
var wantsToEditRelays by remember { var wantsToEditRelays by remember {
mutableStateOf(false) mutableStateOf(false)
@@ -99,7 +99,6 @@ fun MainTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel)
Column() { Column() {
TopAppBar( TopAppBar(
elevation = 0.dp, elevation = 0.dp,
backgroundColor = Color(0xFFFFFF),
title = { title = {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -36,7 +36,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -110,11 +109,9 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
val accountUserFollowsState by baseAccountUser.live().follows.observeAsState() val accountUserFollowsState by baseAccountUser.live().follows.observeAsState()
val accountUserFollows = accountUserFollowsState?.user ?: return val accountUserFollows = accountUserFollowsState?.user ?: return
val ctx = LocalContext.current.applicationContext
Box { Box {
val banner = accountUser.info?.banner val banner = accountUser.info?.banner
if (banner != null && banner.isNotBlank()) { if (!banner.isNullOrBlank()) {
AsyncImage( AsyncImage(
model = banner, model = banner,
contentDescription = stringResource(id = R.string.profile_image), contentDescription = stringResource(id = R.string.profile_image),
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.ui.navigation package com.vitorpamplona.amethyst.ui.navigation
import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.navigation.NamedNavArgument import androidx.navigation.NamedNavArgument
@@ -18,7 +17,7 @@ import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
sealed class Route( sealed class Route(
val route: String, val route: String,
val icon: Int, val icon: Int,
val hasNewItems: (Account, NotificationCache, Context) -> Boolean = { _, _, _ -> false }, val hasNewItems: (Account, NotificationCache) -> Boolean = { _, _ -> false },
val arguments: List<NamedNavArgument> = emptyList() val arguments: List<NamedNavArgument> = emptyList()
) { ) {
val base: String val base: String
@@ -28,7 +27,7 @@ sealed class Route(
route = "Home?scrollToTop={scrollToTop}", route = "Home?scrollToTop={scrollToTop}",
icon = R.drawable.ic_home, icon = R.drawable.ic_home,
arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }), arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }),
hasNewItems = { accountViewModel, cache, context -> homeHasNewItems(accountViewModel, cache, context) } hasNewItems = { accountViewModel, cache -> homeHasNewItems(accountViewModel, cache) }
) )
object Search : Route( object Search : Route(
@@ -40,17 +39,13 @@ sealed class Route(
object Notification : Route( object Notification : Route(
route = "Notification", route = "Notification",
icon = R.drawable.ic_notifications, icon = R.drawable.ic_notifications,
hasNewItems = { accountViewModel, cache, context -> hasNewItems = { accountViewModel, cache -> notificationHasNewItems(accountViewModel, cache) }
notificationHasNewItems(accountViewModel, cache, context)
}
) )
object Message : Route( object Message : Route(
route = "Message", route = "Message",
icon = R.drawable.ic_dm, icon = R.drawable.ic_dm,
hasNewItems = { accountViewModel, cache, context -> hasNewItems = { accountViewModel, cache -> messagesHasNewItems(accountViewModel, cache) }
messagesHasNewItems(accountViewModel, cache, context)
}
) )
object Filters : Route( object Filters : Route(
@@ -92,8 +87,8 @@ fun currentRoute(navController: NavHostController): String? {
return navBackStackEntry?.destination?.route return navBackStackEntry?.destination?.route
} }
private fun homeHasNewItems(account: Account, cache: NotificationCache, context: Context): Boolean { private fun homeHasNewItems(account: Account, cache: NotificationCache): Boolean {
val lastTime = cache.load("HomeFollows", context) val lastTime = cache.load("HomeFollows")
HomeNewThreadFeedFilter.account = account HomeNewThreadFeedFilter.account = account
@@ -103,12 +98,8 @@ private fun homeHasNewItems(account: Account, cache: NotificationCache, context:
) > lastTime ) > lastTime
} }
private fun notificationHasNewItems( private fun notificationHasNewItems(account: Account, cache: NotificationCache): Boolean {
account: Account, val lastTime = cache.load("Notification")
cache: NotificationCache,
context: Context
): Boolean {
val lastTime = cache.load("Notification", context)
NotificationFeedFilter.account = account NotificationFeedFilter.account = account
@@ -118,18 +109,14 @@ private fun notificationHasNewItems(
) > lastTime ) > lastTime
} }
private fun messagesHasNewItems( private fun messagesHasNewItems(account: Account, cache: NotificationCache): Boolean {
account: Account,
cache: NotificationCache,
context: Context
): Boolean {
ChatroomListKnownFeedFilter.account = account ChatroomListKnownFeedFilter.account = account
val note = ChatroomListKnownFeedFilter.feed().firstOrNull { val note = ChatroomListKnownFeedFilter.feed().firstOrNull {
it.createdAt() != null && it.channel() == null && it.author != account.userProfile() it.createdAt() != null && it.channel() == null && it.author != account.userProfile()
} ?: return false } ?: return false
val lastTime = cache.load("Room/${note.author?.pubkeyHex}", context) val lastTime = cache.load("Room/${note.author?.pubkeyHex}")
return (note.createdAt() ?: 0) > lastTime return (note.createdAt() ?: 0) > lastTime
} }
@@ -42,13 +42,10 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BadgeCompose(likeSetCard: BadgeCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
val noteState by likeSetCard.note.live().metadata.observeAsState() val noteState by likeSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext val context = LocalContext.current.applicationContext
val noteEvent = note?.event val noteEvent = note?.event
@@ -61,13 +58,13 @@ fun BadgeCompose(likeSetCard: BadgeCard, modifier: Modifier = Modifier, isInnerN
LaunchedEffect(key1 = likeSetCard) { LaunchedEffect(key1 = likeSetCard) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead, context) isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt(), context) NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt())
} }
} }
var backgroundColor = if (isNew) { val backgroundColor = if (isNew) {
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background) MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
} else { } else {
MaterialTheme.colors.background MaterialTheme.colors.background
@@ -22,7 +22,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -44,8 +43,6 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext
val noteEvent = note?.event val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
@@ -56,13 +53,13 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
LaunchedEffect(key1 = boostSetCard) { LaunchedEffect(key1 = boostSetCard) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
isNew = boostSetCard.createdAt > NotificationCache.load(routeForLastRead, context) isNew = boostSetCard.createdAt > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, boostSetCard.createdAt, context) NotificationCache.markAsRead(routeForLastRead, boostSetCard.createdAt)
} }
} }
var backgroundColor = if (isNew) { val backgroundColor = if (isNew) {
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background) MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
} else { } else {
MaterialTheme.colors.background MaterialTheme.colors.background
@@ -64,8 +64,6 @@ fun ChatroomCompose(
val notificationCacheState = NotificationCache.live.observeAsState() val notificationCacheState = NotificationCache.live.observeAsState()
val notificationCache = notificationCacheState.value ?: return val notificationCache = notificationCacheState.value ?: return
val context = LocalContext.current.applicationContext
if (note?.event == null) { if (note?.event == null) {
BlankNote(Modifier) BlankNote(Modifier)
} else if (note.channel() != null) { } else if (note.channel() != null) {
@@ -84,22 +82,22 @@ fun ChatroomCompose(
} else { } else {
noteEvent?.content() noteEvent?.content()
} }
channel?.let { channel -> channel?.let { chan ->
var hasNewMessages by remember { mutableStateOf<Boolean>(false) } var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notificationCache, key2 = note) { LaunchedEffect(key1 = notificationCache, key2 = note) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
note.createdAt()?.let { note.createdAt()?.let { timestamp ->
hasNewMessages = hasNewMessages =
it > notificationCache.cache.load("Channel/${channel.idHex}", context) timestamp > notificationCache.cache.load("Channel/${chan.idHex}")
} }
} }
} }
ChannelName( ChannelName(
channelIdHex = channel.idHex, channelIdHex = chan.idHex,
channelPicture = channel.profilePicture(), channelPicture = chan.profilePicture(),
channelTitle = { channelTitle = { modifier ->
Text( Text(
text = buildAnnotatedString { text = buildAnnotatedString {
withStyle( withStyle(
@@ -107,7 +105,7 @@ fun ChatroomCompose(
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
) { ) {
append(channel.info.name) append(chan.info.name)
} }
withStyle( withStyle(
@@ -120,14 +118,14 @@ fun ChatroomCompose(
} }
}, },
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = it, modifier = modifier,
style = LocalTextStyle.current.copy(textDirection = TextDirection.Content) style = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
) )
}, },
channelLastTime = note.createdAt(), channelLastTime = note.createdAt(),
channelLastContent = "${author?.toBestDisplayName()}: " + description, channelLastContent = "${author?.toBestDisplayName()}: " + description,
hasNewMessages = hasNewMessages, hasNewMessages = hasNewMessages,
onClick = { navController.navigate("Channel/${channel.idHex}") } onClick = { navController.navigate("Channel/${chan.idHex}") }
) )
} }
} else { } else {
@@ -153,8 +151,7 @@ fun ChatroomCompose(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
noteEvent?.let { noteEvent?.let {
hasNewMessages = it.createdAt() > notificationCache.cache.load( hasNewMessages = it.createdAt() > notificationCache.cache.load(
"Room/${userToComposeOn.pubkeyHex}", "Room/${userToComposeOn.pubkeyHex}"
context
) )
} }
} }
@@ -132,11 +132,11 @@ fun ChatroomMessageCompose(
LaunchedEffect(key1 = routeForLastRead) { LaunchedEffect(key1 = routeForLastRead) {
routeForLastRead?.let { routeForLastRead?.let {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val lastTime = NotificationCache.load(it, context) val lastTime = NotificationCache.load(it)
val createdAt = note.createdAt() val createdAt = note.createdAt()
if (createdAt != null) { if (createdAt != null) {
NotificationCache.markAsRead(it, createdAt, context) NotificationCache.markAsRead(it, createdAt)
isNew = createdAt > lastTime isNew = createdAt > lastTime
} }
} }
@@ -206,17 +206,17 @@ fun ChatroomMessageCompose(
.height(25.dp) .height(25.dp)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.clickable(onClick = { .clickable(onClick = {
author?.let { author.let {
navController.navigate("User/${it.pubkeyHex}") navController.navigate("User/${it.pubkeyHex}")
} }
}) })
) )
Text( Text(
" ${author?.toBestDisplayName()}", " ${author.toBestDisplayName()}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
author?.let { author.let {
navController.navigate("User/${it.pubkeyHex}") navController.navigate("User/${it.pubkeyHex}")
} }
}) })
@@ -225,9 +225,9 @@ fun ChatroomMessageCompose(
} }
val replyTo = note.replyTo val replyTo = note.replyTo
if (!innerQuote && replyTo != null && replyTo.isNotEmpty()) { if (!innerQuote && !replyTo.isNullOrEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
replyTo.toSet().mapIndexed { index, note -> replyTo.toSet().mapIndexed { _, note ->
if (note.event != null) { if (note.event != null) {
ChatroomMessageCompose( ChatroomMessageCompose(
note, note,
@@ -360,7 +360,6 @@ private fun RelayBadges(baseNote: Note) {
val relaysToDisplay = if (expanded) noteRelays else noteRelays.take(3) val relaysToDisplay = if (expanded) noteRelays else noteRelays.take(3)
val uri = LocalUriHandler.current val uri = LocalUriHandler.current
val ctx = LocalContext.current.applicationContext
FlowRow(Modifier.padding(start = 10.dp)) { FlowRow(Modifier.padding(start = 10.dp)) {
relaysToDisplay.forEach { relaysToDisplay.forEach {
@@ -22,7 +22,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -37,15 +36,13 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun LikeSetCompose(likeSetCard: LikeSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
val noteState by likeSetCard.note.live().metadata.observeAsState() val noteState by likeSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext
val noteEvent = note?.event val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
@@ -56,13 +53,13 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isIn
LaunchedEffect(key1 = likeSetCard) { LaunchedEffect(key1 = likeSetCard) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
isNew = likeSetCard.createdAt > NotificationCache.load(routeForLastRead, context) isNew = likeSetCard.createdAt > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt, context) NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt)
} }
} }
var backgroundColor = if (isNew) { val backgroundColor = if (isNew) {
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background) MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
} else { } else {
MaterialTheme.colors.background MaterialTheme.colors.background
@@ -21,7 +21,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -39,11 +38,6 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
val noteState by messageSetCard.note.live().metadata.observeAsState() val noteState by messageSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext
val noteEvent = note?.event val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
@@ -55,13 +49,13 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
LaunchedEffect(key1 = messageSetCard) { LaunchedEffect(key1 = messageSetCard) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
isNew = isNew =
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead, context) messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt(), context) NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt())
} }
} }
var backgroundColor = if (isNew) { val backgroundColor = if (isNew) {
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background) MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
} else { } else {
MaterialTheme.colors.background MaterialTheme.colors.background
@@ -25,7 +25,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -41,15 +40,13 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
val noteState by multiSetCard.note.live().metadata.observeAsState() val noteState by multiSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext
val noteEvent = note?.event val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
@@ -60,13 +57,13 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, r
LaunchedEffect(key1 = multiSetCard) { LaunchedEffect(key1 = multiSetCard) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead, context) isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt, context) NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt)
} }
} }
var backgroundColor = if (isNew) { val backgroundColor = if (isNew) {
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background) MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
} else { } else {
MaterialTheme.colors.background MaterialTheme.colors.background
@@ -124,18 +124,18 @@ fun NoteCompose(
LaunchedEffect(key1 = routeForLastRead) { LaunchedEffect(key1 = routeForLastRead) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
routeForLastRead?.let { routeForLastRead?.let {
val lastTime = NotificationCache.load(it, context) val lastTime = NotificationCache.load(it)
val createdAt = note.createdAt() val createdAt = note.createdAt()
if (createdAt != null) { if (createdAt != null) {
NotificationCache.markAsRead(it, createdAt, context) NotificationCache.markAsRead(it, createdAt)
isNew = createdAt > lastTime isNew = createdAt > lastTime
} }
} }
} }
} }
var backgroundColor = if (isNew) { val backgroundColor = if (isNew) {
val newColor = MaterialTheme.colors.primary.copy(0.12f) val newColor = MaterialTheme.colors.primary.copy(0.12f)
if (parentBackgroundColor != null) { if (parentBackgroundColor != null) {
newColor.compositeOver(parentBackgroundColor) newColor.compositeOver(parentBackgroundColor)
@@ -350,7 +350,7 @@ fun NoteCompose(
// Reposts have trash in their contents. // Reposts have trash in their contents.
if (noteEvent is ReactionEvent) { if (noteEvent is ReactionEvent) {
val refactorReactionText = val refactorReactionText =
if (noteEvent.content == "+") "" else noteEvent.content ?: " " if (noteEvent.content == "+") "" else noteEvent.content
Text( Text(
text = refactorReactionText text = refactorReactionText
@@ -365,7 +365,6 @@ fun NoteCompose(
ReportEvent.ReportType.SPAM -> stringResource(R.string.spam) ReportEvent.ReportType.SPAM -> stringResource(R.string.spam)
ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation) ReportEvent.ReportType.IMPERSONATION -> stringResource(R.string.impersonation)
ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior) ReportEvent.ReportType.ILLEGAL -> stringResource(R.string.illegal_behavior)
else -> stringResource(R.string.unknown)
} }
}.toSet().joinToString(", ") }.toSet().joinToString(", ")
@@ -600,7 +599,6 @@ private fun RelayBadges(baseNote: Note) {
val relaysToDisplay = if (expanded) noteRelays else noteRelays.take(3) val relaysToDisplay = if (expanded) noteRelays else noteRelays.take(3)
val uri = LocalUriHandler.current val uri = LocalUriHandler.current
val ctx = LocalContext.current.applicationContext
FlowRow(Modifier.padding(top = 10.dp, start = 5.dp, end = 4.dp)) { FlowRow(Modifier.padding(top = 10.dp, start = 5.dp, end = 4.dp)) {
relaysToDisplay.forEach { relaysToDisplay.forEach {
@@ -666,7 +664,7 @@ fun NoteAuthorPicture(
baseNote: Note, baseNote: Note,
baseUserAccount: User, baseUserAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: ((User) -> Unit)? = null onClick: ((User) -> Unit)? = null
) { ) {
val noteState by baseNote.live().metadata.observeAsState() val noteState by baseNote.live().metadata.observeAsState()
@@ -674,8 +672,6 @@ fun NoteAuthorPicture(
val author = note.author val author = note.author
val ctx = LocalContext.current.applicationContext
Box( Box(
Modifier Modifier
.width(size) .width(size)
@@ -685,13 +681,13 @@ fun NoteAuthorPicture(
RobohashAsyncImage( RobohashAsyncImage(
robot = "authornotfound", robot = "authornotfound",
contentDescription = stringResource(R.string.unknown_author), contentDescription = stringResource(R.string.unknown_author),
modifier = pictureModifier modifier = modifier
.fillMaxSize(1f) .fillMaxSize(1f)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
) )
} else { } else {
UserPicture(author, baseUserAccount, size, pictureModifier, onClick) UserPicture(author, baseUserAccount, size, modifier, onClick)
} }
} }
} }
@@ -715,15 +711,13 @@ fun UserPicture(
baseUser: User, baseUser: User,
baseUserAccount: User, baseUserAccount: User,
size: Dp, size: Dp,
pictureModifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: ((User) -> Unit)? = null, onClick: ((User) -> Unit)? = null,
onLongClick: ((User) -> Unit)? = null onLongClick: ((User) -> Unit)? = null
) { ) {
val userState by baseUser.live().metadata.observeAsState() val userState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return val user = userState?.user ?: return
val ctx = LocalContext.current.applicationContext
Box( Box(
Modifier Modifier
.width(size) .width(size)
@@ -733,7 +727,7 @@ fun UserPicture(
robot = user.pubkeyHex, robot = user.pubkeyHex,
model = ResizeImage(user.profilePicture(), size), model = ResizeImage(user.profilePicture(), size),
contentDescription = stringResource(id = R.string.profile_image), contentDescription = stringResource(id = R.string.profile_image),
modifier = pictureModifier modifier = modifier
.fillMaxSize(1f) .fillMaxSize(1f)
.clip(shape = CircleShape) .clip(shape = CircleShape)
.background(MaterialTheme.colors.background) .background(MaterialTheme.colors.background)
@@ -803,7 +797,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: "")); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: "")); onDismiss() }) {
Text(stringResource(R.string.copy_text)) Text(stringResource(R.string.copy_text))
} }
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString("@${note.author?.pubkeyNpub()}" ?: "")); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString("@${note.author?.pubkeyNpub()}")); onDismiss() }) {
Text(stringResource(R.string.copy_user_pubkey)) Text(stringResource(R.string.copy_user_pubkey))
} }
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); onDismiss() }) { DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(note.idNote())); onDismiss() }) {
@@ -840,10 +834,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
Divider() Divider()
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
note.author?.let { note.author?.let {
accountViewModel.hide( accountViewModel.hide(it)
it,
appContext
)
}; onDismiss() }; onDismiss()
}) { }) {
Text(stringResource(R.string.block_hide_user)) Text(stringResource(R.string.block_hide_user))
@@ -851,35 +842,35 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
Divider() Divider()
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.SPAM) accountViewModel.report(note, ReportEvent.ReportType.SPAM)
note.author?.let { accountViewModel.hide(it, appContext) } note.author?.let { accountViewModel.hide(it) }
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_spam_scam)) Text(stringResource(R.string.report_spam_scam))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.PROFANITY) accountViewModel.report(note, ReportEvent.ReportType.PROFANITY)
note.author?.let { accountViewModel.hide(it, appContext) } note.author?.let { accountViewModel.hide(it) }
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_hateful_speech)) Text(stringResource(R.string.report_hateful_speech))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION) accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION)
note.author?.let { accountViewModel.hide(it, appContext) } note.author?.let { accountViewModel.hide(it) }
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_impersonation)) Text(stringResource(R.string.report_impersonation))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.NUDITY) accountViewModel.report(note, ReportEvent.ReportType.NUDITY)
note.author?.let { accountViewModel.hide(it, appContext) } note.author?.let { accountViewModel.hide(it) }
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_nudity_porn)) Text(stringResource(R.string.report_nudity_porn))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL) accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL)
note.author?.let { accountViewModel.hide(it, appContext) } note.author?.let { accountViewModel.hide(it) }
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_illegal_behaviour)) Text(stringResource(R.string.report_illegal_behaviour))
@@ -124,7 +124,7 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
} }
VerticalDivider(primaryLight) VerticalDivider(primaryLight)
NoteQuickActionItem(Icons.Default.AlternateEmail, stringResource(R.string.quick_action_copy_user_id)) { NoteQuickActionItem(Icons.Default.AlternateEmail, stringResource(R.string.quick_action_copy_user_id)) {
clipboardManager.setText(AnnotatedString("@${note.author?.pubkeyNpub()}" ?: "")) clipboardManager.setText(AnnotatedString("@${note.author?.pubkeyNpub()}"))
showToast(R.string.copied_user_id_to_clipboard) showToast(R.string.copied_user_id_to_clipboard)
onDismiss() onDismiss()
} }
@@ -39,12 +39,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -280,7 +278,7 @@ fun LikeReaction(
} }
} }
) { ) {
if (reactedNote?.isReactedBy(accountViewModel.userProfile()) == true) { if (reactedNote.isReactedBy(accountViewModel.userProfile())) {
Icon( Icon(
painter = painterResource(R.drawable.ic_liked), painter = painterResource(R.drawable.ic_liked),
null, null,
@@ -298,7 +296,7 @@ fun LikeReaction(
} }
Text( Text(
" ${showCount(reactedNote?.reactions?.size)}", " ${showCount(reactedNote.reactions.size)}",
fontSize = 14.sp, fontSize = 14.sp,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f), color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
modifier = textModifier modifier = textModifier
@@ -590,15 +588,13 @@ class UpdateZapAmountViewModel : ViewModel() {
} }
} }
@OptIn(ExperimentalComposeUiApi::class, ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) { fun UpdateZapAmountDialog(onClose: () -> Unit, account: Account) {
val postViewModel: UpdateZapAmountViewModel = viewModel() val postViewModel: UpdateZapAmountViewModel = viewModel()
val ctx = LocalContext.current.applicationContext
// initialize focus reference to be able to request focus programmatically // initialize focus reference to be able to request focus programmatically
val keyboardController = LocalSoftwareKeyboardController.current // val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(account) { LaunchedEffect(account) {
postViewModel.load(account) postViewModel.load(account)
@@ -22,7 +22,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.model.RelayInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -34,7 +33,6 @@ import java.time.format.DateTimeFormatter
fun RelayCompose( fun RelayCompose(
relay: RelayInfo, relay: RelayInfo,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
navController: NavController,
onAddRelay: () -> Unit, onAddRelay: () -> Unit,
onRemoveRelay: () -> Unit onRemoveRelay: () -> Unit
) { ) {
@@ -48,8 +46,6 @@ fun RelayCompose(
modifier = Modifier modifier = Modifier
.padding(start = 12.dp, end = 12.dp, top = 10.dp) .padding(start = 12.dp, end = 12.dp, top = 10.dp)
) { ) {
// UserPicture(user, navController, account.userProfile(), 55.dp)
Column( Column(
modifier = Modifier modifier = Modifier
.padding(start = 10.dp) .padding(start = 10.dp)
@@ -13,7 +13,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -33,7 +32,6 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
val userState by account.userProfile().live().follows.observeAsState() val userState by account.userProfile().live().follows.observeAsState()
val userFollows = userState?.user ?: return val userFollows = userState?.user ?: return
val ctx = LocalContext.current.applicationContext
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
Column( Column(
@@ -58,8 +56,8 @@ fun UserCompose(baseUser: User, accountViewModel: AccountViewModel, navControlle
UsernameDisplay(baseUser) UsernameDisplay(baseUser)
} }
val userState by baseUser.live().metadata.observeAsState() val baseUserState by baseUser.live().metadata.observeAsState()
val user = userState?.user ?: return val user = baseUserState?.user ?: return
Text( Text(
user.info?.about ?: "", user.info?.about ?: "",
@@ -18,7 +18,6 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -54,7 +53,6 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
val baseAuthor = noteZapRequest.author val baseAuthor = noteZapRequest.author
val ctx = LocalContext.current.applicationContext
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
if (baseAuthor == null) { if (baseAuthor == null) {
@@ -83,8 +81,8 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
UsernameDisplay(baseAuthor) UsernameDisplay(baseAuthor)
} }
val userState by baseAuthor.live().metadata.observeAsState() val baseAuthorState by baseAuthor.live().metadata.observeAsState()
val user = userState?.user ?: return val user = baseAuthorState?.user ?: return
Text( Text(
user.info?.about ?: "", user.info?.about ?: "",
@@ -23,7 +23,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.compositeOver import androidx.compose.ui.graphics.compositeOver
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -39,15 +38,13 @@ import kotlinx.coroutines.withContext
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) { fun ZapSetCompose(zapSetCard: ZapSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
val noteState by zapSetCard.note.live().metadata.observeAsState() val noteState by zapSetCard.note.live().metadata.observeAsState()
val note = noteState?.note val note = noteState?.note
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
val context = LocalContext.current.applicationContext
val noteEvent = note?.event val noteEvent = note?.event
var popupExpanded by remember { mutableStateOf(false) } var popupExpanded by remember { mutableStateOf(false) }
@@ -58,9 +55,9 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInner
LaunchedEffect(key1 = zapSetCard) { LaunchedEffect(key1 = zapSetCard) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead, context) isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
NotificationCache.markAsRead(routeForLastRead, zapSetCard.createdAt, context) NotificationCache.markAsRead(routeForLastRead, zapSetCard.createdAt)
} }
} }
@@ -26,7 +26,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -44,8 +43,6 @@ import com.vitorpamplona.amethyst.ui.qrcode.QrCodeScanner
fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) { fun ShowQRDialog(user: User, onScan: (String) -> Unit, onClose: () -> Unit) {
var presenting by remember { mutableStateOf(true) } var presenting by remember { mutableStateOf(true) }
val ctx = LocalContext.current.applicationContext
Dialog( Dialog(
onDismissRequest = onClose, onDismissRequest = onClose,
properties = DialogProperties(usePlatformDefaultWidth = false) properties = DialogProperties(usePlatformDefaultWidth = false)
@@ -6,6 +6,7 @@ import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.Account
import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Hex
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -67,6 +68,7 @@ class AccountStateViewModel() : ViewModel() {
login(account) login(account)
} }
@OptIn(DelicateCoroutinesApi::class)
fun login(account: Account) { fun login(account: Account) {
LocalPreferences.updatePrefsForLogin(account) LocalPreferences.updatePrefsForLogin(account)
@@ -86,16 +88,16 @@ class AccountStateViewModel() : ViewModel() {
} }
} }
@OptIn(DelicateCoroutinesApi::class)
private val saveListener: (com.vitorpamplona.amethyst.model.AccountState) -> Unit = { private val saveListener: (com.vitorpamplona.amethyst.model.AccountState) -> Unit = {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
LocalPreferences.saveToEncryptedStorage(it.account) LocalPreferences.saveToEncryptedStorage(it.account)
} }
} }
@OptIn(DelicateCoroutinesApi::class)
private fun prepareLogoutOrSwitch() { private fun prepareLogoutOrSwitch() {
val state = accountContent.value when (val state = accountContent.value) {
when (state) {
is AccountState.LoggedIn -> { is AccountState.LoggedIn -> {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
state.account.saveable.removeObserver(saveListener) state.account.saveable.removeObserver(saveListener)
@@ -90,7 +90,7 @@ private fun FeedLoaded(
), ),
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { index, item -> itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
when (item) { when (item) {
is NoteCard -> NoteCompose( is NoteCard -> NoteCompose(
item.note, item.note,
@@ -22,7 +22,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController import androidx.navigation.NavController
@@ -94,7 +93,6 @@ private fun FeedLoaded(
val account = accountState?.account ?: return val account = accountState?.account ?: return
val notificationCacheState = NotificationCache.live.observeAsState() val notificationCacheState = NotificationCache.live.observeAsState()
val notificationCache = notificationCacheState.value ?: return val notificationCache = notificationCacheState.value ?: return
val context = LocalContext.current.applicationContext
LaunchedEffect(key1 = markAsRead.value) { LaunchedEffect(key1 = markAsRead.value) {
if (markAsRead.value) { if (markAsRead.value) {
@@ -118,7 +116,7 @@ private fun FeedLoaded(
"Room/${userToComposeOn.pubkeyHex}" "Room/${userToComposeOn.pubkeyHex}"
} }
notificationCache.cache.markAsRead(route, it.createdAt(), context) notificationCache.cache.markAsRead(route, it.createdAt())
} }
} }
markAsRead.value = false markAsRead.value = false
@@ -78,7 +78,7 @@ private fun LnZapFeedLoaded(
), ),
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.second.idHex }) { index, item -> itemsIndexed(state.feed.value, key = { _, item -> item.second.idHex }) { _, item ->
ZapNoteCompose(item, accountViewModel = accountViewModel, navController = navController) ZapNoteCompose(item, accountViewModel = accountViewModel, navController = navController)
} }
} }
@@ -22,7 +22,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavController
import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.model.RelayInfo
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.model.UserState
@@ -107,7 +106,7 @@ class RelayFeedViewModel : ViewModel() {
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewModel, navController: NavController) { fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewModel) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -136,11 +135,10 @@ fun RelayFeedView(viewModel: RelayFeedViewModel, accountViewModel: AccountViewMo
), ),
state = listState state = listState
) { ) {
itemsIndexed(feedState, key = { _, item -> item.url }) { index, item -> itemsIndexed(feedState, key = { _, item -> item.url }) { _, item ->
RelayCompose( RelayCompose(
item, item,
accountViewModel = accountViewModel, accountViewModel = accountViewModel,
navController = navController,
onAddRelay = { wantsToAddRelay = item.url }, onAddRelay = { wantsToAddRelay = item.url },
onRemoveRelay = { wantsToAddRelay = item.url } onRemoveRelay = { wantsToAddRelay = item.url }
) )
@@ -78,7 +78,7 @@ private fun FeedLoaded(
), ),
state = listState state = listState
) { ) {
itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { index, item -> itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController) UserCompose(item, accountViewModel = accountViewModel, navController = navController)
} }
} }
@@ -167,6 +167,7 @@ private fun authenticatedCopyNSec(
return return
} }
@Suppress("DEPRECATION")
fun keyguardPrompt() { fun keyguardPrompt() {
val intent = keyguardManager.createConfirmDeviceCredentialIntent( val intent = keyguardManager.createConfirmDeviceCredentialIntent(
context.getString(R.string.app_name_release), context.getString(R.string.app_name_release),
@@ -97,19 +97,19 @@ class AccountViewModel(private val account: Account) : ViewModel() {
return account.decryptContent(note) return account.decryptContent(note)
} }
fun hide(user: User, ctx: Context) { fun hide(user: User) {
account.hideUser(user.pubkeyHex) account.hideUser(user.pubkeyHex)
} }
fun show(user: User, ctx: Context) { fun show(user: User) {
account.showUser(user.pubkeyHex) account.showUser(user.pubkeyHex)
} }
fun translateTo(lang: Locale, ctx: Context) { fun translateTo(lang: Locale) {
account.updateTranslateTo(lang.language) account.updateTranslateTo(lang.language)
} }
fun dontTranslateFrom(lang: String, ctx: Context) { fun dontTranslateFrom(lang: String) {
account.addDontTranslateFrom(lang) account.addDontTranslateFrom(lang)
} }
@@ -71,7 +71,6 @@ import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter
import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.navigation.Route
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView import com.vitorpamplona.amethyst.ui.screen.ChatroomFeedView
import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
@@ -79,7 +78,6 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
fun ChannelScreen( fun ChannelScreen(
channelId: String?, channelId: String?,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
accountStateViewModel: AccountStateViewModel,
navController: NavController navController: NavController
) { ) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
@@ -103,7 +101,7 @@ fun ChannelScreen(
} }
DisposableEffect(channelId) { DisposableEffect(channelId) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Channel Start") println("Channel Start")
NostrChannelDataSource.start() NostrChannelDataSource.start()
@@ -145,7 +145,7 @@ fun TabKnown(
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
NostrChatroomListDataSource.resetFilters() NostrChatroomListDataSource.resetFilters()
feedViewModel.refresh() feedViewModel.refresh()
@@ -186,7 +186,7 @@ fun TabNew(
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
NostrChatroomListDataSource.resetFilters() NostrChatroomListDataSource.resetFilters()
feedViewModel.refresh() feedViewModel.refresh()
@@ -36,7 +36,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
@@ -81,7 +80,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
} }
DisposableEffect(userId) { DisposableEffect(userId) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Private Message Start") println("Private Message Start")
NostrChatroomDataSource.start() NostrChatroomDataSource.start()
@@ -101,11 +100,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
Column(Modifier.fillMaxHeight()) { Column(Modifier.fillMaxHeight()) {
NostrChatroomDataSource.withUser?.let { NostrChatroomDataSource.withUser?.let {
ChatroomHeader( ChatroomHeader(it, navController = navController)
it,
accountViewModel = accountViewModel,
navController = navController
)
} }
Column( Column(
@@ -198,9 +193,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
} }
@Composable @Composable
fun ChatroomHeader(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) { fun ChatroomHeader(baseUser: User, navController: NavController) {
val ctx = LocalContext.current.applicationContext
Column( Column(
modifier = Modifier.clickable( modifier = Modifier.clickable(
onClick = { navController.navigate("User/${baseUser.pubkeyHex}") } onClick = { navController.navigate("User/${baseUser.pubkeyHex}") }
@@ -69,7 +69,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
scaffoldState = scaffoldState scaffoldState = scaffoldState
) { ) {
Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) { Column(modifier = Modifier.padding(bottom = it.calculateBottomPadding())) {
AppNavigation(navController, accountViewModel, accountStateViewModel, startingPage) AppNavigation(navController, accountViewModel, startingPage)
} }
} }
} }
@@ -34,7 +34,7 @@ fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavCon
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
feedViewModel.refresh() feedViewModel.refresh()
} }
@@ -27,7 +27,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
@@ -111,7 +110,7 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Profile Start") println("Profile Start")
NostrUserProfileDataSource.loadUserProfile(userId) NostrUserProfileDataSource.loadUserProfile(userId)
@@ -259,13 +258,13 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
} }
) { ) {
when (pagerState.currentPage) { when (pagerState.currentPage) {
0 -> TabNotesNewThreads(baseUser, accountViewModel, navController) 0 -> TabNotesNewThreads(accountViewModel, navController)
1 -> TabNotesConversations(baseUser, accountViewModel, navController) 1 -> TabNotesConversations(accountViewModel, navController)
2 -> TabFollows(baseUser, accountViewModel, navController) 2 -> TabFollows(baseUser, accountViewModel, navController)
3 -> TabFollowers(baseUser, accountViewModel, navController) 3 -> TabFollowers(baseUser, accountViewModel, navController)
4 -> TabReceivedZaps(baseUser, accountViewModel, navController) 4 -> TabReceivedZaps(baseUser, accountViewModel, navController)
5 -> TabReports(baseUser, accountViewModel, navController) 5 -> TabReports(baseUser, accountViewModel, navController)
6 -> TabRelays(baseUser, accountViewModel, navController) 6 -> TabRelays(baseUser, accountViewModel)
} }
} }
} }
@@ -335,7 +334,7 @@ private fun ProfileHeader(
baseUser = baseUser, baseUser = baseUser,
baseUserAccount = account.userProfile(), baseUserAccount = account.userProfile(),
size = 100.dp, size = 100.dp,
pictureModifier = Modifier.border( modifier = Modifier.border(
3.dp, 3.dp,
MaterialTheme.colors.background, MaterialTheme.colors.background,
CircleShape CircleShape
@@ -576,8 +575,6 @@ fun BadgeThumb(
val event = (note.event as? BadgeDefinitionEvent) val event = (note.event as? BadgeDefinitionEvent)
val image = event?.thumb() ?: event?.image() val image = event?.thumb() ?: event?.image()
val ctx = LocalContext.current.applicationContext
Box( Box(
Modifier Modifier
.width(size) .width(size)
@@ -656,7 +653,7 @@ private fun DrawBanner(baseUser: User) {
} }
@Composable @Composable
fun TabNotesNewThreads(user: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabNotesNewThreads(accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
if (accountState != null) { if (accountState != null) {
val feedViewModel: NostrUserProfileNewThreadsFeedViewModel = viewModel() val feedViewModel: NostrUserProfileNewThreadsFeedViewModel = viewModel()
@@ -676,7 +673,7 @@ fun TabNotesNewThreads(user: User, accountViewModel: AccountViewModel, navContro
} }
@Composable @Composable
fun TabNotesConversations(user: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabNotesConversations(accountViewModel: AccountViewModel, navController: NavController) {
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
if (accountState != null) { if (accountState != null) {
val feedViewModel: NostrUserProfileConversationsFeedViewModel = viewModel() val feedViewModel: NostrUserProfileConversationsFeedViewModel = viewModel()
@@ -772,13 +769,13 @@ fun TabReports(baseUser: User, accountViewModel: AccountViewModel, navController
} }
@Composable @Composable
fun TabRelays(user: User, accountViewModel: AccountViewModel, navController: NavController) { fun TabRelays(user: User, accountViewModel: AccountViewModel) {
val feedViewModel: RelayFeedViewModel = viewModel() val feedViewModel: RelayFeedViewModel = viewModel()
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(user) { DisposableEffect(user) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Profile Relay Start") println("Profile Relay Start")
feedViewModel.subscribeTo(user) feedViewModel.subscribeTo(user)
@@ -801,7 +798,7 @@ fun TabRelays(user: User, accountViewModel: AccountViewModel, navController: Nav
Column( Column(
modifier = Modifier.padding(vertical = 0.dp) modifier = Modifier.padding(vertical = 0.dp)
) { ) {
RelayFeedView(feedViewModel, accountViewModel, navController) RelayFeedView(feedViewModel, accountViewModel)
} }
} }
} }
@@ -943,8 +940,6 @@ fun ShowUserButton(onClick: () -> Unit) {
@Composable @Composable
fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) { fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
val clipboardManager = LocalClipboardManager.current val clipboardManager = LocalClipboardManager.current
val context = LocalContext.current.applicationContext
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
@@ -960,52 +955,51 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
Divider() Divider()
if (account.isHidden(user)) { if (account.isHidden(user)) {
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
user.let { accountViewModel.show(user)
accountViewModel.show( onDismiss()
it,
context
)
}; onDismiss()
}) { }) {
Text(stringResource(R.string.unblock_user)) Text(stringResource(R.string.unblock_user))
} }
} else { } else {
DropdownMenuItem(onClick = { user.let { accountViewModel.hide(it, context) }; onDismiss() }) { DropdownMenuItem(onClick = {
accountViewModel.hide(user)
onDismiss()
}) {
Text(stringResource(id = R.string.block_hide_user)) Text(stringResource(id = R.string.block_hide_user))
} }
} }
Divider() Divider()
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.SPAM) accountViewModel.report(user, ReportEvent.ReportType.SPAM)
user.let { accountViewModel.hide(it, context) } accountViewModel.hide(user)
onDismiss() onDismiss()
}) { }) {
Text(stringResource(id = R.string.report_spam_scam)) Text(stringResource(id = R.string.report_spam_scam))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.PROFANITY) accountViewModel.report(user, ReportEvent.ReportType.PROFANITY)
user.let { accountViewModel.hide(it, context) } accountViewModel.hide(user)
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_hateful_speech)) Text(stringResource(R.string.report_hateful_speech))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION) accountViewModel.report(user, ReportEvent.ReportType.IMPERSONATION)
user.let { accountViewModel.hide(it, context) } accountViewModel.hide(user)
onDismiss() onDismiss()
}) { }) {
Text(stringResource(id = R.string.report_impersonation)) Text(stringResource(id = R.string.report_impersonation))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.NUDITY) accountViewModel.report(user, ReportEvent.ReportType.NUDITY)
user.let { accountViewModel.hide(it, context) } accountViewModel.hide(user)
onDismiss() onDismiss()
}) { }) {
Text(stringResource(R.string.report_nudity_porn)) Text(stringResource(R.string.report_nudity_porn))
} }
DropdownMenuItem(onClick = { DropdownMenuItem(onClick = {
accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL) accountViewModel.report(user, ReportEvent.ReportType.ILLEGAL)
user.let { accountViewModel.hide(it, context) } accountViewModel.hide(user)
onDismiss() onDismiss()
}) { }) {
Text(stringResource(id = R.string.report_illegal_behaviour)) Text(stringResource(id = R.string.report_illegal_behaviour))
@@ -36,7 +36,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -88,7 +87,7 @@ fun SearchScreen(
} }
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Global Start") println("Global Start")
NostrGlobalDataSource.start() NostrGlobalDataSource.start()
@@ -127,8 +126,6 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
val onlineSearch = NostrSearchEventOrUserDataSource val onlineSearch = NostrSearchEventOrUserDataSource
val ctx = LocalContext.current.applicationContext
val isTrailingIconVisible by remember { val isTrailingIconVisible by remember {
derivedStateOf { derivedStateOf {
searchValue.isNotBlank() searchValue.isNotBlank()
@@ -237,11 +234,11 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
bottom = 10.dp bottom = 10.dp
) )
) { ) {
itemsIndexed(searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { index, item -> itemsIndexed(searchResults.value, key = { _, item -> "u" + item.pubkeyHex }) { _, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController) UserCompose(item, accountViewModel = accountViewModel, navController = navController)
} }
itemsIndexed(searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { index, item -> itemsIndexed(searchResultsChannels.value, key = { _, item -> "c" + item.idHex }) { _, item ->
ChannelName( ChannelName(
channelIdHex = item.idHex, channelIdHex = item.idHex,
channelPicture = item.profilePicture(), channelPicture = item.profilePicture(),
@@ -258,7 +255,7 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
) )
} }
itemsIndexed(searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { index, item -> itemsIndexed(searchResultsNotes.value, key = { _, item -> "n" + item.idHex }) { _, item ->
NoteCompose(item, accountViewModel = accountViewModel, navController = navController) NoteCompose(item, accountViewModel = accountViewModel, navController = navController)
} }
} }
@@ -36,7 +36,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navControl
} }
DisposableEffect(accountViewModel) { DisposableEffect(accountViewModel) {
val observer = LifecycleEventObserver { source, event -> val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) { if (event == Lifecycle.Event.ON_RESUME) {
println("Thread Start") println("Thread Start")
ThreadFeedFilter.loadThread(noteId) ThreadFeedFilter.loadThread(noteId)