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.PeriodicWorkRequestBuilder
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.models.Account
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.ConnectivityService
import com.greenart7c3.nostrsigner.service.NotificationDataSource
import com.greenart7c3.nostrsigner.service.ProfileDataSource
import com.greenart7c3.nostrsigner.service.RelayDisconnectService
import com.vitorpamplona.ammolite.relays.COMMON_FEED_TYPES
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.RelaySetupInfoToConnect
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.nip34Git.issue.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.repository.GitRepositoryEvent
@@ -70,8 +63,6 @@ class Amber : Application() {
HttpClientManager.getHttpClient(useProxy)
}
val client: NostrClient = NostrClient(factory)
var profileClient = ConcurrentHashMap<String, NostrClient>()
val profileDataSource = ConcurrentHashMap<String, ProfileDataSource>()
val applicationIOScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var databases = ConcurrentHashMap<String, AppDatabase>()
var settings: AmberSettings = AmberSettings()
@@ -339,93 +330,6 @@ class Amber : Application() {
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(
subject: 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.relays.AmberRelayStats
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.AccountsBottomSheet
import com.greenart7c3.nostrsigner.ui.actions.ActiveRelaysScreen
@@ -173,7 +174,7 @@ fun MainScreen(
},
)
Amber.instance.fetchProfileData(
ProfileFetcherService().fetchProfileData(
account = account,
onPictureFound = {
profileUrl = it
@@ -39,11 +39,11 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.window.core.layout.WindowWidthSizeClass
import coil3.compose.AsyncImage
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.BuildConfig
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.Permission
import com.greenart7c3.nostrsigner.service.ProfileFetcherService
import com.greenart7c3.nostrsigner.service.toShortenHex
import com.greenart7c3.nostrsigner.ui.RememberType
import com.greenart7c3.nostrsigner.ui.navigation.Route
@@ -58,7 +58,7 @@ fun ProfilePicture(account: Account) {
var profileUrl by remember { mutableStateOf<String?>(null) }
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
Amber.instance.fetchProfileData(
ProfileFetcherService().fetchProfileData(
account = account,
onPictureFound = {
profileUrl = it