Move profile fetch to its own file

This commit is contained in:
greenart7c3
2025-07-11 13:27:33 -03:00
parent 1622a3c9f6
commit 9e8288acc9
4 changed files with 112 additions and 99 deletions
@@ -10,11 +10,6 @@ import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.PeriodicWorkRequestBuilder import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.crossfade
import coil3.util.DebugLogger
import com.greenart7c3.nostrsigner.database.AppDatabase import com.greenart7c3.nostrsigner.database.AppDatabase
import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.AmberSettings import com.greenart7c3.nostrsigner.models.AmberSettings
@@ -26,7 +21,6 @@ import com.greenart7c3.nostrsigner.relays.AmberRelayStats
import com.greenart7c3.nostrsigner.service.ClearLogsWorker import com.greenart7c3.nostrsigner.service.ClearLogsWorker
import com.greenart7c3.nostrsigner.service.ConnectivityService import com.greenart7c3.nostrsigner.service.ConnectivityService
import com.greenart7c3.nostrsigner.service.NotificationDataSource import com.greenart7c3.nostrsigner.service.NotificationDataSource
import com.greenart7c3.nostrsigner.service.ProfileDataSource
import com.greenart7c3.nostrsigner.service.RelayDisconnectService import com.greenart7c3.nostrsigner.service.RelayDisconnectService
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
import com.vitorpamplona.ammolite.relays.MutableSubscriptionCache import com.vitorpamplona.ammolite.relays.MutableSubscriptionCache
@@ -35,7 +29,6 @@ import com.vitorpamplona.ammolite.relays.Relay
import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect
import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent import com.vitorpamplona.quartz.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
@@ -70,8 +63,6 @@ class Amber : Application() {
HttpClientManager.getHttpClient(useProxy) HttpClientManager.getHttpClient(useProxy)
} }
val client: NostrClient = NostrClient(factory) val client: NostrClient = NostrClient(factory)
var profileClient = ConcurrentHashMap<String, NostrClient>()
val profileDataSource = ConcurrentHashMap<String, ProfileDataSource>()
val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var databases = ConcurrentHashMap<String, AppDatabase>() private var databases = ConcurrentHashMap<String, AppDatabase>()
var settings: AmberSettings = AmberSettings() var settings: AmberSettings = AmberSettings()
@@ -339,93 +330,6 @@ class Amber : Application() {
applicationIOScope.cancel() applicationIOScope.cancel()
} }
fun fetchProfileData(
account: Account,
onPictureFound: (String) -> Unit,
) {
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR == "offline") {
return
}
SingletonImageLoader.setSafe {
ImageLoader.Builder(this)
.crossfade(true)
.logger(DebugLogger())
.components {
add(
OkHttpNetworkFetcherFactory(
callFactory = {
HttpClientManager.getHttpClient(settings.useProxy)
},
),
)
}
.build()
}
val lastMetaData = LocalPreferences.getLastMetadataUpdate(this, account.npub)
val lastCheck = LocalPreferences.getLastCheck(this, account.npub)
val oneDayAgo = TimeUtils.oneDayAgo()
val fifteenMinutesAgo = TimeUtils.fifteenMinutesAgo()
if ((lastMetaData == 0L || oneDayAgo > lastMetaData) && (lastCheck == 0L || fifteenMinutesAgo > lastCheck)) {
Log.d(TAG, "Fetching profile data for ${account.npub}")
LocalPreferences.setLastCheck(this, account.npub, TimeUtils.now())
val relays = LocalPreferences.loadSettingsFromEncryptedStorage().defaultProfileRelays.map {
RelaySetupInfoToConnect(
url = it.url,
read = it.read,
write = it.write,
feedTypes = it.feedTypes,
forceProxy = settings.useProxy,
)
}.toTypedArray()
profileClient.getOrPut(account.hexKey, defaultValue = { NostrClient(factory) }).reconnect(relays)
profileDataSource.getOrPut(
account.hexKey,
defaultValue = {
ProfileDataSource(
client = profileClient.get(account.hexKey)!!,
account = account,
onReceiveEvent = { event ->
applicationIOScope.launch {
if (event.kind == MetadataEvent.KIND) {
(event as MetadataEvent).contactMetaData()?.let { metadata ->
metadata.name?.let { name ->
account.name = name
applicationIOScope.launch {
LocalPreferences.saveToEncryptedStorage(account = account, context = this@Amber)
}
}
metadata.profilePicture()?.let { url ->
LocalPreferences.saveProfileUrlToEncryptedStorage(url, account.npub)
LocalPreferences.setLastMetadataUpdate(this@Amber, account.npub, TimeUtils.now())
onPictureFound(url)
}
}
}
profileDataSource[account.hexKey]?.stop()
profileClient[account.hexKey]?.getAll()?.forEach {
Log.d(TAG, "disconnecting profile relay ${it.url}")
it.disconnect()
}
}
},
)
},
)
profileDataSource.get(account.hexKey)?.start()
} else {
Log.d(TAG, "Using cached profile data for ${account.npub}")
LocalPreferences.loadProfileUrlFromEncryptedStorage(account.npub)?.let {
onPictureFound(it)
return
}
}
}
suspend fun sendFeedBack( suspend fun sendFeedBack(
subject: String, subject: String,
body: String, body: String,
@@ -0,0 +1,108 @@
package com.greenart7c3.nostrsigner.service
import android.util.Log
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.crossfade
import coil3.util.DebugLogger
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.Amber.Companion.TAG
import com.greenart7c3.nostrsigner.BuildConfig
import com.greenart7c3.nostrsigner.LocalPreferences
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.okhttp.HttpClientManager
import com.vitorpamplona.ammolite.relays.NostrClient
import com.vitorpamplona.ammolite.relays.RelaySetupInfoToConnect
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.utils.TimeUtils
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class ProfileFetcherService {
fun fetchProfileData(
account: Account,
onPictureFound: (String) -> Unit,
) {
@Suppress("KotlinConstantConditions")
if (BuildConfig.FLAVOR == "offline") {
return
}
SingletonImageLoader.setSafe {
ImageLoader.Builder(Amber.instance)
.crossfade(true)
.logger(DebugLogger())
.components {
add(
OkHttpNetworkFetcherFactory(
callFactory = {
HttpClientManager.getHttpClient(Amber.instance.settings.useProxy)
},
),
)
}
.build()
}
val lastMetaData = LocalPreferences.getLastMetadataUpdate(Amber.instance, account.npub)
val lastCheck = LocalPreferences.getLastCheck(Amber.instance, account.npub)
val oneDayAgo = TimeUtils.oneDayAgo()
val fifteenMinutesAgo = TimeUtils.fifteenMinutesAgo()
if ((lastMetaData == 0L || oneDayAgo > lastMetaData) && (lastCheck == 0L || fifteenMinutesAgo > lastCheck)) {
Log.d(TAG, "Fetching profile data for ${account.npub}")
LocalPreferences.setLastCheck(Amber.instance, account.npub, TimeUtils.now())
val relays = LocalPreferences.loadSettingsFromEncryptedStorage().defaultProfileRelays.map {
RelaySetupInfoToConnect(
url = it.url,
read = it.read,
write = it.write,
feedTypes = it.feedTypes,
forceProxy = Amber.instance.settings.useProxy,
)
}.toTypedArray()
val client = NostrClient(Amber.instance.factory)
client.reconnect(relays)
val profileDataSource = ProfileDataSource(
client = client,
account = account,
onReceiveEvent = { event ->
Amber.instance.applicationIOScope.launch {
if (event.kind == MetadataEvent.KIND) {
(event as MetadataEvent).contactMetaData()?.let { metadata ->
metadata.name?.let { name ->
account.name = name
Amber.instance.applicationIOScope.launch {
LocalPreferences.saveToEncryptedStorage(account = account, context = Amber.instance)
}
}
metadata.profilePicture()?.let { url ->
LocalPreferences.saveProfileUrlToEncryptedStorage(url, account.npub)
LocalPreferences.setLastMetadataUpdate(Amber.instance, account.npub, TimeUtils.now())
onPictureFound(url)
}
}
}
}
},
)
profileDataSource.start()
Amber.instance.applicationIOScope.launch {
delay(60000)
profileDataSource.stop()
client.getAll().forEach {
Log.d(TAG, "disconnecting profile relay ${it.url}")
it.disconnect()
}
}
} else {
Log.d(TAG, "Using cached profile data for ${account.npub}")
LocalPreferences.loadProfileUrlFromEncryptedStorage(account.npub)?.let {
onPictureFound(it)
return
}
}
}
}
@@ -63,6 +63,7 @@ import com.greenart7c3.nostrsigner.models.IntentData
import com.greenart7c3.nostrsigner.models.IntentResultType import com.greenart7c3.nostrsigner.models.IntentResultType
import com.greenart7c3.nostrsigner.relays.AmberRelayStats import com.greenart7c3.nostrsigner.relays.AmberRelayStats
import com.greenart7c3.nostrsigner.service.NotificationDataSource import com.greenart7c3.nostrsigner.service.NotificationDataSource
import com.greenart7c3.nostrsigner.service.ProfileFetcherService
import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen import com.greenart7c3.nostrsigner.ui.actions.AccountBackupScreen
import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet import com.greenart7c3.nostrsigner.ui.actions.AccountsBottomSheet
import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen
@@ -173,7 +174,7 @@ fun MainScreen(
}, },
) )
Amber.instance.fetchProfileData( ProfileFetcherService().fetchProfileData(
account = account, account = account,
onPictureFound = { onPictureFound = {
profileUrl = it profileUrl = it
@@ -39,11 +39,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.window.core.layout.WindowWidthSizeClass import androidx.window.core.layout.WindowWidthSizeClass
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.BuildConfig import com.greenart7c3.nostrsigner.BuildConfig
import com.greenart7c3.nostrsigner.R import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.Permission import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.service.ProfileFetcherService
import com.greenart7c3.nostrsigner.service.toShortenHex import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.RememberType import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.navigation.Route import com.greenart7c3.nostrsigner.ui.navigation.Route
@@ -58,7 +58,7 @@ fun ProfilePicture(account: Account) {
var profileUrl by remember { mutableStateOf<String?>(null) } var profileUrl by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
Amber.instance.fetchProfileData( ProfileFetcherService().fetchProfileData(
account = account, account = account,
onPictureFound = { onPictureFound = {
profileUrl = it profileUrl = it