support for sign multiple events simultaneous

This commit is contained in:
greenart7c3
2023-11-27 11:03:55 -03:00
parent 4c94947509
commit 55809ad1b0
11 changed files with 571 additions and 192 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ dependencies {
implementation "androidx.core:core-splashscreen:1.0.1"
implementation "androidx.core:core-ktx:$core_ktx_version"
implementation 'androidx.activity:activity-compose:1.8.0'
implementation 'androidx.activity:activity-compose:1.8.1'
implementation "androidx.compose.ui:ui:$compose_ui_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_ui_version"
@@ -1,5 +1,6 @@
package com.greenart7c3.nostrsigner
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
@@ -9,12 +10,24 @@ import androidx.compose.material3.Surface
import androidx.compose.ui.Modifier
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.ui.AccountScreen
import com.greenart7c3.nostrsigner.ui.AccountStateViewModel
import com.greenart7c3.nostrsigner.ui.theme.NostrSignerTheme
class MainViewModel : ViewModel() {
var intents = IntentLiveData()
}
class IntentLiveData : MutableLiveData<MutableList<IntentData?>>(mutableListOf())
class MainActivity : AppCompatActivity() {
private val mainViewModel = MainViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
@@ -37,9 +50,23 @@ class MainActivity : AppCompatActivity() {
val accountStateViewModel: AccountStateViewModel = viewModel {
AccountStateViewModel(intent.getStringExtra("current_user"))
}
AccountScreen(accountStateViewModel, intent, packageName, appName)
AccountScreen(accountStateViewModel, intent, packageName, appName, mainViewModel)
}
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent == null) return
val list = mutableListOf(IntentUtils.getIntentData(intent))
mainViewModel.intents.value?.forEach {
if (list.none { item -> item?.id == it?.id }) {
list.add(it)
}
}
mainViewModel.intents.value = list
}
}
@@ -1,5 +1,6 @@
package com.greenart7c3.nostrsigner.models
import androidx.compose.runtime.MutableState
import com.vitorpamplona.quartz.encoders.HexKey
data class Permission(
@@ -44,7 +45,10 @@ class IntentData(
val callBackUrl: String?,
val compression: CompressionType,
val returnType: ReturnType,
val permissions: List<Permission>?
val permissions: List<Permission>?,
val currentAccount: String,
val checked: MutableState<Boolean>,
val rememberMyChoice: MutableState<Boolean>
)
enum class SignerType {
@@ -2,6 +2,7 @@ package com.greenart7c3.nostrsigner.service
import android.content.Intent
import android.net.Uri
import androidx.compose.runtime.mutableStateOf
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.greenart7c3.nostrsigner.models.CompressionType
@@ -60,7 +61,10 @@ object IntentUtils {
intent.extras?.getString("callbackUrl"),
compressionType,
returnType,
permissions
permissions,
intent.extras?.getString("current_user") ?: "",
mutableStateOf(true),
mutableStateOf(false)
)
}
return null
@@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import com.greenart7c3.nostrsigner.MainViewModel
import com.greenart7c3.nostrsigner.service.IntentUtils
@Composable
@@ -14,9 +16,11 @@ fun AccountScreen(
accountStateViewModel: AccountStateViewModel,
intent: Intent,
packageName: String?,
appName: String?
appName: String?,
mainViewModel: MainViewModel
) {
val accountState by accountStateViewModel.accountContent.collectAsState()
val intents by mainViewModel.intents.observeAsState()
Column {
Crossfade(
@@ -29,9 +33,12 @@ fun AccountScreen(
LoginPage(accountStateViewModel)
}
is AccountState.LoggedIn -> {
val intentData = IntentUtils.getIntentData(intent)
val localIntents = intents?.mapNotNull { it } ?: listOf()
val newIntents = localIntents.ifEmpty {
listOf(IntentUtils.getIntentData(intent))
}
MainScreen(state.account, accountStateViewModel, intentData, packageName, appName, state.route)
MainScreen(state.account, accountStateViewModel, newIntents, packageName, appName, state.route)
}
}
}
@@ -1,20 +1,48 @@
package com.greenart7c3.nostrsigner.ui
import android.app.Activity
import android.content.Intent
import android.widget.Toast
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.KeyboardArrowUp
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.Switch
import androidx.compose.material3.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
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.google.gson.GsonBuilder
import com.greenart7c3.nostrsigner.LocalPreferences
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.IntentData
@@ -22,9 +50,12 @@ import com.greenart7c3.nostrsigner.models.SignerType
import com.greenart7c3.nostrsigner.service.AmberUtils
import com.greenart7c3.nostrsigner.service.IntentUtils
import com.greenart7c3.nostrsigner.service.getAppCompatActivity
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.components.EncryptDecryptData
import com.greenart7c3.nostrsigner.ui.components.EventData
import com.greenart7c3.nostrsigner.ui.components.LoginWithPubKey
import com.greenart7c3.nostrsigner.ui.components.RememberMyChoice
import com.greenart7c3.nostrsigner.ui.theme.ButtonBorder
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.encoders.toNpub
import com.vitorpamplona.quartz.events.Event
@@ -35,7 +66,7 @@ import kotlinx.coroutines.launch
@Composable
fun HomeScreen(
modifier: Modifier,
json: IntentData?,
intents: List<IntentData?>,
packageName: String?,
applicationName: String?,
account: Account
@@ -43,110 +74,224 @@ fun HomeScreen(
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val clipboardManager = LocalClipboardManager.current
var loading by remember { mutableStateOf(false) }
Box(
modifier
) {
if (json == null) {
if (loading) {
Box(
modifier
) {
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text(stringResource(R.string.no_event_to_sign))
CircularProgressIndicator()
}
} else {
json.let {
var key = "$packageName-${it.type}"
val appName = packageName ?: it.name
when (it.type) {
SignerType.GET_PUBLIC_KEY -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
LoginWithPubKey(
appName,
applicationName,
json.permissions,
{ permissions ->
val sig = account.keyPair.pubKey.toNpub()
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
sig,
sig,
it,
permissions = permissions
)
}
} else {
Box(
modifier
) {
if (intents.filterNotNull().isEmpty()) {
Column(
Modifier.fillMaxSize(),
Arrangement.Center,
Alignment.CenterHorizontally
) {
Text(stringResource(R.string.no_event_to_sign))
}
} else {
if (intents.size == 1) {
intents.first()?.let {
var key = "$packageName-${it.type}"
val appName = packageName ?: it.name
when (it.type) {
SignerType.GET_PUBLIC_KEY -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
return@LoginWithPubKey
},
{
context.getAppCompatActivity()?.finish()
}
)
}
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EncryptDecryptData(
it.data,
shouldRunOnAccept,
remember,
packageName,
applicationName,
appName,
it.type,
{
if (it.type == SignerType.NIP04_ENCRYPT && it.data.contains("?iv=", ignoreCase = true)) {
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.message_already_encrypted),
Toast.LENGTH_SHORT
).show()
LoginWithPubKey(
appName,
applicationName,
it.permissions,
{ permissions ->
val sig = account.keyPair.pubKey.toNpub()
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
sig,
sig,
it,
permissions = permissions
)
}
return@LoginWithPubKey
},
{
context.getAppCompatActivity()?.finish()
}
return@EncryptDecryptData
} else {
try {
coroutineScope.launch(Dispatchers.IO) {
val sig = try {
AmberUtils.encryptOrDecryptData(
it.data,
it.type,
account,
it.pubKey
)
?: "Could not decrypt the message"
} catch (e: Exception) {
"Could not decrypt the message"
}
)
}
SignerType.NIP04_DECRYPT, SignerType.NIP04_ENCRYPT, SignerType.NIP44_ENCRYPT, SignerType.NIP44_DECRYPT, SignerType.DECRYPT_ZAP_EVENT -> {
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EncryptDecryptData(
it.data,
shouldRunOnAccept,
remember,
packageName,
applicationName,
appName,
it.type,
{
if (it.type == SignerType.NIP04_ENCRYPT && it.data.contains(
"?iv=",
ignoreCase = true
)
) {
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.message_already_encrypted),
Toast.LENGTH_SHORT
).show()
}
return@EncryptDecryptData
} else {
try {
coroutineScope.launch(Dispatchers.IO) {
val sig = try {
AmberUtils.encryptOrDecryptData(
it.data,
it.type,
account,
it.pubKey
)
?: "Could not decrypt the message"
} catch (e: Exception) {
"Could not decrypt the message"
}
if (it.type == SignerType.NIP04_ENCRYPT && sig == "Could not decrypt the message") {
coroutineScope.launch {
Toast.makeText(
context,
"Error encrypting content",
Toast.LENGTH_SHORT
).show()
}
return@launch
} else {
val result =
if (sig == "Could not decrypt the message" && (it.type == SignerType.DECRYPT_ZAP_EVENT)) {
""
} else {
sig
}
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
result,
result,
it
)
}
}
return@EncryptDecryptData
} catch (e: Exception) {
val message = if (it.type.toString().contains("ENCRYPT", true)) {
context.getString(R.string.encrypt)
} else {
context.getString(R.string.decrypt)
}
if (it.type == SignerType.NIP04_ENCRYPT && sig == "Could not decrypt the message") {
coroutineScope.launch {
Toast.makeText(
context,
"Error encrypting content",
context.getString(
R.string.error_to_data,
message
),
Toast.LENGTH_SHORT
).show()
}
return@launch
} else {
val result =
if (sig == "Could not decrypt the message" && (it.type == SignerType.DECRYPT_ZAP_EVENT)) {
""
} else {
sig
}
return@EncryptDecryptData
}
}
},
{
context.getAppCompatActivity()?.finish()
},
{
try {
AmberUtils.encryptOrDecryptData(
it.data,
it.type,
account,
it.pubKey
)
?: "Could not decrypt the message"
} catch (e: Exception) {
"Could not decrypt the message"
}
}
)
}
else -> {
val event = IntentUtils.getIntent(it.data, account.keyPair)
key = "$packageName-${it.type}-${event.kind}"
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EventData(
shouldRunOnAccept,
remember,
packageName,
appName,
applicationName,
event,
it.data,
{
if (event.pubKey != account.keyPair.pubKey.toHexKey()) {
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.event_pubkey_is_not_equal_to_current_logged_in_user),
Toast.LENGTH_SHORT
).show()
}
return@EventData
}
val localEvent = try {
Event.fromJson(it.data)
} catch (e: Exception) {
Event.fromJson(event.toJson())
}
account.signer.sign<Event>(
localEvent.createdAt,
localEvent.kind,
localEvent.tags,
localEvent.content
) { signedEvent ->
coroutineScope.launch {
sendResult(
context,
packageName,
@@ -154,107 +299,294 @@ fun HomeScreen(
key,
remember.value,
clipboardManager,
result,
result,
signedEvent.toJson(),
if (localEvent is LnZapRequestEvent && localEvent.tags.any { tag -> tag.any { t -> t == "anon" } }) signedEvent.toJson() else signedEvent.sig,
it
)
}
}
return@EncryptDecryptData
} catch (e: Exception) {
val message =
if (it.type.toString().contains("ENCRYPT", true)) {
context.getString(R.string.encrypt)
} else {
context.getString(R.string.decrypt)
}
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.error_to_data, message),
Toast.LENGTH_SHORT
).show()
}
return@EncryptDecryptData
},
{
context.getAppCompatActivity()?.finish()
}
}
},
{
context.getAppCompatActivity()?.finish()
},
{
try {
AmberUtils.encryptOrDecryptData(
it.data,
it.type,
account,
it.pubKey
)
?: "Could not decrypt the message"
} catch (e: Exception) {
"Could not decrypt the message"
}
)
}
)
}
else -> {
val event = IntentUtils.getIntent(it.data, account.keyPair)
key = "$packageName-${it.type}-${event.kind}"
val remember = remember {
mutableStateOf(account.savedApps[key] ?: false)
}
val shouldRunOnAccept = account.savedApps[key] ?: false
EventData(
shouldRunOnAccept,
remember,
packageName,
appName,
applicationName,
event,
it.data,
{
if (event.pubKey != account.keyPair.pubKey.toHexKey()) {
coroutineScope.launch {
Toast.makeText(
context,
context.getString(R.string.event_pubkey_is_not_equal_to_current_logged_in_user),
Toast.LENGTH_SHORT
).show()
}
} else {
Column(
Modifier.fillMaxSize()
) {
var selectAll by remember {
mutableStateOf(false)
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(horizontal = 8.dp)
.clickable {
selectAll = !selectAll
intents
.filterNotNull()
.forEach {
it.checked.value = selectAll
}
}
) {
Text(
modifier = Modifier.weight(1f),
text = stringResource(R.string.select_deselect_all)
)
Switch(
checked = selectAll,
onCheckedChange = {
selectAll = !selectAll
intents.filterNotNull().forEach {
it.checked.value = selectAll
}
return@EventData
}
val localEvent = try {
Event.fromJson(it.data)
} catch (e: Exception) {
Event.fromJson(event.toJson())
}
account.signer.sign<Event>(localEvent.createdAt, localEvent.kind, localEvent.tags, localEvent.content) { signedEvent ->
coroutineScope.launch {
sendResult(
context,
packageName,
account,
key,
remember.value,
clipboardManager,
signedEvent.toJson(),
if (localEvent is LnZapRequestEvent && localEvent.tags.any { tag -> tag.any { t -> t == "anon" } }) signedEvent.toJson() else signedEvent.sig,
it
)
}
LazyColumn(
Modifier.fillMaxHeight(0.9f)
) {
items(intents.size) {
var isExpanded by remember { mutableStateOf(false) }
Card(
Modifier
.padding(4.dp)
.clickable {
isExpanded = !isExpanded
}
) {
intents[it]?.let {
val name = LocalPreferences.getAccountName(it.currentAccount)
Row(
Modifier
.fillMaxWidth(),
Arrangement.Center,
Alignment.CenterVertically
) {
Text(
name.ifBlank { it.currentAccount.toShortenHex() }
)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(4.dp)
) {
Icon(
Icons.Default.run {
if (isExpanded) {
KeyboardArrowDown
} else {
KeyboardArrowUp
}
},
contentDescription = "",
tint = Color.LightGray
)
intents[it]?.let {
val appName = applicationName ?: packageName ?: it.name
val text = if (it.type == SignerType.SIGN_EVENT) {
val event =
IntentUtils.getIntent(it.data, account.keyPair)
if (event.kind == 22242) "requests client authentication" else "requests event signature"
} else {
when (it.type) {
SignerType.NIP44_ENCRYPT -> stringResource(R.string.encrypt_nip44)
SignerType.NIP04_ENCRYPT -> stringResource(R.string.encrypt_nip04)
SignerType.NIP44_DECRYPT -> stringResource(R.string.decrypt_nip44)
SignerType.NIP04_DECRYPT -> stringResource(R.string.decrypt_nip04)
SignerType.DECRYPT_ZAP_EVENT -> stringResource(R.string.decrypt_zap_event)
else -> stringResource(R.string.encrypt_decrypt)
}
}
Text(
modifier = Modifier.weight(1f),
text = buildAnnotatedString {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(appName)
}
append(" $text")
},
fontSize = 18.sp
)
Switch(
checked = it.checked.value,
onCheckedChange = { _ ->
it.checked.value = !it.checked.value
}
)
}
}
intents[it]?.let {
if (isExpanded) {
Column(
Modifier
.fillMaxSize()
.padding(10.dp)
) {
Text(
"Event content",
fontWeight = FontWeight.Bold
)
val content =
if (it.type == SignerType.SIGN_EVENT) {
val event = IntentUtils.getIntent(
it.data,
account.keyPair
)
if (event.kind == 22242) event.relay() else event.content
} else {
it.data
}
Text(
content,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
RememberMyChoice(
shouldRunOnAccept = false,
it.rememberMyChoice.value,
packageName,
{ }
) {
it.rememberMyChoice.value = !it.rememberMyChoice.value
intents.filter { intentData ->
intentData?.type == it.type
}.forEach { intentData ->
intentData?.let { intent ->
intent.rememberMyChoice.value =
it.rememberMyChoice.value
}
}
}
}
}
}
}
},
{
context.getAppCompatActivity()?.finish()
}
)
}
Row(
Modifier
.fillMaxWidth()
.padding(10.dp),
Arrangement.Center
) {
Button(
modifier = Modifier.fillMaxWidth(),
shape = ButtonBorder,
onClick = {
loading = true
coroutineScope.launch(Dispatchers.IO) {
try {
val activity = context.getAppCompatActivity()
val results = mutableListOf<Result>()
for (intentData in intents.filterNotNull()) {
val localAccount =
if (intentData.currentAccount.isNotBlank()) {
LocalPreferences.loadFromEncryptedStorage(
intentData.currentAccount
)
} else {
account
} ?: continue
if (packageName != null) {
if (intentData.type == SignerType.SIGN_EVENT) {
val localEvent = try {
Event.fromJson(intentData.data)
} catch (e: Exception) {
Event.fromJson(
IntentUtils.getIntent(
intentData.data,
localAccount.keyPair
).toJson()
)
}
val key = "$packageName-${intentData.type}-${localEvent.kind}"
if (intentData.rememberMyChoice.value) {
localAccount.savedApps[key] =
intentData.rememberMyChoice.value
LocalPreferences.saveToEncryptedStorage(
localAccount
)
}
localAccount.signer.sign<Event>(
localEvent.createdAt,
localEvent.kind,
localEvent.tags,
localEvent.content
) { signedEvent ->
results.add(
Result(
null,
signedEvent.sig,
intentData.id
)
)
}
} else {
val key = "$packageName-${intentData.type}"
localAccount.savedApps[key] =
intentData.rememberMyChoice.value
LocalPreferences.saveToEncryptedStorage(
localAccount
)
val signature = AmberUtils.encryptOrDecryptData(
intentData.data,
intentData.type,
localAccount,
intentData.pubKey
) ?: continue
results.add(
Result(
null,
signature,
intentData.id
)
)
}
}
}
if (results.isNotEmpty()) {
val gson = GsonBuilder().serializeNulls().create()
val json = gson.toJson(results)
val intent = Intent()
intent.putExtra("results", json)
activity?.setResult(Activity.RESULT_OK, intent)
}
activity?.finish()
} finally {
loading = false
}
}
}
) {
Text("Confirm")
}
}
}
}
}
}
}
}
class Result(
val `package`: String?,
val signature: String?,
val id: String?
)
@@ -175,7 +175,7 @@ fun GoToTop(goToTop: () -> Unit) {
fun MainScreen(
account: Account,
accountStateViewModel: AccountStateViewModel,
json: IntentData?,
intents: List<IntentData?>,
packageName: String?,
appName: String?,
route: String?
@@ -291,7 +291,7 @@ fun MainScreen(
Modifier
.fillMaxSize()
.padding(padding),
json,
intents,
packageName,
appName,
account
@@ -122,10 +122,12 @@ fun EncryptDecryptData(
RememberMyChoice(
shouldRunOnAccept,
remember,
remember.value,
packageName,
onAccept
)
) {
remember.value = !remember.value
}
AcceptRejectButtons(
onAccept,
@@ -91,10 +91,12 @@ fun EventData(
RememberMyChoice(
shouldRunOnAccept,
remember,
remember.value,
packageName,
onAccept
)
) {
remember.value = !remember.value
}
AcceptRejectButtons(
onAccept,
@@ -7,7 +7,6 @@ import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@@ -16,9 +15,10 @@ import com.greenart7c3.nostrsigner.R
@Composable
fun RememberMyChoice(
shouldRunOnAccept: Boolean,
remember: MutableState<Boolean>,
remember: Boolean,
packageName: String?,
onAccept: () -> Unit
onAccept: () -> Unit,
onChanged: () -> Unit
) {
if (shouldRunOnAccept) {
LaunchedEffect(Unit) {
@@ -31,7 +31,7 @@ fun RememberMyChoice(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clickable {
remember.value = !remember.value
onChanged()
}
) {
Text(
@@ -39,9 +39,9 @@ fun RememberMyChoice(
text = stringResource(R.string.remember_my_choice_and_don_t_ask_again)
)
Switch(
checked = remember.value,
checked = remember,
onCheckedChange = {
remember.value = !remember.value
onChanged()
}
)
}
+1
View File
@@ -81,4 +81,5 @@
<string name="relay">Relay</string>
<string name="go_to_top">go to top</string>
<string name="event_copied_to_the_clipboard">Event copied to the clipboard</string>
<string name="select_deselect_all">Select/Deselect all</string>
</resources>