Merge pull request #1636 from davotoula/reduce-sonarcube-warnings

Reduce sonarcube warnings
This commit is contained in:
Vitor Pamplona
2026-01-04 13:37:26 -05:00
committed by GitHub
7 changed files with 137 additions and 104 deletions
@@ -248,10 +248,8 @@ object LocalPreferences {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val prefsDir = File(prefsDirPath) val prefsDir = File(prefsDirPath)
prefsDir.list()?.forEach { prefsDir.list()?.forEach {
if (it.contains(npub)) { if (it.contains(npub) && !File(prefsDir, it).delete()) {
if (!File(prefsDir, it).delete()) { Log.w("LocalPreferences", "Failed to delete preference file: $it")
Log.w("LocalPreferences", "Failed to delete preference file: $it")
}
} }
} }
} }
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.service.relays
import androidx.collection.LruCache import androidx.collection.LruCache
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relays.MutableTime
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
typealias SincePerRelayMap = MutableMap<NormalizedRelayUrl, MutableTime> typealias SincePerRelayMap = MutableMap<NormalizedRelayUrl, MutableTime>
@@ -74,64 +74,23 @@ fun VoiceMessagePreview(
var progress by remember { mutableFloatStateOf(0f) } var progress by remember { mutableFloatStateOf(0f) }
var mediaPlayer by remember { mutableStateOf<MediaPlayer?>(null) } var mediaPlayer by remember { mutableStateOf<MediaPlayer?>(null) }
// Initialize MediaPlayer ManageMediaPlayer(
DisposableEffect(voiceMetadata.url, localFile) { voiceMetadata = voiceMetadata,
val player = createMediaPlayer(voiceMetadata.url, localFile) localFile = localFile,
player?.setOnCompletionListener { onCompletion = {
isPlaying = false isPlaying = false
progress = 0f progress = 0f
} },
mediaPlayer = player onPlayerChanged = { mediaPlayer = it },
onRelease = { isPlaying = false },
)
onDispose { TrackPlaybackProgress(
// Stop playback and clean up mediaPlayer = mediaPlayer,
try { isPlaying = isPlaying,
player?.stop() onProgressUpdate = { progress = it },
} catch (e: IllegalStateException) { onInvalidState = { isPlaying = false },
// Player might already be stopped )
Log.d("VoiceMessagePreview", "MediaPlayer stop failed (already stopped)", e)
}
player?.release()
mediaPlayer = null
isPlaying = false
}
}
// Update progress while playing
LaunchedEffect(mediaPlayer, isPlaying) {
// Capture player reference to avoid reading volatile state repeatedly
val player = mediaPlayer
if (player != null && isPlaying) {
while (isActive) {
try {
if (player.isPlaying) {
val current = player.currentPosition.toFloat()
val duration = player.duration.toFloat()
// Validate values before calculating progress
val newProgress =
if (duration > 0 && current >= 0) {
(current / duration).coerceIn(0f, 1f)
} else {
0f
}
// Only update if value is valid (not NaN or Infinity)
if (newProgress.isFinite()) {
progress = newProgress
}
} else {
// Player stopped, exit loop and let LaunchedEffect restart
break
}
} catch (e: IllegalStateException) {
// Player in invalid state, stop tracking
Log.w("VoiceMessagePreview", "MediaPlayer in invalid state during progress tracking", e)
isPlaying = false
break
}
delay(100)
}
}
}
Box( Box(
modifier = modifier =
@@ -150,27 +109,13 @@ fun VoiceMessagePreview(
// Play/Pause Button // Play/Pause Button
IconButton( IconButton(
onClick = { onClick = {
val player = mediaPlayer handlePlayPauseClick(
if (player != null) { mediaPlayer = mediaPlayer,
try { isPlaying = isPlaying,
if (isPlaying) { progress = progress,
player.pause() onProgressReset = { progress = 0f },
isPlaying = false onPlayingChanged = { isPlaying = it },
} else { )
// Validate progress before comparison
if (progress.isFinite() && progress >= 1f) {
player.seekTo(0)
progress = 0f
}
player.start()
isPlaying = true
}
} catch (e: IllegalStateException) {
// MediaPlayer in invalid state, ignore
Log.w("VoiceMessagePreview", "MediaPlayer operation failed in onClick handler", e)
isPlaying = false
}
}
}, },
modifier = Modifier.size(48.dp), modifier = Modifier.size(48.dp),
) { ) {
@@ -194,24 +139,11 @@ fun VoiceMessagePreview(
waveformBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.onSurfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant)), waveformBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.onSurfaceVariant, MaterialTheme.colorScheme.onSurfaceVariant)),
progressBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.primary)), progressBrush = Brush.linearGradient(listOf(MaterialTheme.colorScheme.primary, MaterialTheme.colorScheme.primary)),
onProgressChange = { newProgress -> onProgressChange = { newProgress ->
// Validate incoming progress value handleWaveformScrub(
if (newProgress.isFinite() && newProgress >= 0f && newProgress <= 1f) { newProgress = newProgress,
val player = mediaPlayer mediaPlayer = mediaPlayer,
if (player != null) { onProgressChanged = { progress = it },
try { )
val duration = player.duration
// Only seek if duration is valid
if (duration > 0) {
val newPosition = (newProgress * duration).toInt()
player.seekTo(newPosition)
progress = newProgress
}
} catch (e: IllegalStateException) {
// MediaPlayer in invalid state, ignore
Log.w("VoiceMessagePreview", "MediaPlayer seek failed in onProgressChange", e)
}
}
}
}, },
) )
@@ -240,6 +172,115 @@ fun VoiceMessagePreview(
} }
} }
@Composable
private fun ManageMediaPlayer(
voiceMetadata: AudioMeta,
localFile: File?,
onCompletion: () -> Unit,
onPlayerChanged: (MediaPlayer?) -> Unit,
onRelease: () -> Unit,
) {
DisposableEffect(voiceMetadata.url, localFile) {
val player = createMediaPlayer(voiceMetadata.url, localFile)
player?.setOnCompletionListener { onCompletion() }
onPlayerChanged(player)
onDispose {
try {
player?.stop()
} catch (e: IllegalStateException) {
Log.d("VoiceMessagePreview", "MediaPlayer stop failed (already stopped)", e)
}
player?.release()
onPlayerChanged(null)
onRelease()
}
}
}
@Composable
private fun TrackPlaybackProgress(
mediaPlayer: MediaPlayer?,
isPlaying: Boolean,
onProgressUpdate: (Float) -> Unit,
onInvalidState: () -> Unit,
) {
LaunchedEffect(mediaPlayer, isPlaying) {
val player = mediaPlayer ?: return@LaunchedEffect
if (!isPlaying) return@LaunchedEffect
while (isActive) {
try {
if (!player.isPlaying) break
calculateProgress(player.currentPosition.toFloat(), player.duration.toFloat())?.let { onProgressUpdate(it) }
} catch (e: IllegalStateException) {
Log.w("VoiceMessagePreview", "MediaPlayer in invalid state during progress tracking", e)
onInvalidState()
break
}
delay(100)
}
}
}
private fun calculateProgress(
current: Float,
duration: Float,
): Float? {
val progress =
if (duration > 0 && current >= 0) {
(current / duration).coerceIn(0f, 1f)
} else {
0f
}
return progress.takeIf { it.isFinite() }
}
private fun handlePlayPauseClick(
mediaPlayer: MediaPlayer?,
isPlaying: Boolean,
progress: Float,
onProgressReset: () -> Unit,
onPlayingChanged: (Boolean) -> Unit,
) {
val player = mediaPlayer ?: return
try {
if (isPlaying) {
player.pause()
onPlayingChanged(false)
return
}
if (progress.isFinite() && progress >= 1f) {
player.seekTo(0)
onProgressReset()
}
player.start()
onPlayingChanged(true)
} catch (e: IllegalStateException) {
Log.w("VoiceMessagePreview", "MediaPlayer operation failed in onClick handler", e)
onPlayingChanged(false)
}
}
private fun handleWaveformScrub(
newProgress: Float,
mediaPlayer: MediaPlayer?,
onProgressChanged: (Float) -> Unit,
) {
if (!newProgress.isFinite() || newProgress < 0f || newProgress > 1f) return
val player = mediaPlayer ?: return
try {
val duration = player.duration
if (duration > 0) {
val newPosition = (newProgress * duration).toInt()
player.seekTo(newPosition)
onProgressChanged(newProgress)
}
} catch (e: IllegalStateException) {
Log.w("VoiceMessagePreview", "MediaPlayer seek failed in onProgressChange", e)
}
}
private fun createMediaPlayer( private fun createMediaPlayer(
url: String, url: String,
localFile: File?, localFile: File?,
@@ -972,7 +972,6 @@ open class ShortNotePostViewModel :
val uploadErrorTitle = stringRes(appContext, R.string.upload_error_title) val uploadErrorTitle = stringRes(appContext, R.string.upload_error_title)
val uploadVoiceNip95NotSupported = stringRes(appContext, R.string.upload_error_voice_message_nip95_not_supported) val uploadVoiceNip95NotSupported = stringRes(appContext, R.string.upload_error_voice_message_nip95_not_supported)
val uploadVoiceFailed = stringRes(appContext, R.string.upload_error_voice_message_failed) val uploadVoiceFailed = stringRes(appContext, R.string.upload_error_voice_message_failed)
val uploadVoiceUnexpected = stringRes(appContext, R.string.upload_error_voice_message_unexpected_state)
val uploadVoiceExceptionMessage: (String) -> String = { detail -> val uploadVoiceExceptionMessage: (String) -> String = { detail ->
stringRes(appContext, R.string.upload_error_voice_message_exception, detail) stringRes(appContext, R.string.upload_error_voice_message_exception, detail)
} }
@@ -30,8 +30,6 @@ import com.vitorpamplona.quartz.nip01Core.core.isAddressable
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.store.sqlite.sql.where import com.vitorpamplona.quartz.nip01Core.store.sqlite.sql.where
import com.vitorpamplona.quartz.utils.EventFactory import com.vitorpamplona.quartz.utils.EventFactory
import kotlin.collections.component1
import kotlin.collections.component2
class QueryBuilder( class QueryBuilder(
val fts: FullTextSearchModule, val fts: FullTextSearchModule,
@@ -24,7 +24,6 @@ import kotlinx.cinterop.ExperimentalForeignApi
import kotlinx.cinterop.refTo import kotlinx.cinterop.refTo
import platform.Security.SecRandomCopyBytes import platform.Security.SecRandomCopyBytes
import platform.Security.kSecRandomDefault import platform.Security.kSecRandomDefault
import kotlin.random.Random.Default.nextBytes
actual class SecureRandom { actual class SecureRandom {
actual fun nextInt(): Int { actual fun nextInt(): Int {
@@ -59,7 +59,6 @@ import com.vitorpamplona.quartz.nip47WalletConnect.jackson.ResponseDeserializer
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.jackson.RumorDeserializer import com.vitorpamplona.quartz.nip59Giftwrap.rumors.jackson.RumorDeserializer
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.jackson.RumorSerializer import com.vitorpamplona.quartz.nip59Giftwrap.rumors.jackson.RumorSerializer
import kotlinx.serialization.json.JsonNull.content
import java.io.InputStream import java.io.InputStream
class JacksonMapper { class JacksonMapper {