diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt index 1fc2400a2..dc49a2f49 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt @@ -49,7 +49,7 @@ class FileHeader( mimeType: String?, dimPrecomputed: String?, onReady: (FileHeader) -> Unit, - onError: (String?) -> Unit, + onError: (String) -> Unit, ) { try { val imageData: ByteArray? = ImageDownloader().waitAndGetImage(fileUrl) @@ -57,12 +57,12 @@ class FileHeader( if (imageData != null) { prepare(imageData, mimeType, dimPrecomputed, onReady, onError) } else { - onError(null) + onError("Unable to download image from $fileUrl") } } catch (e: Exception) { if (e is CancellationException) throw e Log.e("ImageDownload", "Couldn't download image from server: ${e.message}") - onError(e.message) + onError(e.message ?: e.javaClass.simpleName) } } @@ -71,7 +71,7 @@ class FileHeader( mimeType: String?, dimPrecomputed: String?, onReady: (FileHeader) -> Unit, - onError: (String?) -> Unit, + onError: (String) -> Unit, ) { try { val hash = CryptoUtils.sha256(data).toHexKey() @@ -178,7 +178,7 @@ class FileHeader( } catch (e: Exception) { if (e is CancellationException) throw e Log.e("ImageDownload", "Couldn't convert image in to File Header: ${e.message}") - onError(e.message) + onError(e.message ?: e.javaClass.simpleName) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt index 62ef237ca..cb351bcf3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostView.kt @@ -109,10 +109,8 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.amethyst.ui.theme.subtleBorder import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -135,12 +133,6 @@ fun EditPostView( LaunchedEffect(Unit) { postViewModel.load(edit, versionLookingAt, accountViewModel) - - launch(Dispatchers.IO) { - postViewModel.imageUploadingError.collect { error -> - withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } - } - } } DisposableEffect(Unit) { @@ -339,13 +331,13 @@ fun EditPostView( url, accountViewModel.account.defaultFileServer, onAdd = { alt, server, sensitiveContent -> - postViewModel.upload(url, alt, sensitiveContent, false, server, context) + postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context) if (!server.isNip95) { accountViewModel.account.changeDefaultFileServer(server.server) } }, onCancel = { postViewModel.contentToAddUrl = null }, - onError = { scope.launch { postViewModel.imageUploadingError.emit(it) } }, + onError = { scope.launch { Toast.makeText(context, context.resources.getText(it), Toast.LENGTH_SHORT).show() } }, accountViewModel = accountViewModel, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt index 53aa0a0e8..9f0c7d45f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.model.Account @@ -42,14 +43,13 @@ import com.vitorpamplona.amethyst.service.Nip96Uploader import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch @Stable @@ -68,8 +68,6 @@ open class EditPostViewModel : ViewModel() { var message by mutableStateOf(TextFieldValue("")) var urlPreview by mutableStateOf(null) var isUploadingImage by mutableStateOf(false) - val imageUploadingError = - MutableSharedFlow(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST) var userSuggestions by mutableStateOf>(emptyList()) var userSuggestionAnchor: TextRange? = null @@ -152,6 +150,7 @@ open class EditPostViewModel : ViewModel() { sensitiveContent: Boolean, isPrivate: Boolean = false, server: ServerOption, + onError: (String, String) -> Unit, context: Context, ) { isUploadingImage = true @@ -169,7 +168,16 @@ open class EditPostViewModel : ViewModel() { onReady = { fileUri, contentType, size -> if (server.isNip95) { contentResolver.openInputStream(fileUri)?.use { - createNIP95Record(it.readBytes(), contentType, alt, sensitiveContent) + createNIP95Record( + it.readBytes(), + contentType, + alt, + sensitiveContent, + onError = { + onError(stringRes(context, R.string.failed_to_upload_media_no_details), it) + }, + context, + ) } } else { viewModelScope.launch(Dispatchers.IO) { @@ -193,6 +201,10 @@ open class EditPostViewModel : ViewModel() { localContentType = contentType, alt = alt, sensitiveContent = sensitiveContent, + onError = { + onError(stringRes(context, R.string.failed_to_upload_media_no_details), it) + }, + context = context, ) } catch (e: Exception) { if (e is CancellationException) throw e @@ -202,16 +214,14 @@ open class EditPostViewModel : ViewModel() { e, ) isUploadingImage = false - viewModelScope.launch { - imageUploadingError.emit("Failed to upload: ${e.message}") - } + onError(stringRes(context, R.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) } } } }, onError = { isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit(it) } + onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it)) }, ) } @@ -306,6 +316,8 @@ open class EditPostViewModel : ViewModel() { localContentType: String?, alt: String?, sensitiveContent: Boolean, + onError: (String) -> Unit = {}, + context: Context, ) { // Images don't seem to be ready immediately after upload val imageUrl = uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "url" }?.get(1) @@ -334,7 +346,7 @@ open class EditPostViewModel : ViewModel() { Log.e("ImageDownload", "Couldn't download image from server") cancel() isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") } + onError(stringRes(context, R.string.server_did_not_provide_a_url_after_uploading)) return } @@ -353,7 +365,7 @@ open class EditPostViewModel : ViewModel() { }, onError = { isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") } + onError(stringRes(context, R.string.could_not_prepare_header, it)) }, ) } @@ -363,10 +375,12 @@ open class EditPostViewModel : ViewModel() { mimeType: String?, alt: String?, sensitiveContent: Boolean, + onError: (String) -> Unit = {}, + context: Context, ) { if (bytes.size > 80000) { viewModelScope.launch { - imageUploadingError.emit("Media is too big for NIP-95") + onError(stringRes(context, id = R.string.media_too_big_for_nip95)) isUploadingImage = false } return @@ -393,7 +407,7 @@ open class EditPostViewModel : ViewModel() { }, onError = { isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") } + onError(stringRes(context, R.string.could_not_prepare_header, it)) }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index d3cdcfa45..8aa79400e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -64,24 +64,22 @@ open class NewMediaModel : ViewModel() { var uploadingDescription = mutableStateOf(null) var onceUploaded: () -> Unit = {} - var onError: (String) -> Unit = {} open fun load( account: Account, uri: Uri, contentType: String?, - onError: (String) -> Unit, ) { this.account = account this.galleryUri = uri this.mediaType = contentType this.selectedServer = ServerOption(defaultServer(), false) - this.onError = onError } fun upload( context: Context, relayList: List? = null, + onError: (String) -> Unit = {}, ) { isUploadingImage = true @@ -110,6 +108,7 @@ open class NewMediaModel : ViewModel() { alt, sensitiveContent, relayList = relayList, + onError = onError, context, ) } @@ -148,6 +147,7 @@ open class NewMediaModel : ViewModel() { alt = alt, sensitiveContent = sensitiveContent, relayList = relayList, + onError = onError, context, ) } catch (e: Exception) { @@ -189,6 +189,7 @@ open class NewMediaModel : ViewModel() { alt: String, sensitiveContent: Boolean, relayList: List? = null, + onError: (String) -> Unit = {}, context: Context, ) { uploadingPercentage.value = 0.40f @@ -282,11 +283,12 @@ open class NewMediaModel : ViewModel() { alt: String, sensitiveContent: Boolean, relayList: List? = null, + onError: (String) -> Unit = {}, context: Context, ) { if (bytes.size > 80000) { viewModelScope.launch { - onError("Media is too big for NIP-95") + onError(stringRes(context, id = R.string.media_too_big_for_nip95)) isUploadingImage = false uploadingPercentage.value = 0.00f uploadingDescription.value = null diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt index a6576355e..a03baa8b1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaView.kt @@ -94,9 +94,7 @@ fun NewMediaView( LaunchedEffect(uri) { val mediaType = resolver.getType(uri) ?: "" - postViewModel.load(account, uri, mediaType) { - accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) - } + postViewModel.load(account, uri, mediaType) } var showRelaysDialog by remember { mutableStateOf(false) } @@ -161,7 +159,9 @@ fun NewMediaView( PostButton( onPost = { onClose() - postViewModel.upload(context, relayList) + postViewModel.upload(context, relayList) { + accountViewModel.toast(stringRes(context, R.string.failed_to_upload_media_no_details), it) + } postViewModel.selectedServer?.let { if (!it.isNip95) { account.changeDefaultFileServer(it.server) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 3601976ab..e7406e80e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -180,7 +180,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.lang.Math.round @OptIn(ExperimentalMaterial3Api::class, FlowPreview::class) @@ -220,10 +219,6 @@ fun NewPostView( LaunchedEffect(Unit) { launch(Dispatchers.IO) { postViewModel.load(accountViewModel, baseReplyTo, quote, fork, version, draft) - - postViewModel.imageUploadingError.collect { error -> - withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } - } } } @@ -498,13 +493,13 @@ fun NewPostView( url, accountViewModel.account.defaultFileServer, onAdd = { alt, server, sensitiveContent -> - postViewModel.upload(url, alt, sensitiveContent, false, server, context) + postViewModel.upload(url, alt, sensitiveContent, false, server, accountViewModel::toast, context) if (!server.isNip95) { accountViewModel.account.changeDefaultFileServer(server.server) } }, onCancel = { postViewModel.contentToAddUrl = null }, - onError = { scope.launch { postViewModel.imageUploadingError.emit(it) } }, + onError = { scope.launch { Toast.makeText(context, context.resources.getText(it), Toast.LENGTH_SHORT).show() } }, accountViewModel = accountViewModel, ) } @@ -1616,7 +1611,7 @@ fun ImageVideoDescription( defaultServer: Nip96MediaServers.ServerName, onAdd: (String, ServerOption, Boolean) -> Unit, onCancel: () -> Unit, - onError: (String) -> Unit, + onError: (Int) -> Unit, accountViewModel: AccountViewModel, ) { val resolver = LocalContext.current.contentResolver @@ -1753,7 +1748,7 @@ fun ImageVideoDescription( bitmap = resolver.loadThumbnail(uri, Size(1200, 1000), null) } catch (e: Exception) { if (e is CancellationException) throw e - onError("Unable to load thumbnail") + onError(R.string.unable_to_load_thumbnail) Log.w("NewPostView", "Couldn't create thumbnail, but the video can be uploaded", e) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index eedf5298e..9d6edbad1 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.text.input.TextFieldValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.fonfon.kgeohash.toGeoHash +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor import com.vitorpamplona.amethyst.commons.richtext.RichTextParser import com.vitorpamplona.amethyst.model.Account @@ -48,6 +49,7 @@ import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.Split import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.quartz.encoders.Hex import com.vitorpamplona.quartz.encoders.HexKey @@ -72,10 +74,8 @@ import com.vitorpamplona.quartz.events.findURLs import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -109,8 +109,6 @@ open class NewPostViewModel : ViewModel() { var message by mutableStateOf(TextFieldValue("")) var urlPreview by mutableStateOf(null) var isUploadingImage by mutableStateOf(false) - val imageUploadingError = - MutableSharedFlow(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST) var userSuggestions by mutableStateOf>(emptyList()) var userSuggestionAnchor: TextRange? = null @@ -768,6 +766,7 @@ open class NewPostViewModel : ViewModel() { sensitiveContent: Boolean, isPrivate: Boolean = false, server: ServerOption, + onError: (title: String, message: String) -> Unit, context: Context, ) { isUploadingImage = true @@ -785,7 +784,16 @@ open class NewPostViewModel : ViewModel() { onReady = { fileUri, contentType, size -> if (server.isNip95) { contentResolver.openInputStream(fileUri)?.use { - createNIP95Record(it.readBytes(), contentType, alt, sensitiveContent) + createNIP95Record( + it.readBytes(), + contentType, + alt, + sensitiveContent, + onError = { + onError(stringRes(context, R.string.failed_to_upload_media_no_details), it) + }, + context, + ) } } else { viewModelScope.launch(Dispatchers.IO) { @@ -809,6 +817,10 @@ open class NewPostViewModel : ViewModel() { localContentType = contentType, alt = alt, sensitiveContent = sensitiveContent, + onError = { + onError(stringRes(context, R.string.failed_to_upload_media_no_details), it) + }, + context = context, ) } catch (e: Exception) { if (e is CancellationException) throw e @@ -818,16 +830,14 @@ open class NewPostViewModel : ViewModel() { e, ) isUploadingImage = false - viewModelScope.launch { - imageUploadingError.emit("Failed to upload: ${e.message}") - } + onError(stringRes(context, R.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) } } } }, onError = { isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit(it) } + onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, it)) }, ) } @@ -1064,6 +1074,8 @@ open class NewPostViewModel : ViewModel() { localContentType: String?, alt: String?, sensitiveContent: Boolean, + onError: (message: String) -> Unit, + context: Context, ) { // Images don't seem to be ready immediately after upload val imageUrl = uploadingResult.tags?.firstOrNull { it.size > 1 && it[0] == "url" }?.get(1) @@ -1092,7 +1104,7 @@ open class NewPostViewModel : ViewModel() { Log.e("ImageDownload", "Couldn't download image from server") cancel() isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") } + onError(stringRes(context, R.string.server_did_not_provide_a_url_after_uploading)) return } @@ -1112,7 +1124,7 @@ open class NewPostViewModel : ViewModel() { }, onError = { isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") } + onError(stringRes(context, R.string.could_not_prepare_header, it)) }, ) } @@ -1126,10 +1138,12 @@ open class NewPostViewModel : ViewModel() { mimeType: String?, alt: String?, sensitiveContent: Boolean, + onError: (message: String) -> Unit, + context: Context, ) { if (bytes.size > 80000) { viewModelScope.launch { - imageUploadingError.emit("Media is too big for NIP-95") + onError(stringRes(context, id = R.string.media_too_big_for_nip95)) isUploadingImage = false } return @@ -1157,7 +1171,7 @@ open class NewPostViewModel : ViewModel() { }, onError = { isUploadingImage = false - viewModelScope.launch { imageUploadingError.emit("Failed to upload the image / video") } + onError(stringRes(context, R.string.could_not_prepare_header, it)) }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt index e4dbc5a8f..450ef6178 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataView.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.actions -import android.widget.Toast import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -46,29 +45,20 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R -import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.placeholderText -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @Composable fun NewUserMetadataView( onClose: () -> Unit, - account: Account, + accountViewModel: AccountViewModel, ) { val postViewModel: NewUserMetadataViewModel = viewModel() val context = LocalContext.current LaunchedEffect(Unit) { - postViewModel.load(account) - - launch(Dispatchers.IO) { - postViewModel.imageUploadingError.collect { error -> - withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } - } - } + postViewModel.load(accountViewModel.account) } Dialog( @@ -164,7 +154,7 @@ fun NewUserMetadataView( tint = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(start = 5.dp), ) { - postViewModel.uploadForPicture(it, context) + postViewModel.uploadForPicture(it, context, onError = accountViewModel::toast) } }, singleLine = true, @@ -189,7 +179,7 @@ fun NewUserMetadataView( tint = MaterialTheme.colorScheme.placeholderText, modifier = Modifier.padding(start = 5.dp), ) { - postViewModel.uploadForBanner(it, context) + postViewModel.uploadForBanner(it, context, onError = accountViewModel::toast) } }, singleLine = true, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt index 93fac1474..acc5284dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewUserMetadataViewModel.kt @@ -27,15 +27,15 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.service.Nip96Uploader import com.vitorpamplona.amethyst.ui.components.MediaCompressor +import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.events.GitHubIdentity import com.vitorpamplona.quartz.events.MastodonIdentity import com.vitorpamplona.quartz.events.TwitterIdentity import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.BufferOverflow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.launch import kotlin.coroutines.cancellation.CancellationException @@ -60,8 +60,6 @@ class NewUserMetadataViewModel : ViewModel() { var isUploadingImageForPicture by mutableStateOf(false) var isUploadingImageForBanner by mutableStateOf(false) - val imageUploadingError = - MutableSharedFlow(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST) fun load(account: Account) { this.account = account @@ -130,6 +128,7 @@ class NewUserMetadataViewModel : ViewModel() { fun uploadForPicture( uri: Uri, context: Context, + onError: (String, String) -> Unit, ) { viewModelScope.launch(Dispatchers.IO) { upload( @@ -137,6 +136,7 @@ class NewUserMetadataViewModel : ViewModel() { context, onUploading = { isUploadingImageForPicture = it }, onUploaded = { picture.value = it }, + onError = onError, ) } } @@ -144,6 +144,7 @@ class NewUserMetadataViewModel : ViewModel() { fun uploadForBanner( uri: Uri, context: Context, + onError: (String, String) -> Unit, ) { viewModelScope.launch(Dispatchers.IO) { upload( @@ -151,6 +152,7 @@ class NewUserMetadataViewModel : ViewModel() { context, onUploading = { isUploadingImageForBanner = it }, onUploaded = { banner.value = it }, + onError = onError, ) } } @@ -160,6 +162,7 @@ class NewUserMetadataViewModel : ViewModel() { context: Context, onUploading: (Boolean) -> Unit, onUploaded: (String) -> Unit, + onError: (String, String) -> Unit, ) { onUploading(true) @@ -194,22 +197,19 @@ class NewUserMetadataViewModel : ViewModel() { onUploaded(url) } else { onUploading(false) - viewModelScope.launch { - imageUploadingError.emit("Failed to upload the image / video") - } + onError(stringRes(context, R.string.failed_to_upload_media_no_details), stringRes(context, R.string.server_did_not_provide_a_url_after_uploading)) } } catch (e: Exception) { if (e is CancellationException) throw e onUploading(false) - viewModelScope.launch { - imageUploadingError.emit("Failed to upload the image / video") - } + onError(stringRes(context, R.string.failed_to_upload_media_no_details), e.message ?: e.javaClass.simpleName) } } }, onError = { onUploading(false) - viewModelScope.launch { imageUploadingError.emit(it) } + + onError(stringRes(context, R.string.error_when_compressing_media), stringRes(context, it)) }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt index d67630b2a..526fd4730 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/MediaCompressor.kt @@ -30,6 +30,7 @@ import com.abedelazizshe.lightcompressorlibrary.VideoCompressor import com.abedelazizshe.lightcompressorlibrary.VideoQuality import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration import com.abedelazizshe.lightcompressorlibrary.config.Configuration +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.checkNotInMainThread import id.zelory.compressor.Compressor import id.zelory.compressor.constraint.default @@ -44,7 +45,7 @@ class MediaCompressor { contentType: String?, applicationContext: Context, onReady: (Uri, String?, Long?) -> Unit, - onError: (String) -> Unit, + onError: (Int) -> Unit, ) { checkNotInMainThread() @@ -87,7 +88,7 @@ class MediaCompressor { if (path != null) { onReady(Uri.fromFile(File(path)), contentType, size) } else { - onError("Compression Returned null") + onError(R.string.compression_returned_null) } } @@ -100,7 +101,7 @@ class MediaCompressor { } override fun onCancelled(index: Int) { - onError("Compression Cancelled") + onError(R.string.compression_cancelled) } }, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 4c9d7afa9..d5aa8ce7b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -114,7 +114,6 @@ import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.richtext.RichTextParser -import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.LocalCache @@ -866,7 +865,7 @@ private fun ProfileActions( remember(accountViewModel) { derivedStateOf { accountViewModel.userProfile() == baseUser } } if (isMe) { - EditButton(accountViewModel.account) + EditButton(accountViewModel) } WatchIsHiddenUser(baseUser, accountViewModel) { isHidden -> @@ -1865,11 +1864,11 @@ private fun MessageButton( } @Composable -private fun EditButton(account: Account) { +private fun EditButton(accountViewModel: AccountViewModel) { var wantsToEdit by remember { mutableStateOf(false) } if (wantsToEdit) { - NewUserMetadataView({ wantsToEdit = false }, account) + NewUserMetadataView({ wantsToEdit = false }, accountViewModel) } InnerEditButton { wantsToEdit = true } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt index 9f97c2997..b87ef93cf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms -import android.widget.Toast import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -175,7 +174,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Date @@ -242,14 +240,6 @@ fun ChannelScreen( val lifeCycleOwner = LocalLifecycleOwner.current - LaunchedEffect(Unit) { - launch(Dispatchers.IO) { - newPostModel.imageUploadingError.collect { error -> - withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } - } - } - } - DisposableEffect(accountViewModel) { NostrChannelDataSource.loadMessagesBetween(accountViewModel.account, channel) NostrChannelDataSource.start() @@ -478,6 +468,7 @@ fun EditFieldRow( alt = null, sensitiveContent = false, server = ServerOption(accountViewModel.account.defaultFileServer, false), + onError = accountViewModel::toast, context = context, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt index 39e59aa59..6c1dcaf8b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms -import android.widget.Toast import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -129,7 +128,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext @Composable fun ChatroomScreen( @@ -275,14 +273,6 @@ fun ChatroomScreen( val lifeCycleOwner = LocalLifecycleOwner.current - LaunchedEffect(room, accountViewModel) { - launch(Dispatchers.IO) { - newPostModel.imageUploadingError.collect { error -> - withContext(Dispatchers.Main) { Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } - } - } - } - DisposableEffect(room, accountViewModel) { NostrChatroomDataSource.loadMessagesBetween(accountViewModel.account, room) NostrChatroomDataSource.start() @@ -458,6 +448,7 @@ fun PrivateMessageEditFieldRow( sensitiveContent = false, isPrivate = isPrivate, server = ServerOption(accountViewModel.account.defaultFileServer, false), + onError = accountViewModel::toast, context = context, ) } diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index ae003f70f..61897e99d 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -779,6 +779,12 @@ Could not prepare local file to upload: %1$s Failed to upload: %1$s Failed to delete: %1$s + Media is too big for NIP-95 + Unable to load thumbnail + + Could not prepare header information: %1$s + Compression Cancelled + Compression failed to return a file Edit draft