Merge pull request #330 from vitorpamplona/pr/321
Moving new pool button to NewPost
This commit is contained in:
@@ -31,10 +31,6 @@ android {
|
|||||||
applicationIdSuffix '.debug'
|
applicationIdSuffix '.debug'
|
||||||
versionNameSuffix '-DEBUG'
|
versionNameSuffix '-DEBUG'
|
||||||
resValue "string", "app_name", "@string/app_name_debug"
|
resValue "string", "app_name", "@string/app_name_debug"
|
||||||
|
|
||||||
lintOptions{
|
|
||||||
disable 'MissingTranslation'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollClosing(pollViewModel: NewPollViewModel) {
|
fun NewPollClosing(pollViewModel: NewPostViewModel) {
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
pollViewModel.isValidClosedAt.value = true
|
pollViewModel.isValidClosedAt.value = true
|
||||||
@@ -75,5 +75,5 @@ fun NewPollClosing(pollViewModel: NewPollViewModel) {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollClosingPreview() {
|
fun NewPollClosingPreview() {
|
||||||
NewPollClosing(NewPollViewModel())
|
NewPollClosing(NewPostViewModel())
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -21,10 +21,10 @@ import androidx.compose.ui.text.input.KeyboardType
|
|||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollConsensusThreshold(pollViewModel: NewPollViewModel) {
|
fun NewPollConsensusThreshold(pollViewModel: NewPostViewModel) {
|
||||||
var text by rememberSaveable { mutableStateOf("") }
|
var text by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
pollViewModel.isValidConsensusThreshold.value = true
|
pollViewModel.isValidConsensusThreshold.value = true
|
||||||
@@ -75,5 +75,5 @@ fun NewPollConsensusThreshold(pollViewModel: NewPollViewModel) {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollConsensusThresholdPreview() {
|
fun NewPollConsensusThresholdPreview() {
|
||||||
NewPollConsensusThreshold(NewPollViewModel())
|
NewPollConsensusThreshold(NewPostViewModel())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import androidx.compose.foundation.BorderStroke
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollOption(pollViewModel: NewPollViewModel, optionIndex: Int) {
|
fun NewPollOption(pollViewModel: NewPostViewModel, optionIndex: Int) {
|
||||||
val colorInValid = TextFieldDefaults.outlinedTextFieldColors(
|
val colorInValid = TextFieldDefaults.outlinedTextFieldColors(
|
||||||
focusedBorderColor = MaterialTheme.colors.error,
|
focusedBorderColor = MaterialTheme.colors.error,
|
||||||
unfocusedBorderColor = Color.Red
|
unfocusedBorderColor = Color.Red
|
||||||
@@ -25,14 +25,26 @@ fun NewPollOption(pollViewModel: NewPollViewModel, optionIndex: Int) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
val deleteIcon: @Composable (() -> Unit) = {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
pollViewModel.pollOptions.remove(optionIndex)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = stringResource(R.string.clear)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1F),
|
||||||
.weight(1F),
|
|
||||||
value = pollViewModel.pollOptions[optionIndex] ?: "",
|
value = pollViewModel.pollOptions[optionIndex] ?: "",
|
||||||
onValueChange = { pollViewModel.pollOptions[optionIndex] = it },
|
onValueChange = { pollViewModel.pollOptions[optionIndex] = it },
|
||||||
label = {
|
label = {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.poll_option_index).format(optionIndex),
|
text = stringResource(R.string.poll_option_index).format(optionIndex + 1),
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -42,31 +54,17 @@ fun NewPollOption(pollViewModel: NewPollViewModel, optionIndex: Int) {
|
|||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
colors = if (pollViewModel.pollOptions[optionIndex]?.isNotEmpty() == true) colorValid else colorInValid
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.Sentences
|
||||||
|
),
|
||||||
|
// colors = if (pollViewModel.pollOptions[optionIndex]?.isNotEmpty() == true) colorValid else colorInValid,
|
||||||
|
trailingIcon = if (optionIndex > 1) deleteIcon else null
|
||||||
)
|
)
|
||||||
if (optionIndex > 1) {
|
|
||||||
Button(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = 6.dp, top = 2.dp)
|
|
||||||
.imePadding(),
|
|
||||||
onClick = { pollViewModel.pollOptions.remove(optionIndex) },
|
|
||||||
border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.32f)),
|
|
||||||
colors = ButtonDefaults.outlinedButtonColors(
|
|
||||||
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painterResource(id = android.R.drawable.ic_delete),
|
|
||||||
contentDescription = "Remove poll option button",
|
|
||||||
modifier = Modifier.size(18.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollOptionPreview() {
|
fun NewPollOptionPreview() {
|
||||||
NewPollOption(NewPollViewModel(), 0)
|
NewPollOption(NewPostViewModel(), 0)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -17,12 +17,12 @@ 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 com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewPollViewModel
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
import com.vitorpamplona.amethyst.ui.actions.UrlUserTagTransformation
|
||||||
|
|
||||||
@OptIn(ExperimentalComposeUiApi::class)
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollPrimaryDescription(pollViewModel: NewPollViewModel) {
|
fun NewPollPrimaryDescription(pollViewModel: NewPostViewModel) {
|
||||||
// initialize focus reference to be able to request focus programmatically
|
// initialize focus reference to be able to request focus programmatically
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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.ui.actions.NewPollViewModel
|
import com.vitorpamplona.amethyst.ui.actions.NewPostViewModel
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollRecipientsField(pollViewModel: NewPollViewModel, account: Account) {
|
fun NewPollRecipientsField(pollViewModel: NewPostViewModel, account: Account) {
|
||||||
// if no recipients, add user's pubkey
|
// if no recipients, add user's pubkey
|
||||||
if (pollViewModel.zapRecipients.isEmpty()) {
|
if (pollViewModel.zapRecipients.isEmpty()) {
|
||||||
pollViewModel.zapRecipients.add(account.userProfile().pubkeyHex)
|
pollViewModel.zapRecipients.add(account.userProfile().pubkeyHex)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import kotlinx.coroutines.delay
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account) {
|
fun NewPollView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = null, account: Account) {
|
||||||
val pollViewModel: NewPollViewModel = viewModel()
|
val pollViewModel: NewPostViewModel = viewModel()
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -84,7 +84,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() &&
|
||||||
|
|||||||
@@ -1,141 +0,0 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
|
||||||
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
|
||||||
import androidx.compose.runtime.mutableStateMapOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
|
||||||
import com.vitorpamplona.amethyst.model.*
|
|
||||||
import com.vitorpamplona.amethyst.service.nip19.Nip19
|
|
||||||
|
|
||||||
class NewPollViewModel : NewPostViewModel() {
|
|
||||||
|
|
||||||
var zapRecipients = mutableStateListOf<HexKey>()
|
|
||||||
var pollOptions = newStateMapPollOptions()
|
|
||||||
var valueMaximum: Int? = null
|
|
||||||
var valueMinimum: Int? = null
|
|
||||||
var consensusThreshold: Int? = null
|
|
||||||
var closedAt: Int? = null
|
|
||||||
|
|
||||||
var isValidRecipients = mutableStateOf(true)
|
|
||||||
var isValidvalueMaximum = mutableStateOf(true)
|
|
||||||
var isValidvalueMinimum = mutableStateOf(true)
|
|
||||||
var isValidConsensusThreshold = mutableStateOf(true)
|
|
||||||
var isValidClosedAt = mutableStateOf(true)
|
|
||||||
|
|
||||||
override fun load(account: Account, replyingTo: Note?, quote: Note?) {
|
|
||||||
super.load(account, replyingTo, quote)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addUserToMentions(user: User) {
|
|
||||||
super.addUserToMentions(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addNoteToReplyTos(note: Note) {
|
|
||||||
super.addNoteToReplyTos(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tagIndex(user: User): Int {
|
|
||||||
return super.tagIndex(user)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tagIndex(note: Note): Int {
|
|
||||||
return super.tagIndex(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun sendPoll() {
|
|
||||||
// adds all references to mentions and reply tos
|
|
||||||
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?.sendPoll(newMessage, replyTos, mentions)
|
|
||||||
}*/
|
|
||||||
|
|
||||||
account?.sendPoll(newMessage, replyTos, mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt)
|
|
||||||
|
|
||||||
clearPollStates()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
super.cancel()
|
|
||||||
|
|
||||||
clearPollStates()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun findUrlInMessage(): String? {
|
|
||||||
return super.findUrlInMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeFromReplyList(it: User) {
|
|
||||||
super.removeFromReplyList(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun updateMessage(it: TextFieldValue) {
|
|
||||||
super.updateMessage(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun autocompleteWithUser(item: User) {
|
|
||||||
super.autocompleteWithUser(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear all states
|
|
||||||
private fun clearPollStates() {
|
|
||||||
message = TextFieldValue("")
|
|
||||||
urlPreview = null
|
|
||||||
isUploadingImage = false
|
|
||||||
mentions = null
|
|
||||||
|
|
||||||
zapRecipients = mutableStateListOf<HexKey>()
|
|
||||||
pollOptions = newStateMapPollOptions()
|
|
||||||
valueMaximum = null
|
|
||||||
valueMinimum = null
|
|
||||||
consensusThreshold = null
|
|
||||||
closedAt = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> {
|
|
||||||
return mutableStateMapOf(Pair(0, ""), Pair(1, ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,7 +23,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollVoteValueRange(pollViewModel: NewPollViewModel) {
|
fun NewPollVoteValueRange(pollViewModel: NewPostViewModel) {
|
||||||
var textMax by rememberSaveable { mutableStateOf("") }
|
var textMax by rememberSaveable { mutableStateOf("") }
|
||||||
var textMin by rememberSaveable { mutableStateOf("") }
|
var textMin by rememberSaveable { mutableStateOf("") }
|
||||||
|
|
||||||
@@ -122,5 +122,5 @@ fun NewPollVoteValueRange(pollViewModel: NewPollViewModel) {
|
|||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun NewPollVoteValueRangePreview() {
|
fun NewPollVoteValueRangePreview() {
|
||||||
NewPollVoteValueRange(NewPollViewModel())
|
NewPollVoteValueRange(NewPostViewModel())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.actions
|
package com.vitorpamplona.amethyst.ui.actions
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -104,8 +106,7 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
postViewModel.sendPost()
|
postViewModel.sendPost()
|
||||||
onClose()
|
onClose()
|
||||||
},
|
},
|
||||||
isActive = postViewModel.message.text.isNotBlank() &&
|
isActive = postViewModel.canPost()
|
||||||
!postViewModel.isUploadingImage
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +162,26 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
textStyle = LocalTextStyle.current.copy(textDirection = TextDirection.Content)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (postViewModel.wantsPoll) {
|
||||||
|
postViewModel.pollOptions.values.forEachIndexed { index, element ->
|
||||||
|
NewPollOption(postViewModel, index)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { postViewModel.pollOptions[postViewModel.pollOptions.size] = "" },
|
||||||
|
border = BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.32f)),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painterResource(id = android.R.drawable.ic_input_add),
|
||||||
|
contentDescription = "Add poll option button",
|
||||||
|
modifier = Modifier.size(18.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val myUrlPreview = postViewModel.urlPreview
|
val myUrlPreview = postViewModel.urlPreview
|
||||||
if (myUrlPreview != null) {
|
if (myUrlPreview != null) {
|
||||||
Row(modifier = Modifier.padding(top = 5.dp)) {
|
Row(modifier = Modifier.padding(top = 5.dp)) {
|
||||||
@@ -217,16 +238,48 @@ fun NewPostView(onClose: () -> Unit, baseReplyTo: Note? = null, quote: Note? = n
|
|||||||
Row(modifier = Modifier.fillMaxWidth()) {
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
UploadFromGallery(
|
UploadFromGallery(
|
||||||
isUploading = postViewModel.isUploadingImage,
|
isUploading = postViewModel.isUploadingImage,
|
||||||
tint = MaterialTheme.colors.primary,
|
tint = MaterialTheme.colors.onBackground,
|
||||||
modifier = Modifier.padding(bottom = 10.dp)
|
modifier = Modifier.padding(bottom = 10.dp)
|
||||||
) {
|
) {
|
||||||
postViewModel.upload(it, context)
|
postViewModel.upload(it, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AddPollButton(postViewModel.wantsPoll) {
|
||||||
|
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AddPollButton(
|
||||||
|
isPollActive: Boolean,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (!isPollActive) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_poll),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_lists),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package com.vitorpamplona.amethyst.ui.actions
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.runtime.mutableStateMapOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
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
|
||||||
@@ -34,6 +37,21 @@ open 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
|
||||||
|
|
||||||
|
// Polls
|
||||||
|
var wantsPoll by mutableStateOf(false)
|
||||||
|
var zapRecipients = mutableStateListOf<HexKey>()
|
||||||
|
var pollOptions = newStateMapPollOptions()
|
||||||
|
var valueMaximum: Int? = null
|
||||||
|
var valueMinimum: Int? = null
|
||||||
|
var consensusThreshold: Int? = null
|
||||||
|
var closedAt: Int? = null
|
||||||
|
|
||||||
|
var isValidRecipients = mutableStateOf(true)
|
||||||
|
var isValidvalueMaximum = mutableStateOf(true)
|
||||||
|
var isValidvalueMinimum = mutableStateOf(true)
|
||||||
|
var isValidConsensusThreshold = mutableStateOf(true)
|
||||||
|
var isValidClosedAt = mutableStateOf(true)
|
||||||
|
|
||||||
open 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 ->
|
||||||
@@ -130,16 +148,15 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
}.joinToString(" ")
|
}.joinToString(" ")
|
||||||
}.joinToString("\n")
|
}.joinToString("\n")
|
||||||
|
|
||||||
if (originalNote?.channel() != null) {
|
if (wantsPoll) {
|
||||||
|
account?.sendPoll(newMessage, replyTos, mentions, pollOptions, valueMaximum, valueMinimum, consensusThreshold, closedAt)
|
||||||
|
} else if (originalNote?.channel() != null) {
|
||||||
account?.sendChannelMessage(newMessage, originalNote!!.channel()!!.idHex, originalNote!!, mentions)
|
account?.sendChannelMessage(newMessage, originalNote!!.channel()!!.idHex, originalNote!!, mentions)
|
||||||
} else {
|
} else {
|
||||||
account?.sendPost(newMessage, replyTos, mentions)
|
account?.sendPost(newMessage, replyTos, mentions)
|
||||||
}
|
}
|
||||||
|
|
||||||
message = TextFieldValue("")
|
cancel()
|
||||||
urlPreview = null
|
|
||||||
isUploadingImage = false
|
|
||||||
mentions = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun upload(it: Uri, context: Context) {
|
fun upload(it: Uri, context: Context) {
|
||||||
@@ -171,6 +188,14 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
urlPreview = null
|
urlPreview = null
|
||||||
isUploadingImage = false
|
isUploadingImage = false
|
||||||
mentions = null
|
mentions = null
|
||||||
|
|
||||||
|
wantsPoll = false
|
||||||
|
zapRecipients = mutableStateListOf<HexKey>()
|
||||||
|
pollOptions = newStateMapPollOptions()
|
||||||
|
valueMaximum = null
|
||||||
|
valueMinimum = null
|
||||||
|
consensusThreshold = null
|
||||||
|
closedAt = null
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun findUrlInMessage(): String? {
|
open fun findUrlInMessage(): String? {
|
||||||
@@ -214,4 +239,13 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun newStateMapPollOptions(): SnapshotStateMap<Int, String> {
|
||||||
|
return mutableStateMapOf(Pair(0, ""), Pair(1, ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun canPost(): Boolean {
|
||||||
|
return message.text.isNotBlank() && !isUploadingImage &&
|
||||||
|
(!wantsPoll || pollOptions.values.all { it.isNotEmpty() })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.buttons
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NewNoteButton(account: Account) {
|
||||||
|
var wantsToPost by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToPost) {
|
||||||
|
NewPostView({ wantsToPost = false }, account = account)
|
||||||
|
}
|
||||||
|
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = { wantsToPost = true },
|
||||||
|
modifier = Modifier.size(55.dp),
|
||||||
|
shape = CircleShape,
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = MaterialTheme.colors.primary),
|
||||||
|
contentPadding = PaddingValues(0.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_compose),
|
||||||
|
null,
|
||||||
|
modifier = Modifier.size(26.dp),
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -524,17 +524,6 @@ fun NoteComposeInner(
|
|||||||
|
|
||||||
DisplayUncitedHashtags(noteEvent, eventContent, navController)
|
DisplayUncitedHashtags(noteEvent, eventContent, navController)
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
TranslateableRichTextViewer(
|
|
||||||
eventContent,
|
|
||||||
canPreview = canPreview && !makeItShort,
|
|
||||||
Modifier.fillMaxWidth(),
|
|
||||||
noteEvent.tags(),
|
|
||||||
backgroundColor,
|
|
||||||
accountViewModel,
|
|
||||||
navController
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (noteEvent is PollNoteEvent) {
|
if (noteEvent is PollNoteEvent) {
|
||||||
PollNote(
|
PollNote(
|
||||||
|
|||||||
@@ -16,16 +16,20 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
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.compose.ui.window.Popup
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -35,55 +39,119 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PollNote(
|
fun PollNote(
|
||||||
note: Note,
|
baseNote: Note,
|
||||||
canPreview: Boolean,
|
canPreview: Boolean,
|
||||||
backgroundColor: Color,
|
backgroundColor: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
|
val zappedNote = zapsState?.note ?: return
|
||||||
|
|
||||||
val pollViewModel = PollNoteViewModel()
|
val pollViewModel = PollNoteViewModel()
|
||||||
pollViewModel.load(note)
|
pollViewModel.load(zappedNote)
|
||||||
|
|
||||||
pollViewModel.pollEvent?.pollOptions()?.forEach { poll_op ->
|
pollViewModel.pollEvent?.pollOptions()?.forEach { poll_op ->
|
||||||
val optionTally = pollViewModel.optionVoteTally(poll_op.key)
|
val optionTally = pollViewModel.optionVoteTally(poll_op.key)
|
||||||
|
val color = if (
|
||||||
|
pollViewModel.consensusThreshold != null &&
|
||||||
|
optionTally >= pollViewModel.consensusThreshold!!
|
||||||
|
) {
|
||||||
|
Color.Green.copy(alpha = 0.32f)
|
||||||
|
} else {
|
||||||
|
MaterialTheme.colors.primary.copy(alpha = 0.32f)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(vertical = 3.dp)
|
||||||
|
) {
|
||||||
|
if (zappedNote.author == account.userProfile() || zappedNote.isZappedBy(account.userProfile()) == true) {
|
||||||
|
ZapVote(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
pollViewModel,
|
||||||
|
poll_op.key,
|
||||||
|
nonClickablePrepend = {
|
||||||
|
Box(
|
||||||
|
Modifier.fillMaxWidth(0.75f).clip(shape = RoundedCornerShape(15.dp))
|
||||||
|
.border(
|
||||||
|
2.dp,
|
||||||
|
color,
|
||||||
|
RoundedCornerShape(15.dp)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.matchParentSize(),
|
||||||
|
color = color,
|
||||||
|
progress = optionTally
|
||||||
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.End,
|
||||||
|
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "${(optionTally * 100).roundToInt()}%",
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
TranslatableRichTextViewer(
|
TranslatableRichTextViewer(
|
||||||
poll_op.value,
|
poll_op.value,
|
||||||
canPreview,
|
canPreview,
|
||||||
modifier = Modifier
|
Modifier.padding(vertical = 5.dp),
|
||||||
.width(250.dp)
|
|
||||||
.padding(0.dp) // padding outside border
|
|
||||||
.border(BorderStroke(1.dp, MaterialTheme.colors.onSurface.copy(alpha = 0.32f)))
|
|
||||||
.padding(4.dp), // padding between border and text
|
|
||||||
pollViewModel.pollEvent?.tags(),
|
pollViewModel.pollEvent?.tags(),
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
navController
|
navController
|
||||||
)
|
)
|
||||||
|
|
||||||
ZapVote(note, accountViewModel, pollViewModel, poll_op.key)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Row(
|
}
|
||||||
verticalAlignment = Alignment.CenterVertically
|
},
|
||||||
|
clickablePrepend = {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ZapVote(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
pollViewModel,
|
||||||
|
poll_op.key,
|
||||||
|
nonClickablePrepend = {},
|
||||||
|
clickablePrepend = {
|
||||||
|
Box(
|
||||||
|
Modifier.fillMaxWidth(0.75f)
|
||||||
|
.clip(shape = RoundedCornerShape(15.dp))
|
||||||
|
.border(
|
||||||
|
2.dp,
|
||||||
|
MaterialTheme.colors.primary,
|
||||||
|
RoundedCornerShape(15.dp)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
// only show tallies after user has zapped note
|
TranslatableRichTextViewer(
|
||||||
if (note.isZappedBy(accountViewModel.userProfile())) {
|
poll_op.value,
|
||||||
LinearProgressIndicator(
|
canPreview,
|
||||||
modifier = Modifier.width(250.dp),
|
Modifier.padding(vertical = 5.dp, horizontal = 10.dp),
|
||||||
color = if (
|
pollViewModel.pollEvent?.tags(),
|
||||||
pollViewModel.consensusThreshold != null &&
|
backgroundColor,
|
||||||
optionTally >= pollViewModel.consensusThreshold!!
|
accountViewModel,
|
||||||
) {
|
navController
|
||||||
Color.Green
|
)
|
||||||
} else { MaterialTheme.colors.primary },
|
}
|
||||||
progress = optionTally
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,11 +165,10 @@ fun ZapVote(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
pollViewModel: PollNoteViewModel,
|
pollViewModel: PollNoteViewModel,
|
||||||
pollOption: Int,
|
pollOption: Int,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier,
|
||||||
|
nonClickablePrepend: @Composable () -> Unit,
|
||||||
|
clickablePrepend: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val zapsState by baseNote.live().zaps.observeAsState()
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
val zappedNote = zapsState?.note
|
val zappedNote = zapsState?.note
|
||||||
|
|
||||||
@@ -112,11 +179,14 @@ fun ZapVote(
|
|||||||
|
|
||||||
var zappingProgress by remember { mutableStateOf(0f) }
|
var zappingProgress by remember { mutableStateOf(0f) }
|
||||||
|
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
|
||||||
|
nonClickablePrepend()
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier
|
modifier = Modifier.combinedClickable(
|
||||||
.then(Modifier.size(20.dp))
|
|
||||||
.combinedClickable(
|
|
||||||
role = Role.Button,
|
role = Role.Button,
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = rememberRipple(bounded = false, radius = 24.dp),
|
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||||
@@ -141,34 +211,63 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} else if (pollViewModel.isVoteAmountAtomic()) {
|
} else if (pollViewModel.isVoteAmountAtomic() && pollViewModel.isPollOptionZappedBy(pollOption, accountViewModel.userProfile())) {
|
||||||
// only allow one vote per option when min==max, i.e. atomic vote amount specified
|
// only allow one vote per option when min==max, i.e. atomic vote amount specified
|
||||||
if (pollViewModel.isPollOptionZappedBy(pollOption, account.userProfile())) {
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast
|
Toast
|
||||||
.makeText(context, R.string.one_vote_per_user_on_atomic_votes, Toast.LENGTH_SHORT)
|
.makeText(
|
||||||
|
context,
|
||||||
|
R.string.one_vote_per_user_on_atomic_votes,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
return@combinedClickable
|
return@combinedClickable
|
||||||
} else { wantsToZap = true }
|
} else if (account.zapAmountChoices.size == 1 && pollViewModel.isValidInputVoteAmount(account.zapAmountChoices.first())) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
account.zapAmountChoices.first() * 1000,
|
||||||
|
pollOption,
|
||||||
|
"",
|
||||||
|
context,
|
||||||
|
onError = {
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
Toast
|
||||||
|
.makeText(context, it, Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProgress = {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
zappingProgress = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
wantsToZap = true
|
wantsToZap = true
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
onLongClick = {}
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
if (wantsToZap) {
|
if (wantsToZap) {
|
||||||
ZapVoteAmountChoicePopup(
|
FilteredZapAmountChoicePopup(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
pollViewModel,
|
pollViewModel,
|
||||||
pollOption,
|
pollOption,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
wantsToZap = false
|
wantsToZap = false
|
||||||
|
zappingProgress = 0f
|
||||||
|
},
|
||||||
|
onChangeAmount = {
|
||||||
|
wantsToZap = false
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -180,7 +279,9 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollViewModel.isPollOptionZappedBy(pollOption, account.userProfile())) {
|
clickablePrepend()
|
||||||
|
|
||||||
|
if (pollViewModel.isPollOptionZappedBy(pollOption, accountViewModel.userProfile())) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Bolt,
|
imageVector = Icons.Default.Bolt,
|
||||||
contentDescription = stringResource(R.string.zaps),
|
contentDescription = stringResource(R.string.zaps),
|
||||||
@@ -198,7 +299,7 @@ fun ZapVote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only show tallies after a user has zapped note
|
// only show tallies after a user has zapped note
|
||||||
if (zappedNote?.isZappedBy(account.userProfile()) == true) {
|
if (baseNote.author == accountViewModel.userProfile() || zappedNote?.isZappedBy(accountViewModel.userProfile()) == true) {
|
||||||
Text(
|
Text(
|
||||||
showAmount(pollViewModel.zappedPollOptionAmount(pollOption)),
|
showAmount(pollViewModel.zappedPollOptionAmount(pollOption)),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
@@ -208,6 +309,98 @@ fun ZapVote(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun FilteredZapAmountChoicePopup(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
pollViewModel: PollNoteViewModel,
|
||||||
|
pollOption: Int,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onChangeAmount: () -> Unit,
|
||||||
|
onError: (text: String) -> Unit,
|
||||||
|
onProgress: (percent: Float) -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
val zapMessage = ""
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val options = account.zapAmountChoices.filter { pollViewModel.isValidInputVoteAmount(it) }.toMutableList()
|
||||||
|
if (options.isEmpty()) {
|
||||||
|
pollViewModel.valueMinimum?.let { minimum ->
|
||||||
|
pollViewModel.valueMaximum?.let { maximum ->
|
||||||
|
if (minimum != maximum) {
|
||||||
|
options.add(((minimum + maximum) / 2).toLong())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pollViewModel.valueMinimum?.let { options.add(it.toLong()) }
|
||||||
|
pollViewModel.valueMaximum?.let { options.add(it.toLong()) }
|
||||||
|
val sortedOptions = options.toSet().sorted()
|
||||||
|
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.BottomCenter,
|
||||||
|
offset = IntOffset(0, -100),
|
||||||
|
onDismissRequest = { onDismiss() }
|
||||||
|
) {
|
||||||
|
FlowRow(horizontalArrangement = Arrangement.Center) {
|
||||||
|
sortedOptions.forEach { amountInSats ->
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
amountInSats * 1000,
|
||||||
|
pollOption,
|
||||||
|
zapMessage,
|
||||||
|
context,
|
||||||
|
onError,
|
||||||
|
onProgress
|
||||||
|
)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"⚡ ${showAmount(amountInSats.toBigDecimal().setScale(1))}",
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.zap(
|
||||||
|
baseNote,
|
||||||
|
amountInSats * 1000,
|
||||||
|
pollOption,
|
||||||
|
zapMessage,
|
||||||
|
context,
|
||||||
|
onError,
|
||||||
|
onProgress
|
||||||
|
)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
onChangeAmount()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapVoteAmountChoicePopup(
|
fun ZapVoteAmountChoicePopup(
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class PollNoteViewModel {
|
|||||||
var pollEvent: PollNoteEvent? = null
|
var pollEvent: PollNoteEvent? = null
|
||||||
private var pollOptions: Map<Int, String>? = null
|
private var pollOptions: Map<Int, String>? = null
|
||||||
var valueMaximum: Int? = null
|
var valueMaximum: Int? = null
|
||||||
private var valueMinimum: Int? = null
|
var valueMinimum: Int? = null
|
||||||
private var closedAt: Int? = null
|
private var closedAt: Int? = null
|
||||||
var consensusThreshold: Float? = null
|
var consensusThreshold: Float? = null
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
import com.vitorpamplona.amethyst.buttons.NewChannelButton
|
||||||
import com.vitorpamplona.amethyst.ui.buttons.FabColumn
|
import com.vitorpamplona.amethyst.ui.buttons.NewNoteButton
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.*
|
import com.vitorpamplona.amethyst.ui.navigation.*
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.AccountSwitchBottomSheet
|
import com.vitorpamplona.amethyst.ui.navigation.AccountSwitchBottomSheet
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
import com.vitorpamplona.amethyst.ui.navigation.AppBottomBar
|
||||||
@@ -91,7 +91,7 @@ fun FloatingButtons(navController: NavHostController, accountViewModel: AccountS
|
|||||||
// Does nothing.
|
// Does nothing.
|
||||||
}
|
}
|
||||||
is AccountState.LoggedIn -> {
|
is AccountState.LoggedIn -> {
|
||||||
FabColumn(state.account)
|
NewNoteButton(state.account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user