Image and Video Compression
This commit is contained in:
@@ -188,6 +188,11 @@ dependencies {
|
|||||||
// immutable collections to avoid recomposition
|
// immutable collections to avoid recomposition
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
|
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.5")
|
||||||
|
|
||||||
|
// Video compression lib
|
||||||
|
implementation 'com.github.AbedElazizShe:LightCompressor:1.2.3'
|
||||||
|
// Image compression lib
|
||||||
|
implementation 'id.zelory:compressor:3.0.1'
|
||||||
|
|
||||||
// Automatic memory leak detection
|
// Automatic memory leak detection
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11'
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.11'
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<queries>
|
<queries>
|
||||||
<package android:name="org.torproject.android"/>
|
<package android:name="org.torproject.android"/>
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
@@ -13,8 +14,9 @@
|
|||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
|
||||||
<!-- Used for SDK < 29 -->
|
<uses-permission
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
|
android:maxSdkVersion="28"
|
||||||
tools:ignore="ScopedStorage" />
|
tools:ignore="ScopedStorage" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.ContentResolver
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.core.net.toFile
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.service.HttpClient
|
import com.vitorpamplona.amethyst.service.HttpClient
|
||||||
@@ -22,19 +23,24 @@ object ImageUploader {
|
|||||||
|
|
||||||
fun uploadImage(
|
fun uploadImage(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
|
contentType: String?,
|
||||||
|
size: Long?,
|
||||||
server: ServersAvailable,
|
server: ServersAvailable,
|
||||||
contentResolver: ContentResolver,
|
contentResolver: ContentResolver,
|
||||||
onSuccess: (String, String?) -> Unit,
|
onSuccess: (String, String?) -> Unit,
|
||||||
onError: (Throwable) -> Unit
|
onError: (Throwable) -> Unit
|
||||||
) {
|
) {
|
||||||
val contentType = contentResolver.getType(uri)
|
val myContentType = contentType ?: contentResolver.getType(uri)
|
||||||
val imageInputStream = contentResolver.openInputStream(uri)
|
val imageInputStream = contentResolver.openInputStream(uri)
|
||||||
|
|
||||||
val length = contentResolver.query(uri, null, null, null, null)?.use {
|
val length = size
|
||||||
it.moveToFirst()
|
?: contentResolver.query(uri, null, null, null, null)?.use {
|
||||||
val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
|
it.moveToFirst()
|
||||||
it.getLong(sizeIndex)
|
val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
|
||||||
} ?: 0
|
it.getLong(sizeIndex)
|
||||||
|
} ?: kotlin.runCatching {
|
||||||
|
uri.toFile().length()
|
||||||
|
}.getOrNull() ?: 0
|
||||||
|
|
||||||
checkNotNull(imageInputStream) {
|
checkNotNull(imageInputStream) {
|
||||||
"Can't open the image input stream"
|
"Can't open the image input stream"
|
||||||
@@ -57,7 +63,7 @@ object ImageUploader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadImage(imageInputStream, length, contentType, myServer, onSuccess, onError)
|
uploadImage(imageInputStream, length, myContentType, myServer, onSuccess, onError)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uploadImage(
|
fun uploadImage(
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
import com.vitorpamplona.amethyst.service.FileHeader
|
import com.vitorpamplona.amethyst.service.FileHeader
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -56,33 +57,56 @@ open class NewMediaModel : ViewModel() {
|
|||||||
isUploadingImage = true
|
isUploadingImage = true
|
||||||
|
|
||||||
val contentResolver = context.contentResolver
|
val contentResolver = context.contentResolver
|
||||||
val uri = galleryUri ?: return
|
val myGalleryUri = galleryUri ?: return
|
||||||
val serverToUse = selectedServer ?: return
|
val serverToUse = selectedServer ?: return
|
||||||
|
|
||||||
if (selectedServer == ServersAvailable.NIP95) {
|
val contentType = contentResolver.getType(myGalleryUri)
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
uploadingPercentage.value = 0.1f
|
uploadingPercentage.value = 0.1f
|
||||||
uploadingDescription.value = "Loading"
|
uploadingDescription.value = "Compress"
|
||||||
val contentType = contentResolver.getType(uri)
|
MediaCompressor().compress(
|
||||||
contentResolver.openInputStream(uri)?.use {
|
myGalleryUri,
|
||||||
createNIP95Record(it.readBytes(), contentType, description)
|
contentType,
|
||||||
}
|
context.applicationContext,
|
||||||
?: run {
|
onReady = { fileUri, contentType, size ->
|
||||||
viewModelScope.launch {
|
|
||||||
imageUploadingError.emit("Failed to upload the image / video")
|
if (selectedServer == ServersAvailable.NIP95) {
|
||||||
isUploadingImage = false
|
uploadingPercentage.value = 0.2f
|
||||||
uploadingPercentage.value = 0.00f
|
uploadingDescription.value = "Loading"
|
||||||
uploadingDescription.value = null
|
contentResolver.openInputStream(fileUri)?.use {
|
||||||
|
createNIP95Record(it.readBytes(), contentType, description)
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
viewModelScope.launch {
|
||||||
|
imageUploadingError.emit("Failed to upload the image / video")
|
||||||
|
isUploadingImage = false
|
||||||
|
uploadingPercentage.value = 0.00f
|
||||||
|
uploadingDescription.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uploadingPercentage.value = 0.2f
|
||||||
|
uploadingDescription.value = "Uploading"
|
||||||
|
ImageUploader.uploadImage(
|
||||||
|
uri = fileUri,
|
||||||
|
contentType = contentType,
|
||||||
|
size = size,
|
||||||
|
server = serverToUse,
|
||||||
|
contentResolver = contentResolver,
|
||||||
|
onSuccess = { imageUrl, mimeType ->
|
||||||
|
createNIP94Record(imageUrl, mimeType, description)
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
isUploadingImage = false
|
||||||
|
uploadingPercentage.value = 0.00f
|
||||||
|
uploadingDescription.value = null
|
||||||
|
viewModelScope.launch {
|
||||||
|
imageUploadingError.emit("Failed to upload the image / video")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uploadingPercentage.value = 0.1f
|
|
||||||
uploadingDescription.value = "Uploading"
|
|
||||||
ImageUploader.uploadImage(
|
|
||||||
uri = uri,
|
|
||||||
server = serverToUse,
|
|
||||||
contentResolver = contentResolver,
|
|
||||||
onSuccess = { imageUrl, mimeType ->
|
|
||||||
createNIP94Record(imageUrl, mimeType, description)
|
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
isUploadingImage = false
|
isUploadingImage = false
|
||||||
@@ -164,7 +188,7 @@ open class NewMediaModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createNIP95Record(bytes: ByteArray, mimeType: String?, description: String) {
|
fun createNIP95Record(bytes: ByteArray, mimeType: String?, description: String) {
|
||||||
uploadingPercentage.value = 0.20f
|
uploadingPercentage.value = 0.30f
|
||||||
uploadingDescription.value = "Hashing"
|
uploadingDescription.value = "Hashing"
|
||||||
|
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
@@ -175,7 +199,7 @@ open class NewMediaModel : ViewModel() {
|
|||||||
description,
|
description,
|
||||||
onReady = {
|
onReady = {
|
||||||
uploadingDescription.value = "Signing"
|
uploadingDescription.value = "Signing"
|
||||||
uploadingPercentage.value = 0.30f
|
uploadingPercentage.value = 0.40f
|
||||||
val nip95 = account?.createNip95(bytes, headerInfo = it)
|
val nip95 = account?.createNip95(bytes, headerInfo = it)
|
||||||
|
|
||||||
if (nip95 != null) {
|
if (nip95 != null) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
|||||||
import com.vitorpamplona.amethyst.service.model.BaseTextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.BaseTextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
||||||
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -142,35 +143,52 @@ open class NewPostViewModel() : ViewModel() {
|
|||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upload(it: Uri, description: String, server: ServersAvailable, context: Context) {
|
fun upload(galleryUri: Uri, description: String, server: ServersAvailable, context: Context) {
|
||||||
isUploadingImage = true
|
isUploadingImage = true
|
||||||
contentToAddUrl = null
|
contentToAddUrl = null
|
||||||
|
|
||||||
val contentResolver = context.contentResolver
|
val contentResolver = context.contentResolver
|
||||||
|
val contentType = contentResolver.getType(galleryUri)
|
||||||
|
|
||||||
if (server == ServersAvailable.NIP95) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val contentType = contentResolver.getType(it)
|
MediaCompressor().compress(
|
||||||
contentResolver.openInputStream(it)?.use {
|
galleryUri,
|
||||||
createNIP95Record(it.readBytes(), contentType, description)
|
contentType,
|
||||||
}
|
context.applicationContext,
|
||||||
} else {
|
onReady = { fileUri, contentType, size ->
|
||||||
ImageUploader.uploadImage(
|
if (server == ServersAvailable.NIP95) {
|
||||||
uri = it,
|
contentResolver.openInputStream(fileUri)?.use {
|
||||||
server = server,
|
createNIP95Record(it.readBytes(), contentType, description)
|
||||||
contentResolver = contentResolver,
|
}
|
||||||
onSuccess = { imageUrl, mimeType ->
|
|
||||||
if (isNIP94Server(server)) {
|
|
||||||
createNIP94Record(imageUrl, mimeType, description)
|
|
||||||
} else {
|
} else {
|
||||||
isUploadingImage = false
|
ImageUploader.uploadImage(
|
||||||
message = TextFieldValue(message.text + "\n\n" + imageUrl)
|
uri = fileUri,
|
||||||
urlPreview = findUrlInMessage()
|
contentType = contentType,
|
||||||
|
size = size,
|
||||||
|
server = server,
|
||||||
|
contentResolver = contentResolver,
|
||||||
|
onSuccess = { imageUrl, mimeType ->
|
||||||
|
if (isNIP94Server(server)) {
|
||||||
|
createNIP94Record(imageUrl, mimeType, description)
|
||||||
|
} else {
|
||||||
|
isUploadingImage = false
|
||||||
|
message = TextFieldValue(message.text + "\n\n" + imageUrl)
|
||||||
|
urlPreview = findUrlInMessage()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
isUploadingImage = false
|
||||||
|
viewModelScope.launch {
|
||||||
|
imageUploadingError.emit("Failed to upload the image / video")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
isUploadingImage = false
|
isUploadingImage = false
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
imageUploadingError.emit("Failed to upload the image / video")
|
imageUploadingError.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
import com.vitorpamplona.amethyst.service.model.ContactListEvent
|
||||||
@@ -8,9 +9,11 @@ import com.vitorpamplona.amethyst.service.relays.Constants
|
|||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
import com.vitorpamplona.amethyst.service.relays.RelayPool
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class NewRelayListViewModel : ViewModel() {
|
class NewRelayListViewModel : ViewModel() {
|
||||||
private lateinit var account: Account
|
private lateinit var account: Account
|
||||||
@@ -25,7 +28,9 @@ class NewRelayListViewModel : ViewModel() {
|
|||||||
|
|
||||||
fun create() {
|
fun create() {
|
||||||
relays.let {
|
relays.let {
|
||||||
account.saveRelayList(it.value)
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
account.saveRelayList(it.value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clear()
|
clear()
|
||||||
|
|||||||
+52
-29
@@ -13,6 +13,8 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.service.model.GitHubIdentity
|
import com.vitorpamplona.amethyst.service.model.GitHubIdentity
|
||||||
import com.vitorpamplona.amethyst.service.model.MastodonIdentity
|
import com.vitorpamplona.amethyst.service.model.MastodonIdentity
|
||||||
import com.vitorpamplona.amethyst.service.model.TwitterIdentity
|
import com.vitorpamplona.amethyst.service.model.TwitterIdentity
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||||
|
import id.zelory.compressor.Compressor.compress
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -143,46 +145,67 @@ class NewUserMetadataViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun uploadForPicture(uri: Uri, context: Context) {
|
fun uploadForPicture(uri: Uri, context: Context) {
|
||||||
upload(
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
uri,
|
upload(
|
||||||
context,
|
uri,
|
||||||
onUploading = {
|
context,
|
||||||
isUploadingImageForPicture = it
|
onUploading = {
|
||||||
},
|
isUploadingImageForPicture = it
|
||||||
onUploaded = {
|
},
|
||||||
picture.value = it
|
onUploaded = {
|
||||||
}
|
picture.value = it
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uploadForBanner(uri: Uri, context: Context) {
|
fun uploadForBanner(uri: Uri, context: Context) {
|
||||||
upload(
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
uri,
|
upload(
|
||||||
context,
|
uri,
|
||||||
onUploading = {
|
context,
|
||||||
isUploadingImageForBanner = it
|
onUploading = {
|
||||||
},
|
isUploadingImageForBanner = it
|
||||||
onUploaded = {
|
},
|
||||||
banner.value = it
|
onUploaded = {
|
||||||
}
|
banner.value = it
|
||||||
)
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upload(it: Uri, context: Context, onUploading: (Boolean) -> Unit, onUploaded: (String) -> Unit) {
|
private suspend fun upload(galleryUri: Uri, context: Context, onUploading: (Boolean) -> Unit, onUploaded: (String) -> Unit) {
|
||||||
onUploading(true)
|
onUploading(true)
|
||||||
|
|
||||||
ImageUploader.uploadImage(
|
val contentResolver = context.contentResolver
|
||||||
uri = it,
|
|
||||||
server = account.defaultFileServer,
|
MediaCompressor().compress(
|
||||||
contentResolver = context.contentResolver,
|
galleryUri,
|
||||||
onSuccess = { imageUrl, mimeType ->
|
contentResolver.getType(galleryUri),
|
||||||
onUploading(false)
|
context.applicationContext,
|
||||||
onUploaded(imageUrl)
|
onReady = { fileUri, contentType, size ->
|
||||||
|
ImageUploader.uploadImage(
|
||||||
|
uri = fileUri,
|
||||||
|
contentType = contentType,
|
||||||
|
size = size,
|
||||||
|
server = account.defaultFileServer,
|
||||||
|
contentResolver = contentResolver,
|
||||||
|
onSuccess = { imageUrl, mimeType ->
|
||||||
|
onUploading(false)
|
||||||
|
onUploaded(imageUrl)
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
onUploading(false)
|
||||||
|
viewModelScope.launch {
|
||||||
|
imageUploadingError.emit("Failed to upload the image / video")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
onUploading(false)
|
onUploading(false)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
imageUploadingError.emit("Failed to upload the image / video")
|
imageUploadingError.emit(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import com.abedelazizshe.lightcompressorlibrary.CompressionListener
|
||||||
|
import com.abedelazizshe.lightcompressorlibrary.VideoCompressor
|
||||||
|
import com.abedelazizshe.lightcompressorlibrary.VideoQuality
|
||||||
|
import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration
|
||||||
|
import com.abedelazizshe.lightcompressorlibrary.config.Configuration
|
||||||
|
import id.zelory.compressor.Compressor
|
||||||
|
import id.zelory.compressor.constraint.default
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class MediaCompressor {
|
||||||
|
suspend fun compress(
|
||||||
|
uri: Uri,
|
||||||
|
contentType: String?,
|
||||||
|
applicationContext: Context,
|
||||||
|
onReady: (Uri, String?, Long?) -> Unit,
|
||||||
|
onError: (String) -> Unit
|
||||||
|
) {
|
||||||
|
if (contentType?.startsWith("video", true) == true) {
|
||||||
|
VideoCompressor.start(
|
||||||
|
context = applicationContext, // => This is required
|
||||||
|
uris = listOf(uri), // => Source can be provided as content uris
|
||||||
|
isStreamable = false,
|
||||||
|
// THIS STORAGE
|
||||||
|
// sharedStorageConfiguration = SharedStorageConfiguration(
|
||||||
|
// saveAt = SaveLocation.movies, // => default is movies
|
||||||
|
// videoName = "compressed_video" // => required name
|
||||||
|
// ),
|
||||||
|
// OR AND NOT BOTH
|
||||||
|
appSpecificStorageConfiguration = AppSpecificStorageConfiguration(
|
||||||
|
videoName = UUID.randomUUID().toString() // => required name
|
||||||
|
),
|
||||||
|
configureWith = Configuration(
|
||||||
|
quality = VideoQuality.LOW
|
||||||
|
),
|
||||||
|
listener = object : CompressionListener {
|
||||||
|
override fun onProgress(index: Int, percent: Float) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(index: Int) {
|
||||||
|
// Compression start
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(index: Int, size: Long, path: String?) {
|
||||||
|
if (path != null) {
|
||||||
|
onReady(Uri.fromFile(File(path)), contentType, size)
|
||||||
|
} else {
|
||||||
|
onError("Compression Returned null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(index: Int, failureMessage: String) {
|
||||||
|
onError(failureMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancelled(index: Int) {
|
||||||
|
onError("Compression Cancelled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else if (contentType?.startsWith("image", true) == true && !contentType.contains("gif")) {
|
||||||
|
val compressedImageFile = Compressor.compress(applicationContext, from(uri, contentType, applicationContext)) {
|
||||||
|
default(width = 640, format = Bitmap.CompressFormat.JPEG)
|
||||||
|
}
|
||||||
|
onReady(compressedImageFile.toUri(), contentType, compressedImageFile.length())
|
||||||
|
} else {
|
||||||
|
onReady(uri, contentType, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun from(uri: Uri?, contentType: String?, context: Context): File {
|
||||||
|
val extension = contentType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
|
||||||
|
|
||||||
|
val inputStream = context.contentResolver.openInputStream(uri!!)
|
||||||
|
val fileName: String = UUID.randomUUID().toString() + ".$extension"
|
||||||
|
val splitName: Array<String> = splitFileName(fileName)
|
||||||
|
val tempFile = File.createTempFile(splitName[0], splitName[1])
|
||||||
|
inputStream?.use { input ->
|
||||||
|
FileOutputStream(tempFile).use { output ->
|
||||||
|
val buffer = ByteArray(1024 * 50)
|
||||||
|
var read: Int = input.read(buffer)
|
||||||
|
while (read != -1) {
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
read = input.read(buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tempFile
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun splitFileName(fileName: String): Array<String> {
|
||||||
|
var name = fileName
|
||||||
|
var extension = ""
|
||||||
|
val i = fileName.lastIndexOf(".")
|
||||||
|
if (i != -1) {
|
||||||
|
name = fileName.substring(0, i)
|
||||||
|
extension = fileName.substring(i)
|
||||||
|
}
|
||||||
|
return arrayOf(name, extension)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,6 +71,7 @@ import com.vitorpamplona.amethyst.ui.actions.NewChannelView
|
|||||||
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
import com.vitorpamplona.amethyst.ui.actions.NewMessageTagger
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
import com.vitorpamplona.amethyst.ui.actions.PostButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.ServersAvailable
|
||||||
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
import com.vitorpamplona.amethyst.ui.actions.UploadFromGallery
|
||||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
@@ -219,7 +220,7 @@ fun ChannelScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
// LAST ROW
|
// LAST ROW
|
||||||
EditFieldRow(newPostModel, accountViewModel) {
|
EditFieldRow(newPostModel, isPrivate = false, accountViewModel = accountViewModel) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val tagger = NewMessageTagger(
|
val tagger = NewMessageTagger(
|
||||||
channelHex = channel.idHex,
|
channelHex = channel.idHex,
|
||||||
@@ -290,6 +291,7 @@ fun DisplayReplyingToNote(
|
|||||||
@Composable
|
@Composable
|
||||||
fun EditFieldRow(
|
fun EditFieldRow(
|
||||||
channelScreenModel: NewPostViewModel,
|
channelScreenModel: NewPostViewModel,
|
||||||
|
isPrivate: Boolean,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
onSendNewMessage: () -> Unit
|
onSendNewMessage: () -> Unit
|
||||||
) {
|
) {
|
||||||
@@ -334,7 +336,22 @@ fun EditFieldRow(
|
|||||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
modifier = Modifier.padding(start = 5.dp)
|
modifier = Modifier.padding(start = 5.dp)
|
||||||
) {
|
) {
|
||||||
channelScreenModel.upload(it, "", accountViewModel.account.defaultFileServer, context)
|
val fileServer = if (isPrivate) {
|
||||||
|
// TODO: Make private servers
|
||||||
|
when (accountViewModel.account.defaultFileServer) {
|
||||||
|
ServersAvailable.NOSTR_BUILD -> ServersAvailable.NOSTR_BUILD
|
||||||
|
ServersAvailable.NOSTRIMG -> ServersAvailable.NOSTRIMG
|
||||||
|
ServersAvailable.NOSTRFILES_DEV -> ServersAvailable.NOSTRFILES_DEV
|
||||||
|
ServersAvailable.NOSTR_BUILD_NIP_94 -> ServersAvailable.NOSTR_BUILD
|
||||||
|
ServersAvailable.NOSTRIMG_NIP_94 -> ServersAvailable.NOSTRIMG
|
||||||
|
ServersAvailable.NOSTRFILES_DEV_NIP_94 -> ServersAvailable.NOSTRFILES_DEV
|
||||||
|
ServersAvailable.NIP95 -> ServersAvailable.NOSTR_BUILD
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
accountViewModel.account.defaultFileServer
|
||||||
|
}
|
||||||
|
|
||||||
|
channelScreenModel.upload(it, "", fileServer, context)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
colors = TextFieldDefaults.textFieldColors(
|
colors = TextFieldDefaults.textFieldColors(
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ fun ChatroomScreen(
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
// LAST ROW
|
// LAST ROW
|
||||||
EditFieldRow(newPostModel, accountViewModel) {
|
EditFieldRow(newPostModel, isPrivate = true, accountViewModel) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
accountViewModel.account.sendPrivateMessage(
|
accountViewModel.account.sendPrivateMessage(
|
||||||
message = newPostModel.message.text,
|
message = newPostModel.message.text,
|
||||||
|
|||||||
Reference in New Issue
Block a user