diff --git a/README.md b/README.md index f8cbbdf46..7d847181e 100644 --- a/README.md +++ b/README.md @@ -242,16 +242,16 @@ openssl base64 < | tr -d '\n' | tee some_signing_key.j Add the following line to your `commonMain` dependencies: ```gradle -implementation('com.github.vitorpamplona.amethyst:quartz:') +implementation('com.vitorpamplona.quartz:quartz:') ``` Variations to each platform are also available: ```gradle -implementation('com.github.vitorpamplona.amethyst:quartz-android:') -implementation('com.github.vitorpamplona.amethyst:quartz-jvm:') -implementation('com.github.vitorpamplona.amethyst:quartz-iosarm64:') -implementation('com.github.vitorpamplona.amethyst:quartz-iossimulatorarm64:') +implementation('com.vitorpamplona.quartz:quartz-android:') +implementation('com.vitorpamplona.quartz:quartz-jvm:') +implementation('com.vitorpamplona.quartz:quartz-iosarm64:') +implementation('com.vitorpamplona.quartz:quartz-iossimulatorarm64:') ``` ### How to use @@ -259,25 +259,25 @@ implementation('com.github.vitorpamplona.amethyst:quartz-iossimulatorarm64:")) +val keyPair = KeyPair() // creates a random key +val keyPair = KeyPair("hex...".hexToByteArray()) +val keyPair = KeyPair("nsec1...".bechToBytes()) +val keyPair = KeyPair(Nip06().privateKeyFromMnemonic("")) val readOnly = KeyPair(pubKey = "hex...".hexToByteArray()) val readOnly = KeyPair(pubKey = "npub1...".bechToBytes()) ``` -Create signers that can be internal, when you have the private key or when it is a read-only user -or external, when it is controlled by Amber in NIP-55 +Create signers that can be Internal, when you have the private key or a read-only public key, +or External, when it is controlled by Amber in NIP-55. -the `NostrSignerInternal` and `NostrSignerExternal` classes. +Use either the `NostrSignerInternal` or `NostrSignerExternal` class: ```kt val signer = NostrSignerInternal(keyPair) val amberSigner = NostrSignerExternal( pubKey = keyPair.pubKey.toHexKey(), - packageName = signerPackageName, - contentResolver = appContext.contentResolver, + packageName = signerPackageName, // Amber package name + contentResolver = appContext.contentResolver, ) ``` @@ -299,16 +299,19 @@ val client = NostrClient(socketBuilder, appScope) If you want to auth, given a logged-in `signer`: ```kt -val authCoordinator = RelayAuthenticator(client, applicationIOScope) { challenge, relay -> - val authedEvent = RelayAuthEvent.create(relayUrl, challenge, signer) +val authCoordinator = RelayAuthenticator(client, appScope) { challenge, relay -> + val authedEvent = RelayAuthEvent.create(relay.url, challenge, signer) client.sendIfExists(authedEvent, relay.url) } ``` -To manage subscriptions, the suggested approach is to use subscriptions in the Application class. +To manage subscriptions, the simplest approach is to build mutable subscriptions in +the Application class. To use the best of the outbox model, this class allows you to +build filters for as many relays as needed. The `NostrClient` will connect to the +complete set of relays for all subscriptions. ```kt -val metadataSub = RelayClientSubscription( +val metadataSub = NostrClientSubscription( client = client, filter = { val filters = listOf( @@ -330,6 +333,14 @@ val metadataSub = RelayClientSubscription( } ``` +In that way, you can simply call `metadataSub.updateFilter()` when you need to update +subscriptions to all relays. Or call `metadataSub.closeSubscription()` to stop the sub +without deleting it. + +When your app goes to the background, you can use NostrClient's `connect` and `disconnect` +methods to stop all communication to relays. Add the `connect` to your `onResume` and `disconnect` +to `onPause` methods. + ## Contributing Issues can be logged on: [https://gitworkshop.dev/repo/amethyst](https://gitworkshop.dev/repo/amethyst) diff --git a/amethyst/build.gradle b/amethyst/build.gradle index e700680f7..2845df088 100644 --- a/amethyst/build.gradle +++ b/amethyst/build.gradle @@ -151,6 +151,12 @@ android { signingConfig = signingConfigs.debug } } + // TODO: remove this when lightcompressor uses one MP4 parser only + packaging { + resources { + resources.pickFirsts.add('builddef.lst') + } + } flavorDimensions = ["channel"] diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index 96b3adfb6..f888022cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -118,7 +118,6 @@ import com.vitorpamplona.quartz.experimental.profileGallery.mimeType import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.core.value import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt index 2da58f601..77bcab70f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/user/UserObservers.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.user import android.annotation.SuppressLint import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.model.Account diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt index 0b506c4dc..ee7f51d97 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MediaCompressor.kt @@ -45,6 +45,7 @@ class MediaCompressor { contentType: String?, mediaQuality: CompressorQuality, applicationContext: Context, + useH265: Boolean = false, ): MediaCompressorResult { // Skip compression if user selected uncompressed if (mediaQuality == CompressorQuality.UNCOMPRESSED) { @@ -57,7 +58,7 @@ class MediaCompressor { // branch into compression based on content type return when { contentType?.startsWith("video", ignoreCase = true) == true -> { - VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality) + VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality, useH265) } contentType?.startsWith("image", ignoreCase = true) == true && !contentType.contains("gif") && diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt index 45d8269e2..207810b1c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/MultiOrchestrator.kt @@ -46,6 +46,8 @@ class MultiOrchestrator( fun first() = list.first() + fun hasVideo() = list.any { it.media.mimeType?.startsWith("video", ignoreCase = true) == true } + suspend fun upload( alt: String?, contentWarningReason: String?, @@ -53,6 +55,7 @@ class MultiOrchestrator( server: ServerName, account: Account, context: Context, + useH265: Boolean = false, ): Result { coroutineScope { val jobs = @@ -67,6 +70,7 @@ class MultiOrchestrator( server, account, context, + useH265, ) } } @@ -85,6 +89,7 @@ class MultiOrchestrator( server: ServerName, account: Account, context: Context, + useH265: Boolean = false, ): Result { coroutineScope { val jobs = @@ -100,6 +105,7 @@ class MultiOrchestrator( server, account, context, + useH265, ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt index dd011f74f..42a763dee 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt @@ -288,9 +288,10 @@ class UploadOrchestrator { mimeType: String?, compressionQuality: CompressorQuality, context: Context, + useH265: Boolean = false, ) = if (compressionQuality != CompressorQuality.UNCOMPRESSED) { updateState(0.02, UploadingState.Compressing) - MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext) + MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext, useH265) } else { MediaCompressorResult(uri, mimeType, null) } @@ -304,8 +305,9 @@ class UploadOrchestrator { server: ServerName, account: Account, context: Context, + useH265: Boolean = false, ): UploadingFinalState { - val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context) + val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265) return when (server.type) { ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context) @@ -324,8 +326,9 @@ class UploadOrchestrator { server: ServerName, account: Account, context: Context, + useH265: Boolean = false, ): UploadingFinalState { - val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context) + val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265) val encrypted = EncryptFiles().encryptFile(context, compressed.uri, encrypt) return when (server.type) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt index 6f6b929df..2369431f7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt @@ -29,6 +29,7 @@ import android.text.format.Formatter.formatFileSize import android.util.Log import android.widget.Toast import com.abedelazizshe.lightcompressorlibrary.CompressionListener +import com.abedelazizshe.lightcompressorlibrary.VideoCodec import com.abedelazizshe.lightcompressorlibrary.VideoCompressor import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration import com.abedelazizshe.lightcompressorlibrary.config.Configuration @@ -88,11 +89,18 @@ data class CompressionRule( val bitrateMbps: Float, val description: String, ) { - fun getBitrateBps(framerate: Float): Int { - // Apply 1.5x multiplier for 60fps+ videos - val multiplier = if (framerate >= 60f) 1.5f else 1.0f + fun getBitrateBps( + framerate: Float, + useH265: Boolean, + ): Int { + // Apply 1.3x multiplier for 60fps+ videos, 0.7x multiplier for H265 + val framerateMultiplier = if (framerate >= 60f) 1.3f else 1.0f + val codecMultiplier = if (useH265) 0.7f else 1.0f + val finalMultiplier = framerateMultiplier * codecMultiplier - return (bitrateMbps * multiplier * MBPS_TO_BPS_MULTIPLIER).toInt() + Log.d("VideoCompressionHelper", "framerate: $framerate, useH265: $useH265, Bitrate multiplier: $finalMultiplier") + + return (bitrateMbps * finalMultiplier * MBPS_TO_BPS_MULTIPLIER).toInt() } } @@ -141,6 +149,7 @@ object VideoCompressionHelper { contentType: String?, applicationContext: Context, mediaQuality: CompressorQuality, + useH265: Boolean, timeoutMs: Long = 60_000L, // configurable, default 60s ): MediaCompressorResult { val videoInfo = getVideoInfo(uri, applicationContext) @@ -152,8 +161,8 @@ object VideoCompressionHelper { .getValue(mediaQuality) .getValue(info.resolution.getStandard()) - val bitrateBps = rule.getBitrateBps(info.framerate) - Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps.") + val bitrateBps = rule.getBitrateBps(info.framerate, useH265) + Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps useH265=$useH265.") Log.d( LOG_TAG, @@ -186,6 +195,7 @@ object VideoCompressionHelper { resizer = resizer, videoNames = listOf(UUID.randomUUID().toString()), isMinBitrateCheckEnabled = false, + videoCodec = if (useH265) VideoCodec.H265 else VideoCodec.H264, ), listener = object : CompressionListener { 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 66f922c90..430737ac9 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 @@ -54,7 +54,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.CenterVertically import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -255,13 +254,13 @@ fun EditPostView( postViewModel.multiOrchestrator?.let { Row( - verticalAlignment = Alignment.CenterVertically, + verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), ) { ImageVideoDescription( it, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> + onAdd = { alt, server, sensitiveContent, mediaQuality, _ -> postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context) if (server.type != ServerType.NIP95) { accountViewModel.account.settings.changeDefaultFileServer(server) @@ -279,7 +278,7 @@ fun EditPostView( if (lud16 != null && postViewModel.wantsInvoice) { Row( - verticalAlignment = Alignment.CenterVertically, + verticalAlignment = CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp), ) { InvoiceRequest( 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 9f261fe9e..da02defbc 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 @@ -87,6 +87,9 @@ open class EditPostViewModel : ViewModel() { // Images and Videos var multiOrchestrator by mutableStateOf(null) + // Codec selection: false = H264, true = H265 + var useH265Codec by mutableStateOf(false) + // Invoices var canAddInvoice by mutableStateOf(false) var wantsInvoice by mutableStateOf(false) @@ -201,6 +204,7 @@ open class EditPostViewModel : ViewModel() { server, myAccount, context, + useH265Codec, ) if (results.allGood) { 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 630f52573..b384c5503 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 @@ -62,6 +62,9 @@ open class NewMediaModel : ViewModel() { // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED var mediaQualitySlider by mutableIntStateOf(1) + // Codec selection: false = H264, true = H265 + var useH265Codec by mutableStateOf(false) + open fun load( account: Account, uris: ImmutableList, @@ -111,6 +114,7 @@ open class NewMediaModel : ViewModel() { serverToUse, myAccount, context, + useH265Codec, ) if (results.allGood) { 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 899c3c59c..1af6109fb 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 @@ -261,4 +261,18 @@ fun ImageVideoPost( steps = 2, ) } + + // Only show H.265 codec option if there are videos in the upload + if (postViewModel.multiOrchestrator?.hasVideo() == true) { + SettingSwitchItem( + title = R.string.video_codec_h265_label, + description = R.string.video_codec_h265_description, + modifier = + Modifier + .fillMaxWidth() + .padding(top = 8.dp), + checked = postViewModel.useH265Codec, + onCheckedChange = { postViewModel.useH265Codec = it }, + ) + } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/SettingSwitchItem.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/SettingSwitchItem.kt index c0b6232d7..feaf98419 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/SettingSwitchItem.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/contentWarning/SettingSwitchItem.kt @@ -75,7 +75,7 @@ fun SettingSwitchItem( text = stringRes(id = description), style = MaterialTheme.typography.bodySmall, color = Color.Gray, - maxLines = 2, + maxLines = 3, overflow = TextOverflow.Ellipsis, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt index 0d3b1052a..84e445182 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/creators/uploads/ImageVideoDescription.kt @@ -78,7 +78,7 @@ import kotlinx.collections.immutable.toImmutableList fun ImageVideoDescription( uris: MultiOrchestrator, defaultServer: ServerName, - onAdd: (String, ServerName, Boolean, Int) -> Unit, + onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit, onDelete: (SelectedMediaProcessing) -> Unit, onCancel: () -> Unit, accountViewModel: AccountViewModel, @@ -91,7 +91,7 @@ fun ImageVideoDescription( uris: MultiOrchestrator, defaultServer: ServerName, includeNIP95: Boolean, - onAdd: (String, ServerName, Boolean, Int) -> Unit, + onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit, onDelete: (SelectedMediaProcessing) -> Unit, onCancel: () -> Unit, accountViewModel: AccountViewModel, @@ -128,6 +128,9 @@ fun ImageVideoDescription( // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED var mediaQualitySlider by remember { mutableIntStateOf(1) } + // Codec selection: false = H264, true = H265 + var useH265Codec by remember { mutableStateOf(false) } + Column( modifier = Modifier @@ -294,32 +297,40 @@ fun ImageVideoDescription( } } - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth(), - ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Box(modifier = Modifier.fillMaxWidth()) { - Text( - text = - when (mediaQualitySlider) { - 0 -> stringRes(R.string.media_compression_quality_low) - 1 -> stringRes(R.string.media_compression_quality_medium) - 2 -> stringRes(R.string.media_compression_quality_high) - 3 -> stringRes(R.string.media_compression_quality_uncompressed) - else -> stringRes(R.string.media_compression_quality_medium) - }, - modifier = Modifier.align(Alignment.Center), - ) - } - - Slider( - value = mediaQualitySlider.toFloat(), - onValueChange = { mediaQualitySlider = it.toInt() }, - valueRange = 0f..3f, - steps = 2, + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Box(modifier = Modifier.fillMaxWidth()) { + Text( + text = + when (mediaQualitySlider) { + 0 -> stringRes(R.string.media_compression_quality_low) + 1 -> stringRes(R.string.media_compression_quality_medium) + 2 -> stringRes(R.string.media_compression_quality_high) + 3 -> stringRes(R.string.media_compression_quality_uncompressed) + else -> stringRes(R.string.media_compression_quality_medium) + }, + modifier = Modifier.align(Alignment.Center), ) } + + Slider( + value = mediaQualitySlider.toFloat(), + onValueChange = { mediaQualitySlider = it.toInt() }, + valueRange = 0f..3f, + steps = 2, + ) + } + + if (uris.first().media.isVideo() == true) { + SettingSwitchItem( + title = R.string.video_codec_h265_label, + description = R.string.video_codec_h265_description, + modifier = + Modifier + .fillMaxWidth() + .padding(top = 8.dp), + checked = useH265Codec, + onCheckedChange = { useH265Codec = it }, + ) } Button( @@ -327,7 +338,7 @@ fun ImageVideoDescription( Modifier .fillMaxWidth() .padding(vertical = 10.dp), - onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) }, + onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider, useH265Codec) }, shape = QuoteBorder, colors = ButtonDefaults.buttonColors( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index c692fc600..e53d66b46 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -285,7 +285,7 @@ private fun GenericCommentPostBody( ImageVideoDescription( it, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> + onAdd = { alt, server, sensitiveContent, mediaQuality, _ -> postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) if (server.type != ServerType.NIP95) { accountViewModel.account.settings.changeDefaultFileServer(server) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index f339704eb..cb2c7965e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -278,7 +278,7 @@ fun GroupDMScreenContent( ImageVideoDescription( selectedFiles, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> + onAdd = { alt, server, sensitiveContent, mediaQuality, _ -> postViewModel.uploadAndHold( accountViewModel.toastManager::toast, context, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index f12efbad8..b0c6b8823 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -266,7 +266,7 @@ private fun NewProductBody( uris = it, defaultServer = accountViewModel.account.settings.defaultFileServer, includeNIP95 = false, - onAdd = { alt, server, sensitiveContent, mediaQuality -> + onAdd = { alt, server, sensitiveContent, mediaQuality, _ -> postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) if (server.type != ServerType.NIP95) { accountViewModel.account.settings.changeDefaultFileServer(server) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index 1929fc017..7d5df8868 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -326,8 +326,8 @@ private fun NewPostScreenBody( ImageVideoDescription( it, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> - postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) + onAdd = { alt, server, sensitiveContent, mediaQuality, useH265 -> + postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context, useH265) if (server.type != ServerType.NIP95) { accountViewModel.account.settings.changeDefaultFileServer(server) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index d9d05bb3a..847214bca 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -624,8 +624,9 @@ open class ShortNotePostViewModel : server: ServerName, onError: (title: String, message: String) -> Unit, context: Context, + useH265: Boolean, ) = try { - uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context) + uploadUnsafe(alt, contentWarningReason, mediaQuality, server, onError, context, useH265) } catch (_: SignerExceptions.ReadOnlyException) { onError( stringRes(context, R.string.read_only_user), @@ -640,6 +641,7 @@ open class ShortNotePostViewModel : server: ServerName, onError: (title: String, message: String) -> Unit, context: Context, + useH265: Boolean, ) { viewModelScope.launch(Dispatchers.Default) { val myMultiOrchestrator = multiOrchestrator ?: return@launch @@ -654,6 +656,7 @@ open class ShortNotePostViewModel : server, account, context, + useH265, ) if (results.allGood) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index 09d47edbb..a4d3100cd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -231,7 +231,7 @@ fun PublicMessageScreenContent( ImageVideoDescription( it, accountViewModel.account.settings.defaultFileServer, - onAdd = { alt, server, sensitiveContent, mediaQuality -> + onAdd = { alt, server, sensitiveContent, mediaQuality, _ -> postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) if (server.type != ServerType.NIP95) { accountViewModel.account.settings.changeDefaultFileServer(server) diff --git a/amethyst/src/main/res/values-cs/strings.xml b/amethyst/src/main/res/values-cs/strings.xml index 7d48a433e..1d68921db 100644 --- a/amethyst/src/main/res/values-cs/strings.xml +++ b/amethyst/src/main/res/values-cs/strings.xml @@ -952,4 +952,35 @@ Pokyny Moderátoři Otevřít rozbalovací nabídku + Sady sledování + Označené záložky + Obecné záložky + Veřejné + Soukromé + Smíšené + Zdá se, že zatím nemáte žádné sady sledování.\nKlepněte níže pro obnovení nebo použijte tlačítko přidat k vytvoření nové. + Přidat autora do sady sledování + Přidat nebo odebrat uživatele ze seznamů, nebo vytvořit nový seznam s tímto uživatelem. + Ikona pro seznam %1$s + %1$s je v tomto seznamu + %1$s není v tomto seznamu + Vaše sady sledování + Nebyly nalezeny žádné sady sledování, nebo žádné nemáte. Klepněte níže pro obnovení nebo použijte menu pro vytvoření nové. + Došlo k problému při načítání: %1$s + Vytvořit nový seznam + Vytvořit nový seznam %1$s s uživatelem + Vytvoří %1$s sadu sledování a přidá do ní %2$s. + Nový seznam %1$s + Název sady + Popis sady (volitelné) + Vytvořit sadu + Přejmenovat sadu + Přejmenováváte z + na.. + Přejmenovat + Událost nemá dostatek informací pro vytvoření magnet odkazu + Moje seznamy/sady + Vybrat podepisovatele + Použít kodek H.265/HEVC + Lepší kvalita při menší velikosti souboru, ale ne všechna zařízení podporují přehrávání H.265. diff --git a/amethyst/src/main/res/values-de/strings.xml b/amethyst/src/main/res/values-de/strings.xml index 6476be0e7..4d90c98c9 100644 --- a/amethyst/src/main/res/values-de/strings.xml +++ b/amethyst/src/main/res/values-de/strings.xml @@ -992,4 +992,35 @@ anz der Bedingungen ist erforderlich Richtlinien Moderatoren Dropdown-Menü öffnen + Folge-Sets + Markierte Lesezeichen + Allgemeine Lesezeichen + Öffentlich + Privat + Gemischt + Es scheint, dass du noch keine Folge-Sets hast.\nTippe unten zum Aktualisieren oder verwende die Plus-Taste, um ein neues zu erstellen. + Autor zum Folge-Set hinzufügen + Benutzer zu Listen hinzufügen oder entfernen, oder eine neue Liste mit diesem Benutzer erstellen. + Symbol für %1$s-Liste + %1$s ist in dieser Liste + %1$s ist nicht in dieser Liste + Deine Folge-Sets + Keine Folge-Sets gefunden oder du hast keine. Tippe unten zum Aktualisieren oder verwende das Menü, um eines zu erstellen. + Beim Abrufen ist ein Problem aufgetreten: %1$s + Neue Liste erstellen + Neue %1$s-Liste mit Benutzer erstellen + Erstellt ein %1$s-Folge-Set und fügt %2$s hinzu. + Neue %1$s-Liste + Set-Name + Set-Beschreibung (optional) + Set erstellen + Set umbenennen + Du benennst um von + zu.. + Umbenennen + Das Ereignis enthält nicht genügend Informationen, um einen Magnetlink zu erstellen + Meine Listen/Sets + Signierer auswählen + H.265/HEVC-Codec verwenden + Bessere Qualität bei kleinerer Dateigröße, aber nicht alle Geräte unterstützen die H.265-Wiedergabe. diff --git a/amethyst/src/main/res/values-hi-rIN/strings.xml b/amethyst/src/main/res/values-hi-rIN/strings.xml index 8e6ffcb6a..e1db6d5b0 100644 --- a/amethyst/src/main/res/values-hi-rIN/strings.xml +++ b/amethyst/src/main/res/values-hi-rIN/strings.xml @@ -1065,4 +1065,5 @@ क्या आप निकटकालिक क्रमदोष सूचनापत्र एक सीधे सन्देश में अमेथिस्ट को भेजना चाहते हैं। कोई व्यक्तिगत जानकारी बाँटी नहीं जाएगी भेजें यह सन्देश %1$d दिनों में अदृश्य हो जाएगा + हस्ताक्षरकर्ता चुनें diff --git a/amethyst/src/main/res/values-hu-rHU/strings.xml b/amethyst/src/main/res/values-hu-rHU/strings.xml index a242f754a..836aa6bba 100644 --- a/amethyst/src/main/res/values-hu-rHU/strings.xml +++ b/amethyst/src/main/res/values-hu-rHU/strings.xml @@ -112,6 +112,7 @@ Közzététel Mentés Létrehozás + Átnevezés Mégse Nem sikerült feltölteni a képet Atjátszó címe @@ -140,12 +141,12 @@ LN-cím LN-webcím (elavult) Mentés a galériába - Kép elmentve a képgalériába + Kép mentve a képgalériába A videó letöltése megkezdődött… A média letöltése megkezdődött… Nem sikerült menteni a képet - Videó elmentve a videógalériába - Nem sikerült elmenteni a videót + Videó mentve a videógalériába + Nem sikerült menteni a videót Kép feltöltése Kép készítése Videó rögzítése @@ -445,6 +446,33 @@ Közelben lévők bejegyzései Globális Némítottak bejegyzései + Követési gyüjtemények + Címkézett könyvjelzők + Általános könyvjelzők + Nyílvános + Privát + Kevert + Úgy tűnik, hogy még egyetlen gyüjteményt sem követ. + \nKoppintson a frissítéshez vagy érintse meg a hozzáadás gombot a gyüjtemény létrehozásához. + + Szerző hozzáadása a követési gyűjteményhez + Felhasználó hozzáadása vagy eltávolítása a listákból, vagy új lista létrehozása ezzel a felhasználóval. + Ikon a(z) %1$s nevű listához + "A(z) %1$s már a létezik a listában" + "A(z) %1$s nincs a listában" + Saját követési gyüjtemények + Nem találhatók követési gyüjtemények, vagy nincs követési gyüjteménye. Érintse meg az alábbi gombot a frissítéshez vagy használja a menüt egy gyüjtemény létrehozásához. + Probléma történt a következő lekérdezésekor: %1$s + Új lista létrehozása + Új %1$s lista létrehozása felhasználóval + %1$s követési gyüjtemény létrehozása, és hozzáadás a következhöz: %2$s. + Új %1$s lista + Név megadása + Leírás megadása (nem kötelező) + Gyüjtemény létrehozása + Gyüjtemény átnevezése + Ön átnevezi a követési gyüjteményt erről: + erre: Alapértelmezett port: 9050 ## Kapcsolódás a TORon keresztül az Orbot segítségével \n\n1. Telepítse az [Orbotot](https://play.google.com/store/apps/details?id=org.torproject.android) @@ -1014,6 +1042,8 @@ Letöltés Nem sikerült megnyitni a fájlt A fájl megnyitásához és letöltéséhez nincsenek torrent-alkalmazások telepítve. + Az esemény nem tartalmaz elegendő információt a mágneshivatkozás létrehozásához + Saját lista/gyüjtemény Lista kiválasztása a hírfolyam szűréséhez Kijelentkeztetés az eszköz zárolása esetén Privát üzenet @@ -1035,4 +1065,5 @@ Szeretné elküldeni a legutóbbi összeomlási jelentést az Amethystnek egy közvetlen üzenetben? A személyes adatait nem osztja meg Küldés Ez az üzenet %1$d nap múlva eltűnik + Aláíró kiválasztása diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml index 82fe2b56d..9e145e1a3 100644 --- a/amethyst/src/main/res/values-pl-rPL/strings.xml +++ b/amethyst/src/main/res/values-pl-rPL/strings.xml @@ -112,6 +112,7 @@ Wyślij Zapisz Utwórz + Zmień nazwę Anuluj Nie udało się przesłać obrazu Adres transmitera @@ -442,6 +443,33 @@ W pobliżu Wszystkie Zablokowane + Zbiory obserwowanych + Oznaczone zakładki + Ogólne zakładki + Publiczna + Prywatna + Mieszana + Wygląda na to, że nie masz jeszcze żadnych zbiorów obserwowanych. + \nDotknij poniżej, aby odświeżyć, lub naciśnij przycisk Dodaj, aby utworzyć nowy. + + Dodaj autora do zbioru obserwowanych + Dodaj lub usuń użytkownika z list, lub utwórz nową listę z tym użytkownikiem. + Ikona dla listy %1$s + "%1$s jest obecny na liście" + "%1$s nie jest na liście" + Twój zbiór obserwowanych + Nie znaleziono zbiorów obserwowanych lub nie masz żadnych zbiorów obserwowanych. Dotknij poniżej, aby odświeżyć lub użyj menu, aby go utworzyć. + Podczas pobierania wystąpił błąd: %1$s + Utwórz nową listę + \"Utwórz nową listę %1$s z użytkownikiem + Tworzy zbiór obserwowanych %1$s i dodaje do niego %2$s. + Nowa lista %1$s + Nazwa zbioru + Opis zbioru (opcjonalnie) + Utwórz zbiór + Zmień nazwę zbioru + Zmieniasz nazwę z + do.. Domyślny port to 9050 ## Połącz przez Tor z Orbotem \n\n1. Zainstaluj [Orbota](https://play.google. om/store/apps/details?id= org.torproject.android) @@ -1011,6 +1039,8 @@ Pobierz Nie udało się otworzyć pliku Brak zainstalowanych aplikacji torrent do otwarcia i pobrania pliku. + Zdarzenie nie ma wystarczającej ilości informacji, aby zbudować link magnetyczny + Moje Listy/Zbiory Wybierz listę, aby filtrować aktualności Wyloguj się przy blokowaniu urządzenia Wiadomość prywatna @@ -1018,6 +1048,7 @@ Transmiter Czatu Transmiter, z którym łączą się wszyscy użytkownicy tego czatu Udostępnij zdjęcie… + Nie można udostępnić obrazu, spróbuj ponownie później… Szukaj tagu: #%1$s Nie tłumacz z Języki wyświetlane tutaj nie będą tłumaczone. Wybierz język, aby usunąć go z listy języków nietłumaczonych. @@ -1031,4 +1062,5 @@ Czy chcesz wysłać ostatni raport o awarii do Amethyst w DM? Żadne dane osobowe nie będą udostępnione Prześlij Ta wiadomość zniknie za %1$d dni + Wybierz Sygnatariusza diff --git a/amethyst/src/main/res/values-pt-rBR/strings.xml b/amethyst/src/main/res/values-pt-rBR/strings.xml index 3cc81998a..439c76e55 100644 --- a/amethyst/src/main/res/values-pt-rBR/strings.xml +++ b/amethyst/src/main/res/values-pt-rBR/strings.xml @@ -112,6 +112,7 @@ Salvar Salvar Criar + Renomear Cancelar Falha ao enviar imagem Endereço do Relay @@ -443,6 +444,31 @@ Perto de mim Global Lista Silenciada + Conjuntos de Seguimento + Favoritos com etiqueta + Favoritos gerais + Público + Privado + Misto + Parece que você ainda não tem conjuntos de seguimento.\nToque abaixo para atualizar ou use o botão de adicionar para criar um novo. + Adicionar autor ao conjunto de seguimento + Adicionar ou remover usuário de listas, ou criar uma nova lista com este usuário. + Ícone da lista %1$s + "%1$s está presente nesta lista" + "%1$s não está nesta lista" + Seus conjuntos de seguimento + Nenhum conjunto de seguimento foi encontrado ou você não possui nenhum. Toque abaixo para atualizar ou use o menu para criar um. + Houve um problema ao buscar: %1$s + Criar nova lista + Criar nova lista %1$s com usuário + Cria um conjunto de seguimento %1$s e adiciona %2$s a ele. + Nova lista %1$s + Nome do conjunto + Descrição do conjunto (opcional) + Criar conjunto + Renomear conjunto + Você está renomeando de + para.. Porta padrão é 9050 ## Conecte-se através do Tor com o Orbot \n\n1. Instale o [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) @@ -830,6 +856,8 @@ Média Alta Sem compressão + Usar codec H.265/HEVC + Melhor qualidade em arquivos menores, mas nem todos os dispositivos suportam reprodução em H.265. Editar rascunho Entrar com Código QR Rota @@ -1012,6 +1040,8 @@ Baixar Falha ao abrir o arquivo Nenhum aplicativo torrent instalado para abrir e baixar o arquivo. + O evento não tem informações suficientes para criar um link magnético + Minhas listas/conjuntos Selecione uma lista para filtrar o feed Terminar sessão no bloqueio do dispositivo Mensagem Privada @@ -1033,4 +1063,5 @@ Gostaria de enviar o relatório de falha recente para o Amethyst em uma DM? Nenhuma informação pessoal será compartilhada Enviar Esta mensagem desaparecerá em %1$d dias + Selecionar assinador diff --git a/amethyst/src/main/res/values-sl-rSI/strings.xml b/amethyst/src/main/res/values-sl-rSI/strings.xml index 7ed17546e..a5ad70e03 100644 --- a/amethyst/src/main/res/values-sl-rSI/strings.xml +++ b/amethyst/src/main/res/values-sl-rSI/strings.xml @@ -456,6 +456,8 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem V moji okolici Globalno Spisek utišanih + Zasebno + Mešano Prevzeta vrata so 9050 ## Poveži se preko Tor omrežja z Orbot aplikacijo \n\n1. Namesti [Orbot aplikacijo](https://play.google.com/store/apps/details?id=org.torproject.android) diff --git a/amethyst/src/main/res/values-sv-rSE/strings.xml b/amethyst/src/main/res/values-sv-rSE/strings.xml index 3695dd940..4eb00d879 100644 --- a/amethyst/src/main/res/values-sv-rSE/strings.xml +++ b/amethyst/src/main/res/values-sv-rSE/strings.xml @@ -112,6 +112,7 @@ Dela Spara Skapa + Byt namn Avbryt Det gick inte att ladda upp bilden Relä Adress @@ -443,6 +444,31 @@ Runt mig Global Tyst listan + Följ-set + Märkta bokmärken + Allmänna bokmärken + Offentlig + Privat + Blandad + Det verkar som att du inte har några följ-set ännu.\nTryck nedan för att uppdatera eller använd plusknappen för att skapa ett nytt. + Lägg till författare i följ-set + Lägg till eller ta bort användare från listor, eller skapa en ny lista med denna användare. + Ikon för %1$s-lista + "%1$s finns i denna lista" + "%1$s finns inte i denna lista" + Dina följ-set + Inga följ-set hittades, eller så har du inga. Tryck nedan för att uppdatera eller använd menyn för att skapa ett. + Ett problem uppstod vid hämtning: %1$s + Skapa ny lista + Skapa ny %1$s-lista med användare + Skapar ett %1$s-följ-set och lägger till %2$s i det. + Ny %1$s-lista + Set-namn + Set-beskrivning (valfritt) + Skapa set + Byt namn på set + Du byter namn från + till.. Standardporten är 9050 ## Anslut genom Tor med Orbot \n\n1. Installera [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android) @@ -829,6 +855,8 @@ Medel Hög Okomprimerad + Använd H.265/HEVC-codec + Bättre kvalitet med mindre filstorlek, men inte alla enheter stöder H.265-uppspelning. Redigera utkast Logga in med QR-kod Rutt @@ -1011,6 +1039,8 @@ Ladda ner Det gick inte att öppna filen Inga torrent-appar installerade för att öppna och ladda ner filen. + Händelsen har inte tillräcklig information för att skapa en magnetlänk + Mina listor/set Välj en lista för att filtrera flödet Logga ut när enheten låses Privat meddelande @@ -1032,4 +1062,5 @@ Vill du skicka den senaste kraschrapporten till Amethyst i ett DM? Ingen personlig information kommer att delas Skicka Detta meddelande försvinner om %1$d dagar + Välj signatör diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index 5db7302c7..6ff31fe76 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -1042,6 +1042,8 @@ Medium High Uncompressed + Use H.265/HEVC Codec + Better quality at smaller file sizes but not all devices support H.265 playback. Edit draft diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 691f31f5f..f835e77be 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ languageId = "17.0.6" lazysodiumAndroid = "5.2.0" lazysodiumJava = "5.2.0" lifecycleRuntimeKtx = "2.9.4" -lightcompressor = "1.4.0" +lightcompressor = "1.5.0" markdown = "e1151c8" media3 = "1.8.0" mockk = "1.14.5"