diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt index 8841b402..cf309616 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainActivity.kt @@ -28,6 +28,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.rememberNavController import com.greenart7c3.nostrsigner.okhttp.HttpClientManager import com.greenart7c3.nostrsigner.service.BunkerRequestUtils +import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.AccountScreen import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator @@ -56,6 +57,7 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) Amber.instance.setMainActivity(this) mainViewModel = MainViewModel(applicationContext) + intent?.let { mainViewModel.onNewIntent(it, callingPackage) } setContent { val isStartingApp = Amber.instance.isStartingAppState.collectAsStateWithLifecycle() if (isStartingApp.value) { @@ -188,7 +190,7 @@ class MainActivity : AppCompatActivity() { } override fun onDestroy() { - mainViewModel.clear() + IntentUtils.clear() super.onDestroy() } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt index 40889e8a..88f50187 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/MainViewModel.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.Stable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavHostController -import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.service.BunkerRequestUtils import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.AccountStateViewModel @@ -17,36 +16,15 @@ import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser import com.vitorpamplona.quartz.nip19Bech32.entities.NPub import com.vitorpamplona.quartz.nip19Bech32.toNpub import com.vitorpamplona.quartz.utils.Hex -import kotlinx.collections.immutable.ImmutableList -import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @Stable @SuppressLint("StaticFieldLeak") class MainViewModel(val context: Context) : ViewModel() { - private val _intents = MutableStateFlow>(persistentListOf()) - val intents = _intents.asStateFlow() var navController: NavHostController? = null - fun addAll(list: List) { - val existingIds = intents.value.mapTo(mutableSetOf()) { it.id } - val newList = list.filter { existingIds.add(it.id) } - _intents.value = (_intents.value + newList).toPersistentList() - } - - fun removeAll(intents: List) { - _intents.value = (_intents.value - intents.toSet()).toPersistentList() - } - - fun clear() { - _intents.value = persistentListOf() - } - fun getAccount(userFromIntent: String?): String? { val currentAccount = LocalPreferences.currentAccount(context) try { @@ -66,7 +44,7 @@ class MainViewModel(val context: Context) : ViewModel() { } val pubKeys = - intents.value.mapNotNull { + IntentUtils.intents.value.mapNotNull { it.event?.pubKey }.filter { it.isNotBlank() } + BunkerRequestUtils.getBunkerRequests().mapNotNull { when (val parsed = Nip19Parser.uriToRoute(it.currentAccount)?.entity) { @@ -149,7 +127,7 @@ class MainViewModel(val context: Context) : ViewModel() { account?.let { acc -> val intentData = IntentUtils.getIntentData(context, intent, callingPackage, intent.getStringExtra("route"), acc) if (intentData != null) { - addAll(listOf(intentData)) + IntentUtils.addAll(listOf(intentData)) } intent.getStringExtra("route")?.let { route -> diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/SignerActivity.kt b/app/src/main/java/com/greenart7c3/nostrsigner/SignerActivity.kt index 8208ba77..6ecc46e7 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/SignerActivity.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/SignerActivity.kt @@ -35,6 +35,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.rememberNavController import com.greenart7c3.nostrsigner.okhttp.HttpClientManager import com.greenart7c3.nostrsigner.service.BunkerRequestUtils +import com.greenart7c3.nostrsigner.service.IntentUtils import com.greenart7c3.nostrsigner.ui.AccountScreen import com.greenart7c3.nostrsigner.ui.AccountStateViewModel import com.greenart7c3.nostrsigner.ui.CenterCircularProgressIndicator @@ -60,6 +61,7 @@ class SignerActivity : AppCompatActivity() { super.onCreate(savedInstanceState) Amber.instance.setMainActivity(this) mainViewModel = MainViewModel(applicationContext) + intent?.let { mainViewModel.onNewIntent(it, callingPackage) } setContent { val isStartingApp = Amber.instance.isStartingAppState.collectAsStateWithLifecycle() NostrSignerTheme { @@ -206,7 +208,7 @@ class SignerActivity : AppCompatActivity() { } override fun onDestroy() { - mainViewModel.clear() + IntentUtils.clear() super.onDestroy() } diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt index f3803ae8..b5552c29 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/service/IntentUtils.kt @@ -16,9 +16,7 @@ import com.greenart7c3.nostrsigner.Amber import com.greenart7c3.nostrsigner.BuildConfig import com.greenart7c3.nostrsigner.FailedMigrationException import com.greenart7c3.nostrsigner.LocalPreferences -import com.greenart7c3.nostrsigner.MainActivity import com.greenart7c3.nostrsigner.R -import com.greenart7c3.nostrsigner.SignerActivity import com.greenart7c3.nostrsigner.database.ApplicationEntity import com.greenart7c3.nostrsigner.database.ApplicationPermissionsEntity import com.greenart7c3.nostrsigner.database.ApplicationWithPermissions @@ -52,10 +50,35 @@ import java.net.URLDecoder import java.util.Base64 import java.util.UUID import java.util.zip.GZIPOutputStream +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch object IntentUtils { + private val _intents = MutableStateFlow>(persistentListOf()) + val intents = _intents.asStateFlow() + + fun addAll(list: List) { + _intents.update { current -> + val existingIds = current.mapTo(mutableSetOf()) { it.id } + val newList = list.filter { existingIds.add(it.id) } + (current + newList).toPersistentList() + } + } + + fun removeAll(list: List) { + _intents.update { current -> (current - list.toSet()).toPersistentList() } + } + + fun clear() { + _intents.value = persistentListOf() + } + // Compiled once at class load time; avoids recompiling the pattern on every isUrlEncoded() call. private val URL_ENCODED_REGEX = Regex("%[0-9a-fA-F]{2}") @@ -561,16 +584,8 @@ object IntentUtils { } val id = intent.extras?.getString("id") ?: "" - val mainViewModel = when (val mainActivity = Amber.instance.getMainActivity()) { - is MainActivity -> mainActivity.mainViewModel - is SignerActivity -> mainActivity.mainViewModel - else -> null - } - - mainViewModel?.let { - if (it.intents.value.any { intent -> intent.id == id }) { - return null - } + if (intents.value.any { it.id == id }) { + return null } var localAccount = currentLoggedInAccount diff --git a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt index e8398f3b..7955e214 100644 --- a/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt +++ b/app/src/main/java/com/greenart7c3/nostrsigner/ui/AccountScreen.kt @@ -71,7 +71,7 @@ fun AccountScreen( MainLoginPage(accountStateViewModel, newNavController) } is AccountState.LoggedIn -> { - val intents by mainViewModel.intents.collectAsState(initial = persistentListOf()) + val intents by IntentUtils.intents.collectAsState(initial = persistentListOf()) LaunchedEffect(intent) { intent.intent?.let { IntentUtils.getIntentData( @@ -81,7 +81,7 @@ fun AccountScreen( it.getStringExtra("route"), state.account, )?.let { intentData -> - mainViewModel.addAll(listOf(intentData)) + IntentUtils.addAll(listOf(intentData)) } } } @@ -107,11 +107,11 @@ fun AccountScreen( onRemoveIntentData = { results, type -> when (type) { IntentResultType.ADD -> { - mainViewModel.addAll(results) + IntentUtils.addAll(results) } IntentResultType.REMOVE -> { - mainViewModel.removeAll(results) + IntentUtils.removeAll(results) } } }, diff --git a/app/src/test/java/com/greenart7c3/nostrsigner/MainViewModelTest.kt b/app/src/test/java/com/greenart7c3/nostrsigner/MainViewModelTest.kt index 02865270..38a5162e 100644 --- a/app/src/test/java/com/greenart7c3/nostrsigner/MainViewModelTest.kt +++ b/app/src/test/java/com/greenart7c3/nostrsigner/MainViewModelTest.kt @@ -1,22 +1,19 @@ package com.greenart7c3.nostrsigner -import android.content.Context import com.greenart7c3.nostrsigner.models.CompressionType import com.greenart7c3.nostrsigner.models.IntentData import com.greenart7c3.nostrsigner.models.ReturnType import com.greenart7c3.nostrsigner.models.SignerType -import io.mockk.mockk +import com.greenart7c3.nostrsigner.service.IntentUtils +import org.junit.After import org.junit.Assert.assertEquals -import org.junit.Before import org.junit.Test class MainViewModelTest { - private lateinit var viewModel: MainViewModel - - @Before - fun setUp() { - viewModel = MainViewModel(mockk(relaxed = true)) + @After + fun tearDown() { + IntentUtils.clear() } private fun intentData(id: String, data: String = "data") = IntentData( @@ -37,48 +34,48 @@ class MainViewModelTest { @Test fun `addAll adds new intents`() { - viewModel.addAll(listOf(intentData("id1"), intentData("id2"))) - assertEquals(2, viewModel.intents.value.size) + IntentUtils.addAll(listOf(intentData("id1"), intentData("id2"))) + assertEquals(2, IntentUtils.intents.value.size) } @Test fun `addAll ignores intents with duplicate id already in state`() { - viewModel.addAll(listOf(intentData("id1"))) - viewModel.addAll(listOf(intentData("id1"))) - assertEquals(1, viewModel.intents.value.size) + IntentUtils.addAll(listOf(intentData("id1"))) + IntentUtils.addAll(listOf(intentData("id1"))) + assertEquals(1, IntentUtils.intents.value.size) } @Test fun `addAll ignores intents with duplicate id even when other fields differ`() { - viewModel.addAll(listOf(intentData("id1", data = "original"))) - viewModel.addAll(listOf(intentData("id1", data = "different"))) - assertEquals(1, viewModel.intents.value.size) - assertEquals("original", viewModel.intents.value.first().data) + IntentUtils.addAll(listOf(intentData("id1", data = "original"))) + IntentUtils.addAll(listOf(intentData("id1", data = "different"))) + assertEquals(1, IntentUtils.intents.value.size) + assertEquals("original", IntentUtils.intents.value.first().data) } @Test fun `addAll deduplicates within a single batch`() { - viewModel.addAll(listOf(intentData("id1"), intentData("id1"))) - assertEquals(1, viewModel.intents.value.size) + IntentUtils.addAll(listOf(intentData("id1"), intentData("id1"))) + assertEquals(1, IntentUtils.intents.value.size) } @Test fun `addAll allows intents with different ids`() { - viewModel.addAll(listOf(intentData("id1"), intentData("id2"), intentData("id3"))) - assertEquals(3, viewModel.intents.value.size) + IntentUtils.addAll(listOf(intentData("id1"), intentData("id2"), intentData("id3"))) + assertEquals(3, IntentUtils.intents.value.size) } @Test fun `addAll appends to existing intents`() { - viewModel.addAll(listOf(intentData("id1"))) - viewModel.addAll(listOf(intentData("id2"))) - assertEquals(2, viewModel.intents.value.size) + IntentUtils.addAll(listOf(intentData("id1"))) + IntentUtils.addAll(listOf(intentData("id2"))) + assertEquals(2, IntentUtils.intents.value.size) } @Test fun `addAll with empty list leaves state unchanged`() { - viewModel.addAll(listOf(intentData("id1"))) - viewModel.addAll(emptyList()) - assertEquals(1, viewModel.intents.value.size) + IntentUtils.addAll(listOf(intentData("id1"))) + IntentUtils.addAll(emptyList()) + assertEquals(1, IntentUtils.intents.value.size) } }