mirror of
https://github.com/greenart7c3/Amber.git
synced 2026-10-05 19:08:23 +00:00
Merge pull request #385 from greenart7c3/claude/move-intents-to-utils-tv9Iw
Refactor intent management to use IntentUtils singleton
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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<ImmutableList<IntentData>>(persistentListOf())
|
||||
val intents = _intents.asStateFlow()
|
||||
var navController: NavHostController? = null
|
||||
|
||||
fun addAll(list: List<IntentData>) {
|
||||
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<IntentData>) {
|
||||
_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 ->
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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<ImmutableList<IntentData>>(persistentListOf())
|
||||
val intents = _intents.asStateFlow()
|
||||
|
||||
fun addAll(list: List<IntentData>) {
|
||||
_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<IntentData>) {
|
||||
_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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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<Context>(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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user