mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-22 22:27:16 +01:00
Show running image uploading. Show error message if the uploading is failed
This commit is contained in:
@@ -15,12 +15,13 @@ object ImageUploader {
|
|||||||
uri: Uri,
|
uri: Uri,
|
||||||
contentResolver: ContentResolver,
|
contentResolver: ContentResolver,
|
||||||
onSuccess: (String) -> Unit,
|
onSuccess: (String) -> Unit,
|
||||||
) {
|
onError: (Throwable) -> Unit,
|
||||||
|
) {
|
||||||
val contentType = contentResolver.getType(uri)
|
val contentType = contentResolver.getType(uri)
|
||||||
|
|
||||||
val client = OkHttpClient.Builder().build()
|
val client = OkHttpClient.Builder().build()
|
||||||
|
|
||||||
val body: RequestBody = MultipartBody.Builder()
|
val requestBody: RequestBody = MultipartBody.Builder()
|
||||||
.setType(MultipartBody.FORM)
|
.setType(MultipartBody.FORM)
|
||||||
.addFormDataPart(
|
.addFormDataPart(
|
||||||
"image",
|
"image",
|
||||||
@@ -30,9 +31,12 @@ object ImageUploader {
|
|||||||
contentType?.toMediaType()
|
contentType?.toMediaType()
|
||||||
|
|
||||||
override fun writeTo(sink: BufferedSink) {
|
override fun writeTo(sink: BufferedSink) {
|
||||||
contentResolver.openInputStream(uri)!!.use { inputStream ->
|
val imageInputStream = contentResolver.openInputStream(uri)
|
||||||
sink.writeAll(inputStream.source())
|
checkNotNull(imageInputStream) {
|
||||||
|
"Can't open the image input stream"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imageInputStream.source().use(sink::writeAll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -41,24 +45,31 @@ object ImageUploader {
|
|||||||
val request: Request = Request.Builder()
|
val request: Request = Request.Builder()
|
||||||
.url("https://api.imgur.com/3/image")
|
.url("https://api.imgur.com/3/image")
|
||||||
.header("Authorization", "Client-ID e6aea87296f3f96")
|
.header("Authorization", "Client-ID e6aea87296f3f96")
|
||||||
.post(body)
|
.post(requestBody)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
client.newCall(request).enqueue(object : Callback {
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
response.use {
|
try {
|
||||||
val body = response.body
|
check(response.isSuccessful)
|
||||||
if (body != null) {
|
response.body.use { body ->
|
||||||
val tree = jacksonObjectMapper().readTree(body.string())
|
val tree = jacksonObjectMapper().readTree(body.string())
|
||||||
val url = tree?.get("data")?.get("link")?.asText()
|
val url = tree?.get("data")?.get("link")?.asText()
|
||||||
if (url != null)
|
checkNotNull(url) {
|
||||||
onSuccess(url)
|
"There must be an uploaded image URL in the response"
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(url)
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
onError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
onError(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,13 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.ButtonDefaults
|
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.LocalTextStyle
|
|
||||||
import androidx.compose.material.MaterialTheme
|
|
||||||
import androidx.compose.material.OutlinedTextField
|
|
||||||
import androidx.compose.material.Surface
|
|
||||||
import androidx.compose.material.Text
|
|
||||||
import androidx.compose.material.TextFieldDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -37,11 +23,9 @@ import androidx.compose.ui.layout.ContentScale
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.style.TextDirection
|
import androidx.compose.ui.text.style.TextDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@@ -49,12 +33,7 @@ import coil.compose.AsyncImage
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreview
|
import com.vitorpamplona.amethyst.ui.components.*
|
||||||
import com.vitorpamplona.amethyst.ui.components.VideoView
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.imageExtension
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.isValidURL
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.videoExtension
|
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.UploadFromGallery
|
import com.vitorpamplona.amethyst.ui.navigation.UploadFromGallery
|
||||||
import com.vitorpamplona.amethyst.ui.note.ReplyInformation
|
import com.vitorpamplona.amethyst.ui.note.ReplyInformation
|
||||||
import com.vitorpamplona.amethyst.ui.screen.UserLine
|
import com.vitorpamplona.amethyst.ui.screen.UserLine
|
||||||
@@ -78,6 +57,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
delay(100)
|
delay(100)
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
|
|
||||||
|
postViewModel.imageUploadingError.collect { error ->
|
||||||
|
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
@@ -106,7 +89,9 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account
|
|||||||
onClose()
|
onClose()
|
||||||
})
|
})
|
||||||
|
|
||||||
UploadFromGallery {
|
UploadFromGallery(
|
||||||
|
isUploading = postViewModel.isUploadingImage,
|
||||||
|
) {
|
||||||
postViewModel.upload(it, context)
|
postViewModel.upload(it, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +100,8 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, account: Account
|
|||||||
postViewModel.sendPost()
|
postViewModel.sendPost()
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
postViewModel.message.text.isNotBlank()
|
isActive = postViewModel.message.text.isNotBlank()
|
||||||
|
&& !postViewModel.isUploadingImage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.vitorpamplona.amethyst.model.*
|
import com.vitorpamplona.amethyst.model.*
|
||||||
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.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.toNpub
|
import nostr.postr.toNpub
|
||||||
|
|
||||||
class NewPostViewModel: ViewModel() {
|
class NewPostViewModel: ViewModel() {
|
||||||
@@ -22,6 +25,8 @@ class NewPostViewModel: ViewModel() {
|
|||||||
|
|
||||||
var message by mutableStateOf(TextFieldValue(""))
|
var message by mutableStateOf(TextFieldValue(""))
|
||||||
var urlPreview by mutableStateOf<String?>(null)
|
var urlPreview by mutableStateOf<String?>(null)
|
||||||
|
var isUploadingImage by mutableStateOf(false)
|
||||||
|
val imageUploadingError = MutableSharedFlow<String?>()
|
||||||
|
|
||||||
var userSuggestions by mutableStateOf<List<User>>(emptyList())
|
var userSuggestions by mutableStateOf<List<User>>(emptyList())
|
||||||
var userSuggestionAnchor: TextRange? = null
|
var userSuggestionAnchor: TextRange? = null
|
||||||
@@ -96,21 +101,33 @@ class NewPostViewModel: ViewModel() {
|
|||||||
|
|
||||||
message = TextFieldValue("")
|
message = TextFieldValue("")
|
||||||
urlPreview = null
|
urlPreview = null
|
||||||
|
isUploadingImage = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upload(it: Uri, context: Context) {
|
fun upload(it: Uri, context: Context) {
|
||||||
|
isUploadingImage = true
|
||||||
|
|
||||||
ImageUploader.uploadImage(
|
ImageUploader.uploadImage(
|
||||||
uri = it,
|
uri = it,
|
||||||
contentResolver = context.contentResolver,
|
contentResolver = context.contentResolver,
|
||||||
) {
|
onSuccess = { imageUrl ->
|
||||||
message = TextFieldValue(message.text + "\n\n" + it)
|
isUploadingImage = false
|
||||||
urlPreview = findUrlInMessage()
|
message = TextFieldValue(message.text + "\n\n" + imageUrl)
|
||||||
}
|
urlPreview = findUrlInMessage()
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
isUploadingImage = false
|
||||||
|
viewModelScope.launch {
|
||||||
|
imageUploadingError.emit("Failed to upload the image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
message = TextFieldValue("")
|
message = TextFieldValue("")
|
||||||
urlPreview = null
|
urlPreview = null
|
||||||
|
isUploadingImage = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun findUrlInMessage(): String? {
|
fun findUrlInMessage(): String? {
|
||||||
|
|||||||
@@ -9,12 +9,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.Button
|
import androidx.compose.material.Button
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.SideEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -24,7 +19,10 @@ import com.google.accompanist.permissions.rememberPermissionState
|
|||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun UploadFromGallery(onImageChosen: (Uri) -> Unit) {
|
fun UploadFromGallery(
|
||||||
|
isUploading: Boolean,
|
||||||
|
onImageChosen: (Uri) -> Unit,
|
||||||
|
) {
|
||||||
val cameraPermissionState =
|
val cameraPermissionState =
|
||||||
rememberPermissionState(
|
rememberPermissionState(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
@@ -50,18 +48,30 @@ fun UploadFromGallery(onImageChosen: (Uri) -> Unit) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopCenter)
|
.align(Alignment.TopCenter)
|
||||||
.padding(4.dp),
|
.padding(4.dp),
|
||||||
|
enabled = !isUploading,
|
||||||
onClick = {
|
onClick = {
|
||||||
showGallerySelect = true
|
showGallerySelect = true
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text("Upload Image")
|
if (!isUploading) {
|
||||||
|
Text("Upload Image")
|
||||||
|
} else {
|
||||||
|
Text("Uploading…")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Column {
|
Column {
|
||||||
Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
|
Button(
|
||||||
Text("Upload Image")
|
onClick = { cameraPermissionState.launchPermissionRequest() },
|
||||||
|
enabled = !isUploading,
|
||||||
|
) {
|
||||||
|
if (!isUploading) {
|
||||||
|
Text("Upload Image")
|
||||||
|
} else {
|
||||||
|
Text("Uploading…")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user