pollViewModel -> override postViewModel funs,

remove imageUpload from pollView,
This commit is contained in:
toadlyBroodle
2023-03-11 18:16:47 +09:00
parent fba873dab9
commit 0cf891bb4d
5 changed files with 49 additions and 206 deletions

View File

@@ -16,12 +16,10 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
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
@@ -32,7 +30,6 @@ import androidx.compose.ui.unit.dp
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
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
@@ -101,7 +98,7 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
PollButton( PollButton(
onPost = { onPost = {
pollViewModel.sendPoll() pollViewModel.sendPost()
onClose() onClose()
}, },
isActive = pollViewModel.message.text.isNotBlank() && isActive = pollViewModel.message.text.isNotBlank() &&
@@ -148,7 +145,7 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
}, },
placeholder = { placeholder = {
Text( Text(
text = stringResource(R.string.what_s_on_your_mind), text = stringResource(R.string.primary_poll_description),
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f) color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
) )
}, },
@@ -160,40 +157,6 @@ fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
visualTransformation = UrlUserTagTransformation(MaterialTheme.colors.primary), visualTransformation = UrlUserTagTransformation(MaterialTheme.colors.primary),
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content) textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
) )
val myUrlPreview = pollViewModel.urlPreview
if (myUrlPreview != null) {
Row(modifier = Modifier.padding(top = 5.dp)) {
if (isValidURL(myUrlPreview)) {
val removedParamsFromUrl =
myUrlPreview.split("?")[0].lowercase()
if (imageExtension.matcher(removedParamsFromUrl).matches()) {
AsyncImage(
model = myUrlPreview,
contentDescription = myUrlPreview,
contentScale = ContentScale.FillWidth,
modifier = Modifier
.padding(top = 4.dp)
.fillMaxWidth()
.clip(shape = RoundedCornerShape(15.dp))
.border(
1.dp,
MaterialTheme.colors.onSurface.copy(alpha = 0.12f),
RoundedCornerShape(15.dp)
)
)
} else if (videoExtension.matcher(removedParamsFromUrl)
.matches()
) {
VideoView(myUrlPreview)
} else {
UrlPreview(myUrlPreview, myUrlPreview)
}
} else if (noProtocolUrlValidator.matcher(myUrlPreview).matches()) {
UrlPreview("https://$myUrlPreview", myUrlPreview)
}
}
}
} }
} }
@@ -265,6 +228,6 @@ fun PollButton(modifier: Modifier = Modifier, onPost: () -> Unit = {}, isActive:
backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray backgroundColor = if (isActive) MaterialTheme.colors.primary else Color.Gray
) )
) { ) {
Text(text = stringResource(R.string.poll), color = Color.White) Text(text = stringResource(R.string.post_poll), color = Color.White)
} }
} }

View File

@@ -1,174 +1,53 @@
package com.vitorpamplona.amethyst.ui.actions package com.vitorpamplona.amethyst.ui.actions
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
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 com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.* import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.nip19.Nip19 import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.isValidURL
import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
import kotlinx.coroutines.flow.MutableSharedFlow
class NewPollViewModel : ViewModel() { class NewPollViewModel : NewPostViewModel() {
private var account: Account? = null
private var originalNote: Note? = null
var mentions by mutableStateOf<List<User>?>(null) override fun load(account: Account, replyingTo: Note?, quote: Note?) {
var replyTos by mutableStateOf<List<Note>?>(null) super.load(account, replyingTo, quote)
var message by mutableStateOf(TextFieldValue(""))
var urlPreview by mutableStateOf<String?>(null)
var isUploadingImage by mutableStateOf(false)
val imageUploadingError = MutableSharedFlow<String?>()
var userSuggestions by mutableStateOf<List<User>>(emptyList())
var userSuggestionAnchor: TextRange? = null
fun load(account: Account, replyingTo: Note?, quote: Note?) {
originalNote = replyingTo
replyingTo?.let { replyNote ->
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
replyNote.author?.let { replyUser ->
val currentMentions = replyNote.mentions ?: emptyList()
if (currentMentions.contains(replyUser)) {
this.mentions = currentMentions
} else {
this.mentions = currentMentions.plus(replyUser)
}
}
}
quote?.let {
message = TextFieldValue(message.text + "\n\n@${it.idNote()}")
}
this.account = account
} }
fun addUserToMentions(user: User) { override fun addUserToMentions(user: User) {
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user) super.addUserToMentions(user)
} }
fun addNoteToReplyTos(note: Note) { override fun addNoteToReplyTos(note: Note) {
note.author?.let { addUserToMentions(it) } super.addNoteToReplyTos(note)
replyTos = if (replyTos?.contains(note) == true) replyTos else replyTos?.plus(note) ?: listOf(note)
} }
fun tagIndex(user: User): Int { override fun tagIndex(user: User): Int {
// Postr Events assembles replies before mentions in the tag order return super.tagIndex(user)
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
} }
fun tagIndex(note: Note): Int { override fun tagIndex(note: Note): Int {
// Postr Events assembles replies before mentions in the tag order return super.tagIndex(note)
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
} }
fun sendPoll() { override fun sendPost() {
// adds all references to mentions and reply tos super.sendPost()
message.text.split('\n').forEach { paragraph: String ->
paragraph.split(' ').forEach { word: String ->
val results = parseDirtyWordForKey(word)
if (results?.key?.type == Nip19.Type.USER) {
addUserToMentions(LocalCache.getOrCreateUser(results.key.hex))
} else if (results?.key?.type == Nip19.Type.NOTE) {
addNoteToReplyTos(LocalCache.getOrCreateNote(results.key.hex))
} else if (results?.key?.type == Nip19.Type.ADDRESS) {
val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex)
if (note != null) {
addNoteToReplyTos(note)
}
}
}
}
// Tags the text in the correct order.
val newMessage = message.text.split('\n').map { paragraph: String ->
paragraph.split(' ').map { word: String ->
val results = parseDirtyWordForKey(word)
if (results?.key?.type == Nip19.Type.USER) {
val user = LocalCache.getOrCreateUser(results.key.hex)
"#[${tagIndex(user)}]${results.restOfWord}"
} else if (results?.key?.type == Nip19.Type.NOTE) {
val note = LocalCache.getOrCreateNote(results.key.hex)
"#[${tagIndex(note)}]${results.restOfWord}"
} else if (results?.key?.type == Nip19.Type.ADDRESS) {
val note = LocalCache.checkGetOrCreateAddressableNote(results.key.hex)
if (note != null) {
"#[${tagIndex(note)}]${results.restOfWord}"
} else {
word
}
} else {
word
}
}.joinToString(" ")
}.joinToString("\n")
if (originalNote?.channel() != null) {
account?.sendChannelMessage(newMessage, originalNote!!.channel()!!.idHex, originalNote!!, mentions)
} else {
account?.sendPost(newMessage, replyTos, mentions)
}
message = TextFieldValue("")
urlPreview = null
isUploadingImage = false
mentions = null
} }
fun cancel() { override fun cancel() {
message = TextFieldValue("") super.cancel()
urlPreview = null
isUploadingImage = false
mentions = null
} }
fun findUrlInMessage(): String? { override fun findUrlInMessage(): String? {
return message.text.split('\n').firstNotNullOfOrNull { paragraph -> return super.findUrlInMessage()
paragraph.split(' ').firstOrNull { word: String ->
isValidURL(word) || noProtocolUrlValidator.matcher(word).matches()
}
}
} }
fun removeFromReplyList(it: User) { override fun removeFromReplyList(it: User) {
mentions = mentions?.minus(it) super.removeFromReplyList(it)
} }
fun updateMessage(it: TextFieldValue) { override fun updateMessage(it: TextFieldValue) {
message = it super.updateMessage(it)
urlPreview = findUrlInMessage()
if (it.selection.collapsed) {
val lastWord = it.text.substring(0, it.selection.end).substringAfterLast("\n").substringAfterLast(" ")
userSuggestionAnchor = it.selection
if (lastWord.startsWith("@") && lastWord.length > 2) {
userSuggestions = LocalCache.findUsersStartingWith(lastWord.removePrefix("@"))
} else {
userSuggestions = emptyList()
}
}
} }
fun autocompleteWithUser(item: User) { override fun autocompleteWithUser(item: User) {
userSuggestionAnchor?.let { super.autocompleteWithUser(item)
val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
val lastWordStart = it.end - lastWord.length
val wordToInsert = "@${item.pubkeyNpub()} "
message = TextFieldValue(
message.text.replaceRange(lastWordStart, it.end, wordToInsert),
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
)
userSuggestionAnchor = null
userSuggestions = emptyList()
}
} }
} }

View File

@@ -16,9 +16,9 @@ import com.vitorpamplona.amethyst.ui.components.noProtocolUrlValidator
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class NewPostViewModel : ViewModel() { open class NewPostViewModel : ViewModel() {
private var account: Account? = null var account: Account? = null
private var originalNote: Note? = null var originalNote: Note? = null
var mentions by mutableStateOf<List<User>?>(null) var mentions by mutableStateOf<List<User>?>(null)
var replyTos by mutableStateOf<List<Note>?>(null) var replyTos by mutableStateOf<List<Note>?>(null)
@@ -31,7 +31,7 @@ class NewPostViewModel : ViewModel() {
var userSuggestions by mutableStateOf<List<User>>(emptyList()) var userSuggestions by mutableStateOf<List<User>>(emptyList())
var userSuggestionAnchor: TextRange? = null var userSuggestionAnchor: TextRange? = null
fun load(account: Account, replyingTo: Note?, quote: Note?) { open fun load(account: Account, replyingTo: Note?, quote: Note?) {
originalNote = replyingTo originalNote = replyingTo
replyingTo?.let { replyNote -> replyingTo?.let { replyNote ->
this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote) this.replyTos = (replyNote.replyTo ?: emptyList()).plus(replyNote)
@@ -52,26 +52,26 @@ class NewPostViewModel : ViewModel() {
this.account = account this.account = account
} }
fun addUserToMentions(user: User) { open fun addUserToMentions(user: User) {
mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user) mentions = if (mentions?.contains(user) == true) mentions else mentions?.plus(user) ?: listOf(user)
} }
fun addNoteToReplyTos(note: Note) { open fun addNoteToReplyTos(note: Note) {
note.author?.let { addUserToMentions(it) } note.author?.let { addUserToMentions(it) }
replyTos = if (replyTos?.contains(note) == true) replyTos else replyTos?.plus(note) ?: listOf(note) replyTos = if (replyTos?.contains(note) == true) replyTos else replyTos?.plus(note) ?: listOf(note)
} }
fun tagIndex(user: User): Int { open fun tagIndex(user: User): Int {
// Postr Events assembles replies before mentions in the tag order // Postr Events assembles replies before mentions in the tag order
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0) return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.size ?: 0) + (mentions?.indexOf(user) ?: 0)
} }
fun tagIndex(note: Note): Int { open fun tagIndex(note: Note): Int {
// Postr Events assembles replies before mentions in the tag order // Postr Events assembles replies before mentions in the tag order
return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0) return (if (originalNote?.channel() != null) 1 else 0) + (replyTos?.indexOf(note) ?: 0)
} }
fun sendPost() { open fun sendPost() {
// adds all references to mentions and reply tos // adds all references to mentions and reply tos
message.text.split('\n').forEach { paragraph: String -> message.text.split('\n').forEach { paragraph: String ->
paragraph.split(' ').forEach { word: String -> paragraph.split(' ').forEach { word: String ->
@@ -147,14 +147,14 @@ class NewPostViewModel : ViewModel() {
) )
} }
fun cancel() { open fun cancel() {
message = TextFieldValue("") message = TextFieldValue("")
urlPreview = null urlPreview = null
isUploadingImage = false isUploadingImage = false
mentions = null mentions = null
} }
fun findUrlInMessage(): String? { open fun findUrlInMessage(): String? {
return message.text.split('\n').firstNotNullOfOrNull { paragraph -> return message.text.split('\n').firstNotNullOfOrNull { paragraph ->
paragraph.split(' ').firstOrNull { word: String -> paragraph.split(' ').firstOrNull { word: String ->
isValidURL(word) || noProtocolUrlValidator.matcher(word).matches() isValidURL(word) || noProtocolUrlValidator.matcher(word).matches()
@@ -162,11 +162,11 @@ class NewPostViewModel : ViewModel() {
} }
} }
fun removeFromReplyList(it: User) { open fun removeFromReplyList(it: User) {
mentions = mentions?.minus(it) mentions = mentions?.minus(it)
} }
fun updateMessage(it: TextFieldValue) { open fun updateMessage(it: TextFieldValue) {
message = it message = it
urlPreview = findUrlInMessage() urlPreview = findUrlInMessage()
@@ -181,7 +181,7 @@ class NewPostViewModel : ViewModel() {
} }
} }
fun autocompleteWithUser(item: User) { open fun autocompleteWithUser(item: User) {
userSuggestionAnchor?.let { userSuggestionAnchor?.let {
val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ") val lastWord = message.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
val lastWordStart = it.end - lastWord.length val lastWordStart = it.end - lastWord.length

View File

@@ -22,16 +22,16 @@ import com.vitorpamplona.amethyst.ui.actions.NewPollView
@Composable @Composable
fun NewPollButton(account: Account) { fun NewPollButton(account: Account) {
var wantsToPost by remember { var wantsToPoll by remember {
mutableStateOf(false) mutableStateOf(false)
} }
if (wantsToPost) { if (wantsToPoll) {
NewPollView({ wantsToPost = false }, account = account) NewPollView({ wantsToPoll = false }, account = account)
} }
OutlinedButton( OutlinedButton(
onClick = { wantsToPost = true }, onClick = { wantsToPoll = true },
modifier = Modifier.size(55.dp), modifier = Modifier.size(55.dp),
shape = CircleShape, shape = CircleShape,
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.primary), colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.primary),

View File

@@ -220,6 +220,7 @@
<string name="twitter_proof_url_template" translatable="false">https://twitter.com/&lt;user&gt;/status/&lt;proof post&gt;</string> <string name="twitter_proof_url_template" translatable="false">https://twitter.com/&lt;user&gt;/status/&lt;proof post&gt;</string>
<string name="private_conversation_notification">"&lt;Unable to decrypt private message&gt;\n\nYou were cited in a private/encrypted conversation between %1$s and %2$s."</string> <string name="private_conversation_notification">"&lt;Unable to decrypt private message&gt;\n\nYou were cited in a private/encrypted conversation between %1$s and %2$s."</string>
<string name="poll">Poll</string> <string name="post_poll">Post Poll</string>
<string name="primary_poll_description">Primary poll description…</string>
</resources> </resources>