sign event in background

This commit is contained in:
greenart7c3
2023-09-04 18:06:09 -03:00
parent 6d691c4741
commit 90aade338c
41 changed files with 727 additions and 1475 deletions
File diff suppressed because it is too large Load Diff
@@ -35,11 +35,29 @@ object AmberUtils {
}
fun openAmber(event: EventInterface) {
checkNotInMainThread()
ServiceManager.shouldPauseService = false
content = ""
isActivityRunning = true
openAmber(
event.toJson(),
SignerType.SIGN_EVENT,
IntentUtils.decryptActivityResultLauncher,
IntentUtils.activityResultLauncher,
""
)
while (isActivityRunning) {
// do nothing
}
}
fun loginWithAmber() {
checkNotInMainThread()
content = ""
isActivityRunning = true
openAmber(
"",
SignerType.GET_PUBLIC_KEY,
IntentUtils.activityResultLauncher,
""
)
while (isActivityRunning) {
@@ -53,7 +71,7 @@ object AmberUtils {
openAmber(
encryptedContent,
SignerType.NIP04_DECRYPT,
IntentUtils.decryptActivityResultLauncher,
IntentUtils.activityResultLauncher,
pubKey
)
while (isActivityRunning) {
@@ -68,7 +86,7 @@ object AmberUtils {
openAmber(
decryptedContent,
SignerType.NIP04_ENCRYPT,
IntentUtils.decryptActivityResultLauncher,
IntentUtils.activityResultLauncher,
pubKey
)
while (isActivityRunning) {
@@ -6,14 +6,8 @@ import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.service.notifications.PushNotificationUtils
import com.vitorpamplona.amethyst.service.notifications.RegisterAccounts
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.MainActivity
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.RelayAuthEvent
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -21,64 +15,11 @@ import kotlinx.coroutines.launch
object IntentUtils {
lateinit var activityResultLauncher: ActivityResultLauncher<Intent>
lateinit var authActivityResultLauncher: ActivityResultLauncher<Intent>
lateinit var decryptActivityResultLauncher: ActivityResultLauncher<Intent>
@OptIn(DelicateCoroutinesApi::class)
fun start(activity: MainActivity) {
activityResultLauncher = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != Activity.RESULT_OK) {
GlobalScope.launch(Dispatchers.Main) {
Toast.makeText(
Amethyst.instance,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@registerForActivityResult
}
val event = it.data?.getStringExtra("event") ?: ""
val signedEvent = Event.fromJson(event)
val authEvent = RelayAuthEvent(signedEvent.id, signedEvent.pubKey, signedEvent.createdAt, signedEvent.tags, signedEvent.content, signedEvent.sig)
RegisterAccounts(LocalPreferences.allSavedAccounts()).postRegistrationEvent(
listOf(authEvent)
)
PushNotificationUtils.hasInit = true
ServiceManager.shouldPauseService = true
}
authActivityResultLauncher = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != Activity.RESULT_OK) {
GlobalScope.launch(Dispatchers.Main) {
Toast.makeText(
Amethyst.instance,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@registerForActivityResult
}
val event = it.data?.getStringExtra("event") ?: ""
val signedEvent = Event.fromJson(event)
val authEvent = RelayAuthEvent(signedEvent.id, signedEvent.pubKey, signedEvent.createdAt, signedEvent.tags, signedEvent.content, signedEvent.sig)
GlobalScope.launch(Dispatchers.IO) {
Client.send(authEvent, authEvent.relay())
}
ServiceManager.shouldPauseService = true
}
decryptActivityResultLauncher = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode != Activity.RESULT_OK) {
GlobalScope.launch(Dispatchers.Main) {
@@ -95,6 +36,7 @@ object IntentUtils {
val event = it.data?.getStringExtra("signature") ?: ""
AmberUtils.content = event
AmberUtils.isActivityRunning = false
ServiceManager.shouldPauseService = true
}
}
}
@@ -8,7 +8,6 @@ import com.vitorpamplona.amethyst.service.relays.EOSEAccount
import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.TypedFilter
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.events.BadgeAwardEvent
import com.vitorpamplona.quartz.events.BadgeProfilesEvent
@@ -190,25 +189,13 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
super.auth(relay, challenge)
if (this::account.isInitialized) {
val loggedInWithAmber = account.loginWithAmber
val event = account.createAuthEvent(relay, challenge, loggedInWithAmber)
val event = account.createAuthEvent(relay, challenge)
if (loggedInWithAmber && !account.isWriteable()) {
if (event != null) {
AmberUtils.openAmber(
event.toJson(),
SignerType.SIGN_EVENT,
IntentUtils.authActivityResultLauncher,
""
)
}
} else {
if (event != null) {
Client.send(
event,
relay.url
)
}
if (event != null) {
Client.send(
event,
relay.url
)
}
}
}
@@ -4,10 +4,7 @@ import android.util.Log
import com.vitorpamplona.amethyst.AccountInfo
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.LocalPreferences
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.HttpClient
import com.vitorpamplona.amethyst.service.IntentUtils
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.quartz.events.RelayAuthEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -22,16 +19,14 @@ class RegisterAccounts(
// creates proof that it controls all accounts
private fun signEventsToProveControlOfAccounts(
accounts: List<AccountInfo>,
notificationToken: String,
loggedInWithAmber: Boolean
notificationToken: String
): List<RelayAuthEvent> {
return accounts.mapNotNull {
val acc = LocalPreferences.loadFromEncryptedStorage(it.npub)
if (acc != null) {
val relayToUse = acc.activeRelays()?.firstOrNull { it.read }
if (relayToUse != null) {
val event = acc.createAuthEvent(relayToUse, notificationToken, loggedInWithAmber)
event
acc.createAuthEvent(relayToUse, notificationToken)
} else {
null
}
@@ -68,25 +63,8 @@ class RegisterAccounts(
}
suspend fun go(notificationToken: String) = withContext(Dispatchers.IO) {
val accountsWithoutPrivKey = accounts.filter { !it.hasPrivKey }
val accountsWithPrivKey = accounts.filter { it.hasPrivKey }
accountsWithoutPrivKey.forEach { account ->
Log.d("fcm register", account.npub)
val events = signEventsToProveControlOfAccounts(listOf(account), notificationToken, account.loggedInWithAmber)
if (events.isNotEmpty()) {
AmberUtils.openAmber(
events.first().toJson(),
SignerType.SIGN_EVENT,
IntentUtils.activityResultLauncher,
""
)
}
}
if (accountsWithPrivKey.isNotEmpty()) {
postRegistrationEvent(
signEventsToProveControlOfAccounts(accountsWithPrivKey, notificationToken, false)
)
}
postRegistrationEvent(
signEventsToProveControlOfAccounts(accounts, notificationToken)
)
}
}
@@ -75,14 +75,12 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ServersAvailable
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
import com.vitorpamplona.amethyst.service.noProtocolUrlValidator
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.components.*
import com.vitorpamplona.amethyst.ui.note.CancelIcon
import com.vitorpamplona.amethyst.ui.note.CloseIcon
@@ -105,7 +103,6 @@ import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.replyModifier
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.toImmutableListOfLists
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
@@ -162,25 +159,6 @@ fun NewPostView(
}
}
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent, relayList = relayList)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onClose()
}
},
data = event!!.toJson()
)
}
Dialog(
onDismissRequest = { onClose() },
properties = DialogProperties(
@@ -234,10 +212,8 @@ fun NewPostView(
PostButton(
onPost = {
scope.launch(Dispatchers.IO) {
event = postViewModel.sendPost(relayList = relayList, !accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
onClose()
}
postViewModel.sendPost(relayList = relayList)
onClose()
}
},
isActive = postViewModel.canPost()
@@ -14,7 +14,11 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.model.*
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ServersAvailable
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.LocationUtil
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
@@ -27,7 +31,6 @@ import com.vitorpamplona.quartz.events.AddressableEvent
import com.vitorpamplona.quartz.events.BaseTextNoteEvent
import com.vitorpamplona.quartz.events.ChatMessageEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.PrivateDmEvent
import com.vitorpamplona.quartz.events.TextNoteEvent
import kotlinx.coroutines.Dispatchers
@@ -158,7 +161,7 @@ open class NewPostViewModel() : ViewModel() {
this.account = account
}
fun sendPost(relayList: List<Relay>? = null, signEvent: Boolean): Event? {
fun sendPost(relayList: List<Relay>? = null) {
try {
val tagger = NewMessageTagger(message.text, mentions, replyTos, originalNote?.channelHex())
tagger.run()
@@ -188,13 +191,12 @@ open class NewPostViewModel() : ViewModel() {
if (originalNote?.channelHex() != null) {
if (originalNote is AddressableEvent && originalNote?.address() != null) {
return account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash, signEvent)
account?.sendLiveMessage(tagger.message, originalNote?.address()!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash)
} else {
return account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash, signEvent)
account?.sendChannelMessage(tagger.message, tagger.channelHex!!, tagger.replyTos, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash)
}
} else if (originalNote?.event is PrivateDmEvent) {
account?.sendPrivateMessage(tagger.message, originalNote!!.author!!, originalNote!!, tagger.mentions, zapReceiver, wantsToMarkAsSensitive, localZapRaiserAmount, geoHash)
return null
} else if (originalNote?.event is ChatMessageEvent) {
val receivers = (originalNote?.event as ChatMessageEvent).recipientsPubKey().plus(originalNote?.author?.pubkeyHex).filterNotNull().toSet().toList()
@@ -207,10 +209,8 @@ open class NewPostViewModel() : ViewModel() {
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
zapReceiver = zapReceiver,
zapRaiserAmount = localZapRaiserAmount,
geohash = geoHash,
signEvent = signEvent
geohash = geoHash
)
return null
} else if (!dmUsers.isNullOrEmpty()) {
if (nip24 || dmUsers.size > 1) {
account?.sendNIP24PrivateMessage(
@@ -222,10 +222,8 @@ open class NewPostViewModel() : ViewModel() {
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
zapReceiver = zapReceiver,
zapRaiserAmount = localZapRaiserAmount,
geohash = geoHash,
signEvent = signEvent
geohash = geoHash
)
return null
} else {
account?.sendPrivateMessage(
message = tagger.message,
@@ -235,14 +233,12 @@ open class NewPostViewModel() : ViewModel() {
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
zapReceiver = zapReceiver,
zapRaiserAmount = localZapRaiserAmount,
geohash = geoHash,
signEvent = signEvent
geohash = geoHash
)
return null
}
} else {
if (wantsPoll) {
return account?.sendPoll(
account?.sendPoll(
tagger.message,
tagger.replyTos,
tagger.mentions,
@@ -255,8 +251,7 @@ open class NewPostViewModel() : ViewModel() {
wantsToMarkAsSensitive,
localZapRaiserAmount,
relayList,
geoHash,
signEvent
geoHash
)
} else {
// adds markers
@@ -266,7 +261,7 @@ open class NewPostViewModel() : ViewModel() {
?: originalNote?.replyTo?.firstOrNull()?.idHex // old rules, first item is root.
val replyId = originalNote?.idHex
return account?.sendPost(
account?.sendPost(
message = tagger.message,
replyTo = tagger.replyTos,
mentions = tagger.mentions,
@@ -278,8 +273,7 @@ open class NewPostViewModel() : ViewModel() {
root = rootId,
directMentions = tagger.directMentions,
relayList = relayList,
geohash = geoHash,
signEvent = signEvent
geohash = geoHash
)
}
}
@@ -56,10 +56,8 @@ import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.model.RelaySetupInfo
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays
import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -69,9 +67,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.lang.Math.round
@@ -91,26 +87,6 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re
}
}
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
postViewModel.clear()
onClose()
}
},
data = event!!.toJson()
)
}
Dialog(
onDismissRequest = onClose,
properties = DialogProperties(usePlatformDefaultWidth = false)
@@ -144,12 +120,8 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re
PostButton(
onPost = {
if (accountViewModel.loggedInWithAmber()) {
event = postViewModel.create(false)
} else {
postViewModel.create(true)
onClose()
}
postViewModel.create()
onClose()
},
true
)
@@ -26,20 +26,14 @@ class NewRelayListViewModel : ViewModel() {
clear()
}
fun create(signEvent: Boolean): ContactListEvent? {
if (!signEvent) {
relays.let {
return account.saveRelayList(it.value, false)
}
}
fun create() {
relays.let {
viewModelScope.launch(Dispatchers.IO) {
account.saveRelayList(it.value, true)
account.saveRelayList(it.value)
}
}
clear()
return null
}
fun clear() {
@@ -17,11 +17,7 @@ import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -33,10 +29,7 @@ import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -70,26 +63,6 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
Column(
modifier = Modifier.padding(10.dp)
) {
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
postViewModel.clear()
onClose()
}
},
data = event!!.toJson()
)
}
Row(
modifier = Modifier
.fillMaxWidth(),
@@ -103,12 +76,8 @@ fun NewUserMetadataView(onClose: () -> Unit, account: Account) {
PostButton(
onPost = {
if (account.loginWithAmber) {
event = postViewModel.create(false)
} else {
postViewModel.create(true)
onClose()
}
postViewModel.create()
onClose()
},
true
)
@@ -13,7 +13,6 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.quartz.events.GitHubIdentity
import com.vitorpamplona.quartz.events.MastodonIdentity
import com.vitorpamplona.quartz.events.MetadataEvent
import com.vitorpamplona.quartz.events.TwitterIdentity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -73,7 +72,7 @@ class NewUserMetadataViewModel : ViewModel() {
}
}
fun create(signEvent: Boolean): MetadataEvent? {
fun create() {
// Tries to not delete any existing attribute that we do not work with.
val latest = account.userProfile().info?.latestMetadata
val currentJson = if (latest != null) {
@@ -122,16 +121,10 @@ class NewUserMetadataViewModel : ViewModel() {
val writer = StringWriter()
ObjectMapper().writeValue(writer, currentJson)
if (signEvent) {
viewModelScope.launch(Dispatchers.IO) {
account.sendNewUserMetadata(writer.buffer.toString(), newClaims, true)
}
clear()
} else {
return account.sendNewUserMetadata(writer.buffer.toString(), newClaims, false)
viewModelScope.launch(Dispatchers.IO) {
account.sendNewUserMetadata(writer.buffer.toString(), newClaims)
}
return null
clear()
}
fun clear() {
@@ -30,7 +30,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
@@ -41,8 +40,6 @@ import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.launch
enum class SignerType {
@@ -189,15 +186,3 @@ fun SignerDialog(
}
}
}
@Preview
@Composable
fun Test() {
SignerDialog(
onClose = { },
onPost = { },
data = TextNoteEvent("", "", TimeUtils.now(), emptyList(), "test", "").toJson(),
type = SignerType.SIGN_EVENT,
pubKey = ""
)
}
@@ -17,7 +17,6 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -33,16 +32,11 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.TextNoteEvent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -57,11 +51,11 @@ class AddBountyAmountViewModel : ViewModel() {
this.bounty = bounty
}
fun sendPost(signEvent: Boolean): TextNoteEvent? {
fun sendPost() {
val newValue = nextAmount.text.trim().toLongOrNull()
if (newValue != null) {
val event = account?.sendPost(
account?.sendPost(
message = newValue.toString(),
replyTo = listOfNotNull(bounty),
mentions = listOfNotNull(bounty?.author),
@@ -69,14 +63,11 @@ class AddBountyAmountViewModel : ViewModel() {
wantsToMarkAsSensitive = false,
replyingTo = null,
root = null,
directMentions = setOf(),
signEvent = signEvent
directMentions = setOf()
)
nextAmount = TextFieldValue("")
return event
}
return null
}
fun cancel() {
@@ -94,25 +85,6 @@ fun AddBountyAmountDialog(bounty: Note, accountViewModel: AccountViewModel, onCl
postViewModel.load(accountViewModel.account, bounty)
val scope = rememberCoroutineScope()
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onClose()
}
},
data = event!!.toJson()
)
}
Dialog(
onDismissRequest = { onClose() },
properties = DialogProperties(
@@ -138,8 +110,8 @@ fun AddBountyAmountDialog(bounty: Note, accountViewModel: AccountViewModel, onCl
PostButton(
onPost = {
event = postViewModel.sendPost(!accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
scope.launch(Dispatchers.IO) {
postViewModel.sendPost()
onClose()
}
},
@@ -37,11 +37,9 @@ import androidx.compose.material.icons.filled.Report
import androidx.compose.material.icons.filled.Share
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
@@ -62,17 +60,13 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
import com.vitorpamplona.amethyst.ui.theme.WarningColor
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
import com.vitorpamplona.quartz.events.AudioTrackEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import kotlinx.coroutines.Dispatchers
@@ -206,25 +200,6 @@ private fun RenderMainPopup(
val isOwnNote = accountViewModel.isLoggedUser(note.author)
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author)
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onDismiss()
}
},
data = event!!.toJson()
)
}
Popup(onDismissRequest = onDismiss, alignment = Alignment.Center) {
Card(
modifier = Modifier.shadow(elevation = 6.dp, shape = cardShape),
@@ -300,10 +275,8 @@ private fun RenderMainPopup(
) {
if (accountViewModel.hideDeleteRequestDialog) {
scope.launch(Dispatchers.IO) {
event = accountViewModel.delete(note, !accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
onDismiss()
}
accountViewModel.delete(note)
onDismiss()
}
} else {
showDeleteAlertDialog.value = true
@@ -315,10 +288,8 @@ private fun RenderMainPopup(
stringResource(R.string.quick_action_unfollow)
) {
scope.launch(Dispatchers.IO) {
event = accountViewModel.unfollow(note.author!!, !accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
onDismiss()
}
accountViewModel.unfollow(note.author!!)
onDismiss()
}
}
} else {
@@ -327,10 +298,8 @@ private fun RenderMainPopup(
stringResource(R.string.quick_action_follow)
) {
scope.launch(Dispatchers.IO) {
event = accountViewModel.follow(note.author!!, !accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
onDismiss()
}
accountViewModel.follow(note.author!!)
onDismiss()
}
}
}
@@ -413,25 +382,6 @@ fun NoteQuickActionItem(icon: ImageVector, label: String, onClick: () -> Unit) {
fun DeleteAlertDialog(note: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit) {
val scope = rememberCoroutineScope()
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onDismiss()
}
},
data = event!!.toJson()
)
}
QuickActionAlertDialog(
title = stringResource(R.string.quick_action_request_deletion_alert_title),
textContent = stringResource(R.string.quick_action_request_deletion_alert_body),
@@ -439,20 +389,16 @@ fun DeleteAlertDialog(note: Note, accountViewModel: AccountViewModel, onDismiss:
buttonText = stringResource(R.string.quick_action_delete_dialog_btn),
onClickDoOnce = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.delete(note, !accountViewModel.loggedInWithAmber())
}
if (!accountViewModel.loggedInWithAmber()) {
onDismiss()
accountViewModel.delete(note)
}
onDismiss()
},
onClickDontShowAgain = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.delete(note, !accountViewModel.loggedInWithAmber())
accountViewModel.delete(note)
accountViewModel.dontShowDeleteRequestDialog()
}
if (!accountViewModel.loggedInWithAmber()) {
onDismiss()
}
onDismiss()
},
onDismiss = onDismiss
)
@@ -77,11 +77,8 @@ import coil.compose.AsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.NewPostView
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.components.ImageUrlType
import com.vitorpamplona.amethyst.ui.components.InLineIconRenderer
import com.vitorpamplona.amethyst.ui.components.TextType
@@ -110,7 +107,6 @@ import com.vitorpamplona.amethyst.ui.theme.TinyBorders
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter
import com.vitorpamplona.quartz.events.Event
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.coroutines.CoroutineScope
@@ -647,26 +643,6 @@ fun BoostReaction(
val iconButtonModifier = remember {
Modifier.size(iconSize)
}
var event by remember {
mutableStateOf<Event?>(null)
}
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
IconButton(
modifier = iconButtonModifier,
@@ -674,7 +650,7 @@ fun BoostReaction(
if (accountViewModel.isWriteable()) {
if (accountViewModel.hasBoosted(baseNote)) {
scope.launch(Dispatchers.IO) {
accountViewModel.deleteBoostsTo(baseNote, true)
accountViewModel.deleteBoostsTo(baseNote)
}
} else {
wantsToBoost = true
@@ -683,7 +659,7 @@ fun BoostReaction(
if (accountViewModel.loggedInWithAmber()) {
if (accountViewModel.hasBoosted(baseNote)) {
scope.launch(Dispatchers.IO) {
event = accountViewModel.deleteBoostsTo(baseNote, false)
accountViewModel.deleteBoostsTo(baseNote)
}
} else {
wantsToBoost = true
@@ -716,7 +692,7 @@ fun BoostReaction(
},
onRepost = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.boost(baseNote, false)
accountViewModel.boost(baseNote)
}
}
)
@@ -766,24 +742,6 @@ fun LikeReaction(
var wantsToChangeReactionSymbol by remember { mutableStateOf(false) }
var wantsToReact by remember { mutableStateOf(false) }
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
Box(
contentAlignment = Center,
@@ -804,9 +762,9 @@ fun LikeReaction(
if (accountViewModel.account.reactionChoices.size == 1) {
val reaction = accountViewModel.account.reactionChoices.first()
if (accountViewModel.hasReactedTo(baseNote, reaction)) {
event = accountViewModel.deleteReactionTo(baseNote, reaction, false)
accountViewModel.deleteReactionTo(baseNote, reaction)
} else {
event = accountViewModel.reactTo(baseNote, reaction, false)
accountViewModel.reactTo(baseNote, reaction)
}
} else if (accountViewModel.account.reactionChoices.size > 1) {
wantsToReact = true
@@ -961,9 +919,9 @@ private fun likeClick(
scope.launch(Dispatchers.IO) {
val reaction = accountViewModel.account.reactionChoices.first()
if (accountViewModel.hasReactedTo(baseNote, reaction)) {
accountViewModel.deleteReactionTo(baseNote, reaction, true)
accountViewModel.deleteReactionTo(baseNote, reaction)
} else {
accountViewModel.reactTo(baseNote, reaction, true)
accountViewModel.reactTo(baseNote, reaction)
}
}
} else if (accountViewModel.account.reactionChoices.size > 1) {
@@ -1294,7 +1252,7 @@ private fun BoostTypeChoicePopup(baseNote: Note, iconSize: Dp, accountViewModel:
onClick = {
if (accountViewModel.isWriteable()) {
scope.launch(Dispatchers.IO) {
accountViewModel.boost(baseNote, true)
accountViewModel.boost(baseNote)
onDismiss()
}
} else {
@@ -1377,40 +1335,16 @@ private fun ActionableReactionButton(
toRemove: Set<String>
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
onDismiss()
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onDismiss()
}
},
data = event!!.toJson()
)
}
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
val loggedInWithAmber = accountViewModel.loggedInWithAmber()
scope.launch(Dispatchers.IO) {
event = accountViewModel.reactToOrDelete(
accountViewModel.reactToOrDelete(
baseNote,
reactionType,
!loggedInWithAmber
reactionType
)
if (!loggedInWithAmber) {
onDismiss()
}
onDismiss()
}
},
shape = ButtonBorder,
@@ -1422,16 +1356,12 @@ private fun ActionableReactionButton(
val thisModifier = remember(reactionType) {
Modifier.combinedClickable(
onClick = {
val loggedInWithAmber = accountViewModel.loggedInWithAmber()
scope.launch(Dispatchers.IO) {
event = accountViewModel.reactToOrDelete(
accountViewModel.reactToOrDelete(
baseNote,
reactionType,
!loggedInWithAmber
reactionType
)
if (!loggedInWithAmber) {
onDismiss()
}
onDismiss()
}
},
onLongClick = {
@@ -41,19 +41,15 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Event
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -388,31 +384,12 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
val scope = rememberCoroutineScope()
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
onDismiss()
}
},
data = event!!.toJson()
)
}
if (!state.isFollowingAuthor) {
DropdownMenuItem(
onClick = {
val author = note.author ?: return@DropdownMenuItem
event = accountViewModel.follow(author, !accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
scope.launch(Dispatchers.IO) {
accountViewModel.follow(author)
onDismiss()
}
}
@@ -479,7 +456,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
event = accountViewModel.removePrivateBookmark(note, bookmarks?.decryptedContent ?: "")
accountViewModel.removePrivateBookmark(note, bookmarks?.decryptedContent ?: "")
} else {
accountViewModel.removePrivateBookmark(note)
onDismiss()
@@ -501,7 +478,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
event = accountViewModel.addPrivateBookmark(note, bookmarks?.decryptedContent ?: "")
accountViewModel.addPrivateBookmark(note, bookmarks?.decryptedContent ?: "")
} else {
accountViewModel.addPrivateBookmark(note)
onDismiss()
@@ -524,7 +501,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
event = accountViewModel.removePublicBookmark(
accountViewModel.removePublicBookmark(
note,
bookmarks?.decryptedContent ?: ""
)
@@ -549,7 +526,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
)
bookmarks?.decryptedContent = AmberUtils.content
AmberUtils.content = ""
event = accountViewModel.addPublicBookmark(
accountViewModel.addPublicBookmark(
note,
bookmarks?.decryptedContent ?: ""
)
@@ -572,10 +549,8 @@ fun NoteDropDownMenu(note: Note, popupExpanded: MutableState<Boolean>, accountVi
DropdownMenuItem(
onClick = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.delete(note, !accountViewModel.loggedInWithAmber())
if (!accountViewModel.loggedInWithAmber()) {
onDismiss()
}
accountViewModel.delete(note)
onDismiss()
}
}
) {
@@ -1,9 +1,6 @@
package com.vitorpamplona.amethyst.ui.note
import android.app.Activity
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -29,14 +26,9 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.screen.ZapReqResponse
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.FollowButton
@@ -47,9 +39,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import kotlinx.coroutines.Dispatchers
@@ -185,113 +174,11 @@ fun UserActionOptions(
accountViewModel: AccountViewModel
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
accountViewModel.account.live.invalidateData()
accountViewModel.account.saveable.invalidateData()
event = null
}
},
data = event!!.toJson()
)
}
val encryptResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
if (it.resultCode != Activity.RESULT_OK) {
scope.launch {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@rememberLauncherForActivityResult
}
val encryptedContent = it.data?.getStringExtra("signature") ?: ""
event = accountViewModel.show(baseAuthor, encryptedContent)
}
)
val decryptResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
if (it.resultCode != Activity.RESULT_OK) {
scope.launch {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@rememberLauncherForActivityResult
}
val decryptedContent = it.data?.getStringExtra("signature") ?: ""
val blockList = accountViewModel.account.getBlockList()
val privateTags = if (blockList == null) {
listOf(listOf("p", baseAuthor.pubkeyHex))
} else {
if (accountViewModel.account.isHidden(baseAuthor)) {
blockList.privateTagsOrEmpty(decryptedContent).filter { element -> !element.contains(baseAuthor.pubkeyHex) }
} else {
blockList.privateTagsOrEmpty(decryptedContent).plus(element = listOf("p", baseAuthor.pubkeyHex))
}
}
val msg = Event.mapper.writeValueAsString(privateTags)
ServiceManager.shouldPauseService = true
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
)
WatchIsHiddenUser(baseAuthor, accountViewModel) { isHidden ->
if (isHidden) {
ShowUserButton {
if (accountViewModel.loggedInWithAmber()) {
scope.launch(Dispatchers.IO) {
val blockList = accountViewModel.account.getBlockList()
val content = blockList?.content ?: ""
if (content.isBlank()) {
val privateTags = listOf(listOf("p", baseAuthor.pubkeyHex))
val msg = Event.mapper.writeValueAsString(privateTags)
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
} else {
AmberUtils.openAmber(
content,
SignerType.NIP04_DECRYPT,
decryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
}
} else {
accountViewModel.show(baseAuthor)
}
accountViewModel.show(baseAuthor)
}
} else {
ShowFollowingOrUnfollowingButton(baseAuthor, accountViewModel)
@@ -321,30 +208,13 @@ fun ShowFollowingOrUnfollowingButton(
}
}
var event by remember { mutableStateOf<ContactListEvent?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
if (isFollowing) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.unfollow(baseAuthor, false)
scope.launch(Dispatchers.IO) {
accountViewModel.unfollow(baseAuthor)
}
} else {
scope.launch {
Toast
@@ -358,7 +228,7 @@ fun ShowFollowingOrUnfollowingButton(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.unfollow(baseAuthor, true)
accountViewModel.unfollow(baseAuthor)
}
}
}
@@ -366,7 +236,9 @@ fun ShowFollowingOrUnfollowingButton(
FollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.follow(baseAuthor, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.follow(baseAuthor)
}
} else {
scope.launch {
Toast
@@ -380,7 +252,7 @@ fun ShowFollowingOrUnfollowingButton(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.follow(baseAuthor, true)
accountViewModel.follow(baseAuthor)
}
}
}
@@ -30,16 +30,11 @@ import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
import com.vitorpamplona.amethyst.ui.note.showAmount
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.BookmarkListEvent
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.DeletionEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GiftWrapEvent
import com.vitorpamplona.quartz.events.LnZapEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.events.PayInvoiceErrorResponse
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.ReactionEvent
import com.vitorpamplona.quartz.events.ReportEvent
import com.vitorpamplona.quartz.events.SealedGossipEvent
import com.vitorpamplona.quartz.events.UserMetadata
@@ -112,16 +107,16 @@ class AccountViewModel(val account: Account) : ViewModel() {
return account.userProfile()
}
fun reactTo(note: Note, reaction: String, signEvent: Boolean): ReactionEvent? {
return account.reactTo(note, reaction, signEvent)
fun reactTo(note: Note, reaction: String) {
account.reactTo(note, reaction)
}
fun reactToOrDelete(note: Note, reaction: String, signEvent: Boolean): Event? {
fun reactToOrDelete(note: Note, reaction: String) {
val currentReactions = account.reactionTo(note, reaction)
if (currentReactions.isNotEmpty()) {
return account.delete(currentReactions, signEvent)
account.delete(currentReactions)
} else {
return account.reactTo(note, reaction, signEvent)
account.reactTo(note, reaction)
}
}
@@ -134,16 +129,16 @@ class AccountViewModel(val account: Account) : ViewModel() {
return account.hasReacted(baseNote, reaction)
}
fun deleteReactionTo(note: Note, reaction: String, signEvent: Boolean): DeletionEvent? {
return account.delete(account.reactionTo(note, reaction), signEvent)
fun deleteReactionTo(note: Note, reaction: String) {
account.delete(account.reactionTo(note, reaction))
}
fun hasBoosted(baseNote: Note): Boolean {
return account.hasBoosted(baseNote)
}
fun deleteBoostsTo(note: Note, signEvent: Boolean): DeletionEvent? {
return account.delete(account.boostsTo(note), signEvent)
fun deleteBoostsTo(note: Note) {
account.delete(account.boostsTo(note))
}
fun calculateIfNoteWasZappedByAccount(zappedNote: Note, onWasZapped: (Boolean) -> Unit) {
@@ -302,8 +297,8 @@ class AccountViewModel(val account: Account) : ViewModel() {
}
}
fun boost(note: Note, signEvent: Boolean): Event? {
return account.boost(note, signEvent)
fun boost(note: Note) {
account.boost(note)
}
fun removeEmojiPack(usersEmojiList: Note, emojiList: Note) {
@@ -318,24 +313,24 @@ class AccountViewModel(val account: Account) : ViewModel() {
account.addPrivateBookmark(note)
}
fun addPrivateBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
return account.addPrivateBookmark(note, decryptedContent)
fun addPrivateBookmark(note: Note, decryptedContent: String) {
account.addPrivateBookmark(note, decryptedContent)
}
fun addPublicBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
return account.addPublicBookmark(note, decryptedContent)
fun addPublicBookmark(note: Note, decryptedContent: String) {
account.addPublicBookmark(note, decryptedContent)
}
fun removePublicBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
return account.removePublicBookmark(note, decryptedContent)
fun removePublicBookmark(note: Note, decryptedContent: String) {
account.removePublicBookmark(note, decryptedContent)
}
fun addPublicBookmark(note: Note) {
account.addPublicBookmark(note)
}
fun removePrivateBookmark(note: Note, decryptedContent: String): BookmarkListEvent? {
return account.removePrivateBookmark(note, decryptedContent)
fun removePrivateBookmark(note: Note, decryptedContent: String) {
account.removePrivateBookmark(note, decryptedContent)
}
fun removePrivateBookmark(note: Note) {
@@ -358,8 +353,8 @@ class AccountViewModel(val account: Account) : ViewModel() {
account.broadcast(note)
}
fun delete(note: Note, signEvent: Boolean): DeletionEvent? {
return account.delete(note, signEvent)
fun delete(note: Note) {
account.delete(note)
}
fun decrypt(note: Note): String? {
@@ -382,12 +377,12 @@ class AccountViewModel(val account: Account) : ViewModel() {
account.prefer(source, target, preference)
}
fun follow(user: User, signEvent: Boolean): ContactListEvent? {
return account.follow(user, signEvent)
fun follow(user: User) {
account.follow(user)
}
fun unfollow(user: User, signEvent: Boolean): ContactListEvent? {
return account.unfollow(user, signEvent)
fun unfollow(user: User) {
account.unfollow(user)
}
fun isLoggedUser(user: User?): Boolean {
@@ -489,20 +484,12 @@ class AccountViewModel(val account: Account) : ViewModel() {
return account.unseal(event)
}
fun show(user: User, encryptedContent: String): PeopleListEvent? {
return account.showUser(user.pubkeyHex, encryptedContent)
}
fun show(user: User) {
viewModelScope.launch(Dispatchers.IO) {
account.showUser(user.pubkeyHex)
}
}
fun hide(user: User, encryptedContent: String): PeopleListEvent? {
return account.hideUser(user.pubkeyHex, encryptedContent)
}
fun hide(user: User) {
viewModelScope.launch(Dispatchers.IO) {
account.hideUser(user.pubkeyHex)
@@ -86,12 +86,10 @@ import com.vitorpamplona.amethyst.model.PublicChatChannel
import com.vitorpamplona.amethyst.model.ServersAvailable
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
import com.vitorpamplona.amethyst.ui.components.LoadNote
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
@@ -131,7 +129,6 @@ import com.vitorpamplona.amethyst.ui.theme.SmallBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
import com.vitorpamplona.quartz.events.Participant
@@ -263,24 +260,6 @@ fun ChannelScreen(
val scope = rememberCoroutineScope()
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
// LAST ROW
EditFieldRow(newPostModel, isPrivate = false, accountViewModel = accountViewModel) {
scope.launch(Dispatchers.IO) {
@@ -292,22 +271,20 @@ fun ChannelScreen(
)
tagger.run()
if (channel is PublicChatChannel) {
event = accountViewModel.account.sendChannelMessage(
accountViewModel.account.sendChannelMessage(
message = tagger.message,
toChannel = channel.idHex,
replyTo = tagger.replyTos,
mentions = tagger.mentions,
wantsToMarkAsSensitive = false,
signEvent = !accountViewModel.loggedInWithAmber()
wantsToMarkAsSensitive = false
)
} else if (channel is LiveActivitiesChannel) {
event = accountViewModel.account.sendLiveMessage(
accountViewModel.account.sendLiveMessage(
message = tagger.message,
toChannel = channel.address,
replyTo = tagger.replyTos,
mentions = tagger.mentions,
wantsToMarkAsSensitive = false,
signEvent = !accountViewModel.loggedInWithAmber()
wantsToMarkAsSensitive = false
)
}
newPostModel.message = TextFieldValue("")
@@ -1127,30 +1104,12 @@ private fun EditButton(accountViewModel: AccountViewModel, channel: PublicChatCh
@Composable
fun JoinChatButton(accountViewModel: AccountViewModel, channel: Channel, nav: (String) -> Unit) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.account.follow(channel, !accountViewModel.loggedInWithAmber())
accountViewModel.account.follow(channel)
}
},
shape = ButtonBorder,
@@ -1167,30 +1126,12 @@ fun JoinChatButton(accountViewModel: AccountViewModel, channel: Channel, nav: (S
@Composable
fun LeaveChatButton(accountViewModel: AccountViewModel, channel: Channel, nav: (String) -> Unit) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.account.unfollow(channel, !accountViewModel.loggedInWithAmber())
accountViewModel.account.unfollow(channel)
}
},
shape = ButtonBorder,
@@ -1207,30 +1148,12 @@ fun LeaveChatButton(accountViewModel: AccountViewModel, channel: Channel, nav: (
@Composable
fun JoinCommunityButton(accountViewModel: AccountViewModel, note: AddressableNote, nav: (String) -> Unit) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.account.follow(note, !accountViewModel.loggedInWithAmber())
accountViewModel.account.follow(note)
}
},
shape = ButtonBorder,
@@ -1247,30 +1170,12 @@ fun JoinCommunityButton(accountViewModel: AccountViewModel, note: AddressableNot
@Composable
fun LeaveCommunityButton(accountViewModel: AccountViewModel, note: AddressableNote, nav: (String) -> Unit) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.account.unfollow(note, !accountViewModel.loggedInWithAmber())
accountViewModel.account.unfollow(note)
}
},
shape = ButtonBorder,
@@ -63,17 +63,13 @@ import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ServersAvailable
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrChatroomDataSource
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
import com.vitorpamplona.amethyst.ui.actions.PostButton
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
@@ -96,7 +92,6 @@ import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.ChatMessageEvent
import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.Event
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
@@ -297,49 +292,6 @@ fun ChatroomScreen(
val scope = rememberCoroutineScope()
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
var message by remember { mutableStateOf<String?>(null) }
if (message != null) {
SignerDialog(
onClose = {
message = null
},
onPost = {
scope.launch(Dispatchers.IO) {
event = accountViewModel.account.sendPrivateMessage(
message = it,
toUser = room.users.first(),
replyingTo = replyTo.value,
mentions = null,
wantsToMarkAsSensitive = false,
signEvent = false
)
message = null
}
},
data = message!!,
pubKey = room.users.first(),
type = SignerType.NIP04_ENCRYPT
)
}
// LAST ROW
PrivateMessageEditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
scope.launch(Dispatchers.IO) {
@@ -350,22 +302,16 @@ fun ChatroomScreen(
toUsers = room.users.toList(),
replyingTo = replyTo.value,
mentions = null,
wantsToMarkAsSensitive = false,
signEvent = true
wantsToMarkAsSensitive = false
)
} else {
if (!accountViewModel.isWriteable() && accountViewModel.loggedInWithAmber()) {
message = newPostModel.message.text
} else {
accountViewModel.account.sendPrivateMessage(
message = newPostModel.message.text,
toUser = room.users.first(),
replyingTo = replyTo.value,
mentions = null,
wantsToMarkAsSensitive = false,
signEvent = true
)
}
accountViewModel.account.sendPrivateMessage(
message = newPostModel.message.text,
toUser = room.users.first(),
replyingTo = replyTo.value,
mentions = null,
wantsToMarkAsSensitive = false
)
}
newPostModel.message = TextFieldValue("")
@@ -700,8 +646,7 @@ fun NewSubjectView(onClose: () -> Unit, accountViewModel: AccountViewModel, room
subject = groupName.value.ifBlank { null },
replyingTo = null,
mentions = null,
wantsToMarkAsSensitive = false,
signEvent = true
wantsToMarkAsSensitive = false
)
}
@@ -31,15 +31,11 @@ import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.screen.NostrGeoHashFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -171,29 +167,14 @@ fun GeoHashActionOptions(
userState?.user?.isFollowingGeohashCached(tag) ?: false
}
}
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
if (isFollowingTag) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.unfollowGeohash(tag, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.unfollowGeohash(tag)
}
} else {
scope.launch {
Toast
@@ -207,7 +188,7 @@ fun GeoHashActionOptions(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.unfollowGeohash(tag, true)
accountViewModel.account.unfollowGeohash(tag)
}
}
}
@@ -215,7 +196,9 @@ fun GeoHashActionOptions(
FollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.followGeohash(tag, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.followGeohash(tag)
}
} else {
scope.launch {
Toast
@@ -229,7 +212,7 @@ fun GeoHashActionOptions(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.followGeohash(tag, true)
accountViewModel.account.followGeohash(tag)
}
}
}
@@ -16,10 +16,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -30,14 +28,10 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -149,30 +143,14 @@ fun HashtagActionOptions(
userState?.user?.isFollowingHashtagCached(tag) ?: false
}
}
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
if (isFollowingTag) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.unfollowHashtag(tag, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.unfollowHashtag(tag)
}
} else {
scope.launch {
Toast
@@ -186,7 +164,7 @@ fun HashtagActionOptions(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.unfollowHashtag(tag, true)
accountViewModel.account.unfollowHashtag(tag)
}
}
}
@@ -194,7 +172,9 @@ fun HashtagActionOptions(
FollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.followHashtag(tag, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.followHashtag(tag)
}
} else {
scope.launch {
Toast
@@ -208,7 +188,7 @@ fun HashtagActionOptions(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.followHashtag(tag, true)
accountViewModel.account.followHashtag(tag)
}
}
}
@@ -1,11 +1,8 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.*
@@ -56,17 +53,12 @@ import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ServiceManager
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.NostrUserProfileDataSource
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
import com.vitorpamplona.amethyst.ui.components.InvoiceRequestCard
@@ -101,13 +93,10 @@ import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.AppDefinitionEvent
import com.vitorpamplona.quartz.events.BadgeDefinitionEvent
import com.vitorpamplona.quartz.events.BadgeProfilesEvent
import com.vitorpamplona.quartz.events.ChatroomKey
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.GitHubIdentity
import com.vitorpamplona.quartz.events.IdentityClaim
import com.vitorpamplona.quartz.events.ImmutableListOfLists
@@ -728,113 +717,11 @@ private fun ProfileActions(
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
accountViewModel.account.live.invalidateData()
accountViewModel.account.saveable.invalidateData()
event = null
}
},
data = event!!.toJson()
)
}
val encryptResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
if (it.resultCode != Activity.RESULT_OK) {
scope.launch {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@rememberLauncherForActivityResult
}
val encryptedContent = it.data?.getStringExtra("signature") ?: ""
event = accountViewModel.show(baseUser, encryptedContent)
}
)
val decryptResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
if (it.resultCode != Activity.RESULT_OK) {
scope.launch {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@rememberLauncherForActivityResult
}
val decryptedContent = it.data?.getStringExtra("signature") ?: ""
val blockList = accountViewModel.account.getBlockList()
val privateTags = if (blockList == null) {
listOf(listOf("p", baseUser.pubkeyHex))
} else {
if (accountViewModel.account.isHidden(baseUser)) {
blockList.privateTagsOrEmpty(decryptedContent).filter { element -> !element.contains(baseUser.pubkeyHex) }
} else {
blockList.privateTagsOrEmpty(decryptedContent).plus(element = listOf("p", baseUser.pubkeyHex))
}
}
val msg = Event.mapper.writeValueAsString(privateTags)
ServiceManager.shouldPauseService = true
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
)
WatchIsHiddenUser(baseUser, accountViewModel) { isHidden ->
if (isHidden) {
ShowUserButton {
if (accountViewModel.loggedInWithAmber()) {
scope.launch(Dispatchers.IO) {
val blockList = accountViewModel.account.getBlockList()
val content = blockList?.content ?: ""
if (content.isBlank()) {
val privateTags = listOf(listOf("p", baseUser.pubkeyHex))
val msg = Event.mapper.writeValueAsString(privateTags)
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
} else {
AmberUtils.openAmber(
content,
SignerType.NIP04_DECRYPT,
decryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
}
} else {
accountViewModel.showUser(baseUser.pubkeyHex)
}
accountViewModel.showUser(baseUser.pubkeyHex)
}
} else {
DisplayFollowUnfollowButton(baseUser, accountViewModel)
@@ -858,30 +745,13 @@ private fun DisplayFollowUnfollowButton(
it.user.isFollowing(accountViewModel.account.userProfile())
}.distinctUntilChanged().observeAsState(initial = baseUser.isFollowing(accountViewModel.account.userProfile()))
var event by remember { mutableStateOf<ContactListEvent?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
event = null
}
},
data = event!!.toJson()
)
}
if (isLoggedInFollowingUser) {
UnfollowButton {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.unfollow(baseUser, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.unfollow(baseUser)
}
} else {
scope.launch {
Toast
@@ -895,7 +765,7 @@ private fun DisplayFollowUnfollowButton(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.unfollow(baseUser, true)
accountViewModel.account.unfollow(baseUser)
}
}
}
@@ -904,7 +774,9 @@ private fun DisplayFollowUnfollowButton(
FollowButton(R.string.follow_back) {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.follow(baseUser, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.follow(baseUser)
}
} else {
scope.launch {
Toast
@@ -918,7 +790,7 @@ private fun DisplayFollowUnfollowButton(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.follow(baseUser, true)
accountViewModel.account.follow(baseUser)
}
}
}
@@ -926,7 +798,9 @@ private fun DisplayFollowUnfollowButton(
FollowButton(R.string.follow) {
if (!accountViewModel.isWriteable()) {
if (accountViewModel.loggedInWithAmber()) {
event = accountViewModel.account.follow(baseUser, false)
scope.launch(Dispatchers.IO) {
accountViewModel.account.follow(baseUser)
}
} else {
scope.launch {
Toast
@@ -940,7 +814,7 @@ private fun DisplayFollowUnfollowButton(
}
} else {
scope.launch(Dispatchers.IO) {
accountViewModel.account.follow(baseUser, true)
accountViewModel.account.follow(baseUser)
}
}
}
@@ -1828,88 +1702,6 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
) {
val clipboardManager = LocalClipboardManager.current
val scope = rememberCoroutineScope()
val context = LocalContext.current
var event by remember { mutableStateOf<Event?>(null) }
if (event != null) {
SignerDialog(
onClose = {
event = null
},
onPost = {
scope.launch(Dispatchers.IO) {
val signedEvent = Event.fromJson(it)
Client.send(signedEvent)
LocalCache.verifyAndConsume(signedEvent, null)
accountViewModel.account.live.invalidateData()
accountViewModel.account.saveable.invalidateData()
event = null
onDismiss()
}
},
data = event!!.toJson()
)
}
val encryptResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
if (it.resultCode != Activity.RESULT_OK) {
scope.launch {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@rememberLauncherForActivityResult
}
val encryptedContent = it.data?.getStringExtra("signature") ?: ""
event = if (accountViewModel.account.isHidden(user)) {
accountViewModel.show(user, encryptedContent)
} else {
accountViewModel.hide(user, encryptedContent)
}
}
)
val decryptResult = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
onResult = {
if (it.resultCode != Activity.RESULT_OK) {
scope.launch {
Toast.makeText(
context,
"Sign request rejected",
Toast.LENGTH_SHORT
).show()
}
return@rememberLauncherForActivityResult
}
val decryptedContent = it.data?.getStringExtra("signature") ?: ""
val blockList = accountViewModel.account.getBlockList()
val privateTags = if (blockList == null) {
listOf(listOf("p", user.pubkeyHex))
} else {
if (accountViewModel.account.isHidden(user)) {
blockList.privateTagsOrEmpty(decryptedContent).filter { element -> !element.contains(user.pubkeyHex) }
} else {
blockList.privateTagsOrEmpty(decryptedContent).plus(element = listOf("p", user.pubkeyHex))
}
}
val msg = Event.mapper.writeValueAsString(privateTags)
ServiceManager.shouldPauseService = true
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
)
DropdownMenuItem(onClick = { clipboardManager.setText(AnnotatedString(user.pubkeyNpub())); onDismiss() }) {
Text(stringResource(R.string.copy_user_id))
@@ -1920,33 +1712,8 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
if (accountViewModel.account.isHidden(user)) {
DropdownMenuItem(
onClick = {
if (accountViewModel.loggedInWithAmber()) {
scope.launch(Dispatchers.IO) {
val blockList = accountViewModel.account.getBlockList()
val content = blockList?.content ?: ""
if (content.isBlank()) {
val privateTags = listOf(listOf("p", user.pubkeyHex))
val msg = Event.mapper.writeValueAsString(privateTags)
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
} else {
AmberUtils.openAmber(
content,
SignerType.NIP04_DECRYPT,
decryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
}
} else {
accountViewModel.show(user)
onDismiss()
}
accountViewModel.show(user)
onDismiss()
}
) {
Text(stringResource(R.string.unblock_user))
@@ -1954,33 +1721,8 @@ fun UserProfileDropDownMenu(user: User, popupExpanded: Boolean, onDismiss: () ->
} else {
DropdownMenuItem(
onClick = {
if (accountViewModel.loggedInWithAmber()) {
scope.launch(Dispatchers.IO) {
val blockList = accountViewModel.account.getBlockList()
val content = blockList?.content ?: ""
if (content.isBlank()) {
val privateTags = listOf(listOf("p", user.pubkeyHex))
val msg = Event.mapper.writeValueAsString(privateTags)
AmberUtils.openAmber(
msg,
SignerType.NIP04_ENCRYPT,
encryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
} else {
AmberUtils.openAmber(
content,
SignerType.NIP04_DECRYPT,
decryptResult,
accountViewModel.account.keyPair.pubKey.toHexKey()
)
}
}
} else {
accountViewModel.hide(user)
onDismiss()
}
accountViewModel.hide(user)
onDismiss()
}
) {
Text(stringResource(id = R.string.block_hide_user))
@@ -36,15 +36,16 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.AmberUtils
import com.vitorpamplona.amethyst.service.PackageUtils
import com.vitorpamplona.amethyst.ui.actions.SignerDialog
import com.vitorpamplona.amethyst.ui.actions.SignerType
import com.vitorpamplona.amethyst.ui.qrcode.SimpleQrCodeScanner
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
@OptIn(ExperimentalComposeUiApi::class)
@@ -65,7 +66,7 @@ fun LoginPage(
val useProxy = remember { mutableStateOf(false) }
val proxyPort = remember { mutableStateOf("9050") }
var connectOrbotDialogOpen by remember { mutableStateOf(false) }
var loginWithAmber by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
Column(
modifier = Modifier
@@ -258,37 +259,6 @@ fun LoginPage(
}
}
if (loginWithAmber) {
SignerDialog(
onClose = {
loginWithAmber = false
},
onPost = {
key.value = TextFieldValue(it)
if (!acceptedTerms.value) {
termsAcceptanceIsRequired =
context.getString(R.string.acceptance_of_terms_is_required)
}
if (key.value.text.isBlank()) {
errorMessage = context.getString(R.string.key_is_required)
}
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt(), true)
} catch (e: Exception) {
Log.e("Login", "Could not sign in", e)
errorMessage = context.getString(R.string.invalid_key)
}
}
loginWithAmber = false
},
data = "",
type = SignerType.GET_PUBLIC_KEY
)
}
Spacer(modifier = Modifier.height(20.dp))
Box(modifier = Modifier.padding(40.dp, 0.dp, 40.dp, 0.dp)) {
@@ -329,7 +299,27 @@ fun LoginPage(
Box(modifier = Modifier.padding(40.dp, 40.dp, 40.dp, 0.dp)) {
Button(
onClick = {
loginWithAmber = true
scope.launch(Dispatchers.IO) {
AmberUtils.loginWithAmber()
key.value = TextFieldValue(AmberUtils.content)
if (!acceptedTerms.value) {
termsAcceptanceIsRequired =
context.getString(R.string.acceptance_of_terms_is_required)
}
if (key.value.text.isBlank()) {
errorMessage = context.getString(R.string.key_is_required)
}
if (acceptedTerms.value && key.value.text.isNotBlank()) {
try {
accountViewModel.startUI(key.value.text, useProxy.value, proxyPort.value.toInt(), true)
} catch (e: Exception) {
Log.e("Login", "Could not sign in", e)
errorMessage = context.getString(R.string.invalid_key)
}
}
}
},
shape = RoundedCornerShape(Size35dp),
modifier = Modifier