Adds server selection support

This commit is contained in:
Vitor Pamplona
2023-04-26 14:23:06 -04:00
parent 86fe9b4a65
commit a669e37774
10 changed files with 120 additions and 24 deletions
@@ -12,6 +12,7 @@ import com.vitorpamplona.amethyst.service.model.ContactListEvent
import com.vitorpamplona.amethyst.service.model.Event import com.vitorpamplona.amethyst.service.model.Event
import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent import com.vitorpamplona.amethyst.service.model.Event.Companion.getRefinedEvent
import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.LnZapEvent
import com.vitorpamplona.amethyst.ui.actions.ServersAvailable
import com.vitorpamplona.amethyst.ui.note.Nip47URI import com.vitorpamplona.amethyst.ui.note.Nip47URI
import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Hex
import nostr.postr.Persona import nostr.postr.Persona
@@ -44,6 +45,7 @@ private object PrefKeys {
const val TRANSLATE_TO = "translateTo" const val TRANSLATE_TO = "translateTo"
const val ZAP_AMOUNTS = "zapAmounts" const val ZAP_AMOUNTS = "zapAmounts"
const val DEFAULT_ZAPTYPE = "defaultZapType" const val DEFAULT_ZAPTYPE = "defaultZapType"
const val DEFAULT_FILE_SERVER = "defaultFileServer"
const val ZAP_PAYMENT_REQUEST_SERVER = "zapPaymentServer" const val ZAP_PAYMENT_REQUEST_SERVER = "zapPaymentServer"
const val LATEST_CONTACT_LIST = "latestContactList" const val LATEST_CONTACT_LIST = "latestContactList"
const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog" const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog"
@@ -194,6 +196,7 @@ object LocalPreferences {
putString(PrefKeys.TRANSLATE_TO, account.translateTo) putString(PrefKeys.TRANSLATE_TO, account.translateTo)
putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices)) putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
putString(PrefKeys.DEFAULT_ZAPTYPE, gson.toJson(account.defaultZapType)) putString(PrefKeys.DEFAULT_ZAPTYPE, gson.toJson(account.defaultZapType))
putString(PrefKeys.DEFAULT_FILE_SERVER, gson.toJson(account.defaultFileServer))
putString(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, gson.toJson(account.zapPaymentRequest)) putString(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, gson.toJson(account.zapPaymentRequest))
putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList)) putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog) putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
@@ -225,6 +228,11 @@ object LocalPreferences {
object : TypeToken<LnZapEvent.ZapType>() {}.type object : TypeToken<LnZapEvent.ZapType>() {}.type
) ?: LnZapEvent.ZapType.PUBLIC ) ?: LnZapEvent.ZapType.PUBLIC
val defaultFileServer = gson.fromJson(
getString(PrefKeys.DEFAULT_FILE_SERVER, "IMGUR"),
object : TypeToken<ServersAvailable>() {}.type
) ?: ServersAvailable.IMGUR
val zapPaymentRequestServer = try { val zapPaymentRequestServer = try {
getString(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, null)?.let { getString(PrefKeys.ZAP_PAYMENT_REQUEST_SERVER, null)?.let {
gson.fromJson(it, Nip47URI::class.java) gson.fromJson(it, Nip47URI::class.java)
@@ -269,6 +277,7 @@ object LocalPreferences {
translateTo, translateTo,
zapAmountChoices, zapAmountChoices,
defaultZapType, defaultZapType,
defaultFileServer,
zapPaymentRequestServer, zapPaymentRequestServer,
hideDeleteRequestDialog, hideDeleteRequestDialog,
hideBlockAlertDialog, hideBlockAlertDialog,
@@ -11,6 +11,7 @@ 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.Relay import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.RelayPool import com.vitorpamplona.amethyst.service.relays.RelayPool
import com.vitorpamplona.amethyst.ui.actions.ServersAvailable
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.Nip47URI import com.vitorpamplona.amethyst.ui.note.Nip47URI
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@@ -45,6 +46,7 @@ class Account(
var translateTo: String = Locale.getDefault().language, var translateTo: String = Locale.getDefault().language,
var zapAmountChoices: List<Long> = listOf(500L, 1000L, 5000L), var zapAmountChoices: List<Long> = listOf(500L, 1000L, 5000L),
var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE, var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE,
var defaultFileServer: ServersAvailable = ServersAvailable.IMGUR,
var zapPaymentRequest: Nip47URI? = null, var zapPaymentRequest: Nip47URI? = null,
var hideDeleteRequestDialog: Boolean = false, var hideDeleteRequestDialog: Boolean = false,
var hideBlockAlertDialog: Boolean = false, var hideBlockAlertDialog: Boolean = false,
@@ -652,6 +654,12 @@ class Account(
saveable.invalidateData() saveable.invalidateData()
} }
fun changeDefaultFileServer(server: ServersAvailable) {
defaultFileServer = server
live.invalidateData()
saveable.invalidateData()
}
fun changeZapAmounts(newAmounts: List<Long>) { fun changeZapAmounts(newAmounts: List<Long>) {
zapAmountChoices = newAmounts zapAmountChoices = newAmounts
live.invalidateData() live.invalidateData()
@@ -7,13 +7,17 @@ import android.media.MediaScannerConnection
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.BuildConfig
import okhttp3.* import okhttp3.*
import okio.BufferedSource import okio.BufferedSource
import okio.IOException import okio.IOException
import okio.buffer
import okio.sink import okio.sink
import okio.source
import java.io.File import java.io.File
import java.util.UUID
object ImageSaver { object ImageSaver {
/** /**
@@ -74,6 +78,38 @@ object ImageSaver {
}) })
} }
fun saveImage(
byteArray: ByteArray,
mimeType: String?,
context: Context,
onSuccess: () -> Any?,
onError: (Throwable) -> Any?
) {
try {
val extension = mimeType?.let { MimeTypeMap.getSingleton().getExtensionFromMimeType(it) } ?: ""
val buffer = byteArray.inputStream().source().buffer()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
saveContentQ(
displayName = UUID.randomUUID().toString(),
contentType = mimeType ?: "",
contentSource = buffer,
contentResolver = context.contentResolver
)
} else {
saveContentDefault(
fileName = UUID.randomUUID().toString() + ".$extension",
contentSource = buffer,
context = context
)
}
onSuccess()
} catch (e: Exception) {
e.printStackTrace()
onError(e)
}
}
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.Q)
private fun saveContentQ( private fun saveContentQ(
displayName: String, displayName: String,
@@ -14,6 +14,7 @@ import java.util.*
object ImageUploader { object ImageUploader {
fun uploadImage( fun uploadImage(
uri: Uri, uri: Uri,
server: ServersAvailable,
contentResolver: ContentResolver, contentResolver: ContentResolver,
onSuccess: (String, String?) -> Unit, onSuccess: (String, String?) -> Unit,
onError: (Throwable) -> Unit onError: (Throwable) -> Unit
@@ -58,6 +58,7 @@ import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.ui.components.* import com.vitorpamplona.amethyst.ui.components.*
import com.vitorpamplona.amethyst.ui.note.ReplyInformation import com.vitorpamplona.amethyst.ui.note.ReplyInformation
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -206,8 +207,10 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
if (url != null) { if (url != null) {
ImageVideoDescription( ImageVideoDescription(
url, url,
onAdd = { description -> account.defaultFileServer,
postViewModel.upload(url, description, context) onAdd = { description, server ->
postViewModel.upload(url, description, server, context)
account.changeDefaultFileServer(server)
}, },
onCancel = { onCancel = {
postViewModel.contentToAddUrl = null postViewModel.contentToAddUrl = null
@@ -485,10 +488,15 @@ fun SearchButton(onPost: () -> Unit = {}, isActive: Boolean, modifier: Modifier
} }
} }
enum class ServersAvailable() {
IMGUR
}
@Composable @Composable
fun ImageVideoDescription( fun ImageVideoDescription(
uri: Uri, uri: Uri,
onAdd: (String) -> Unit, defaultServer: ServersAvailable,
onAdd: (String, ServersAvailable) -> Unit,
onCancel: () -> Unit onCancel: () -> Unit
) { ) {
val resolver = LocalContext.current.contentResolver val resolver = LocalContext.current.contentResolver
@@ -498,6 +506,15 @@ fun ImageVideoDescription(
val isImage = mediaType.startsWith("image") val isImage = mediaType.startsWith("image")
val isVideo = mediaType.startsWith("video") val isVideo = mediaType.startsWith("video")
val fileServers = listOf(
Pair(ServersAvailable.IMGUR, "imgur.com")
)
val fileServerOptions = fileServers.map { it.second }
var selectedServer by remember { mutableStateOf(defaultServer) }
var message by remember { mutableStateOf("") }
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -595,30 +612,49 @@ fun ImageVideoDescription(
} }
} }
var message by remember { mutableStateOf("") } Row(
verticalAlignment = Alignment.CenterVertically,
OutlinedTextField( modifier = Modifier.fillMaxWidth()
label = { Text(text = stringResource(R.string.content_description)) }, ) {
modifier = Modifier.fillMaxWidth(), TextSpinner(
value = message, label = stringResource(id = R.string.file_server),
onValueChange = { message = it }, placeholder = fileServers.filter { it.first == defaultServer }.first().second,
placeholder = { options = fileServerOptions,
Text( onSelect = {
text = stringResource(R.string.content_description_example), selectedServer = fileServers[it].first
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) },
) modifier = Modifier
}, .weight(1f)
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences
) )
) }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
OutlinedTextField(
label = { Text(text = stringResource(R.string.content_description)) },
modifier = Modifier.fillMaxWidth(),
value = message,
onValueChange = { message = it },
placeholder = {
Text(
text = stringResource(R.string.content_description_example),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
)
},
keyboardOptions = KeyboardOptions.Default.copy(
capitalization = KeyboardCapitalization.Sentences
)
)
}
Button( Button(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 10.dp), .padding(vertical = 10.dp),
onClick = { onClick = {
onAdd(message) onAdd(message, selectedServer)
}, },
shape = RoundedCornerShape(15.dp), shape = RoundedCornerShape(15.dp),
colors = ButtonDefaults.buttonColors( colors = ButtonDefaults.buttonColors(
@@ -110,12 +110,13 @@ open class NewPostViewModel : ViewModel() {
cancel() cancel()
} }
fun upload(it: Uri, description: String, context: Context) { fun upload(it: Uri, description: String, server: ServersAvailable, context: Context) {
isUploadingImage = true isUploadingImage = true
contentToAddUrl = null contentToAddUrl = null
ImageUploader.uploadImage( ImageUploader.uploadImage(
uri = it, uri = it,
server = server,
contentResolver = context.contentResolver, contentResolver = context.contentResolver,
onSuccess = { imageUrl, mimeType -> onSuccess = { imageUrl, mimeType ->
createNIP97Record(imageUrl, mimeType, description) createNIP97Record(imageUrl, mimeType, description)
@@ -170,6 +170,7 @@ class NewUserMetadataViewModel : ViewModel() {
ImageUploader.uploadImage( ImageUploader.uploadImage(
uri = it, uri = it,
server = account.defaultFileServer,
contentResolver = context.contentResolver, contentResolver = context.contentResolver,
onSuccess = { imageUrl, mimeType -> onSuccess = { imageUrl, mimeType ->
onUploading(false) onUploading(false)
@@ -233,7 +233,7 @@ fun ChannelScreen(
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, "", context) channelScreenModel.upload(it, "", account.defaultFileServer, context)
} }
}, },
colors = TextFieldDefaults.textFieldColors( colors = TextFieldDefaults.textFieldColors(
@@ -194,7 +194,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr
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)
) { ) {
chatRoomScreenModel.upload(it, "", context) chatRoomScreenModel.upload(it, "", account.defaultFileServer, context)
} }
}, },
colors = TextFieldDefaults.textFieldColors( colors = TextFieldDefaults.textFieldColors(
+5 -1
View File
@@ -303,7 +303,8 @@
<string name="content_description_add_image">Add Image</string> <string name="content_description_add_image">Add Image</string>
<string name="content_description_add_video">Add Video</string> <string name="content_description_add_video">Add Video</string>
<string name="content_description_add_document">Add Document</string> <string name="content_description_add_document">Add Document</string>
<string name="add_content">Create and Add</string>
<string name="add_content">Add to Message</string>
<string name="content_description">Description of the contents</string> <string name="content_description">Description of the contents</string>
<string name="content_description_example">A blue boat in a white sandy beach at sunset</string> <string name="content_description_example">A blue boat in a white sandy beach at sunset</string>
@@ -322,4 +323,7 @@
<string name="zap_type_nonzap">Non-Zap</string> <string name="zap_type_nonzap">Non-Zap</string>
<string name="zap_type_nonzap_explainer">No trace in Nostr, only in Lightning</string> <string name="zap_type_nonzap_explainer">No trace in Nostr, only in Lightning</string>
<string name="file_server">File Server</string>
</resources> </resources>