mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 16:30:49 +00:00
Custom reactions
This commit is contained in:
parent
a9e1ce5aec
commit
52bd6b4443
@ -49,6 +49,7 @@ private object PrefKeys {
|
|||||||
const val LANGUAGE_PREFS = "languagePreferences"
|
const val LANGUAGE_PREFS = "languagePreferences"
|
||||||
const val TRANSLATE_TO = "translateTo"
|
const val TRANSLATE_TO = "translateTo"
|
||||||
const val ZAP_AMOUNTS = "zapAmounts"
|
const val ZAP_AMOUNTS = "zapAmounts"
|
||||||
|
const val REACTION_CHOICES = "reactionChoices"
|
||||||
const val DEFAULT_ZAPTYPE = "defaultZapType"
|
const val DEFAULT_ZAPTYPE = "defaultZapType"
|
||||||
const val DEFAULT_FILE_SERVER = "defaultFileServer"
|
const val DEFAULT_FILE_SERVER = "defaultFileServer"
|
||||||
const val DEFAULT_HOME_FOLLOW_LIST = "defaultHomeFollowList"
|
const val DEFAULT_HOME_FOLLOW_LIST = "defaultHomeFollowList"
|
||||||
@ -208,6 +209,7 @@ object LocalPreferences {
|
|||||||
putString(PrefKeys.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
|
putString(PrefKeys.LANGUAGE_PREFS, gson.toJson(account.languagePreferences))
|
||||||
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.REACTION_CHOICES, gson.toJson(account.reactionChoices))
|
||||||
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.DEFAULT_FILE_SERVER, gson.toJson(account.defaultFileServer))
|
||||||
putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, account.defaultHomeFollowList)
|
putString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, account.defaultHomeFollowList)
|
||||||
@ -258,6 +260,11 @@ object LocalPreferences {
|
|||||||
object : TypeToken<List<Long>>() {}.type
|
object : TypeToken<List<Long>>() {}.type
|
||||||
) ?: listOf(500L, 1000L, 5000L)
|
) ?: listOf(500L, 1000L, 5000L)
|
||||||
|
|
||||||
|
val reactionChoices = gson.fromJson<List<String>>(
|
||||||
|
getString(PrefKeys.REACTION_CHOICES, "[]"),
|
||||||
|
object : TypeToken<List<String>>() {}.type
|
||||||
|
).ifEmpty { listOf("+") } ?: listOf("+")
|
||||||
|
|
||||||
val defaultZapType = gson.fromJson(
|
val defaultZapType = gson.fromJson(
|
||||||
getString(PrefKeys.DEFAULT_ZAPTYPE, "PUBLIC"),
|
getString(PrefKeys.DEFAULT_ZAPTYPE, "PUBLIC"),
|
||||||
object : TypeToken<LnZapEvent.ZapType>() {}.type
|
object : TypeToken<LnZapEvent.ZapType>() {}.type
|
||||||
@ -314,28 +321,29 @@ object LocalPreferences {
|
|||||||
val warnAboutReports = getBoolean(PrefKeys.WARN_ABOUT_REPORTS, true)
|
val warnAboutReports = getBoolean(PrefKeys.WARN_ABOUT_REPORTS, true)
|
||||||
|
|
||||||
val a = Account(
|
val a = Account(
|
||||||
Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
loggedIn = Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||||
followingChannels,
|
followingChannels = followingChannels,
|
||||||
hiddenUsers,
|
hiddenUsers = hiddenUsers,
|
||||||
localRelays,
|
localRelays = localRelays,
|
||||||
dontTranslateFrom,
|
dontTranslateFrom = dontTranslateFrom,
|
||||||
languagePreferences,
|
languagePreferences = languagePreferences,
|
||||||
translateTo,
|
translateTo = translateTo,
|
||||||
zapAmountChoices,
|
zapAmountChoices = zapAmountChoices,
|
||||||
defaultZapType,
|
reactionChoices = reactionChoices,
|
||||||
defaultFileServer,
|
defaultZapType = defaultZapType,
|
||||||
defaultHomeFollowList,
|
defaultFileServer = defaultFileServer,
|
||||||
defaultStoriesFollowList,
|
defaultHomeFollowList = defaultHomeFollowList,
|
||||||
defaultNotificationFollowList,
|
defaultStoriesFollowList = defaultStoriesFollowList,
|
||||||
zapPaymentRequestServer,
|
defaultNotificationFollowList = defaultNotificationFollowList,
|
||||||
hideDeleteRequestDialog,
|
zapPaymentRequest = zapPaymentRequestServer,
|
||||||
hideBlockAlertDialog,
|
hideDeleteRequestDialog = hideDeleteRequestDialog,
|
||||||
latestContactList,
|
hideBlockAlertDialog = hideBlockAlertDialog,
|
||||||
proxy,
|
backupContactList = latestContactList,
|
||||||
proxyPort,
|
proxy = proxy,
|
||||||
showSensitiveContent,
|
proxyPort = proxyPort,
|
||||||
warnAboutReports,
|
showSensitiveContent = showSensitiveContent,
|
||||||
filterSpam
|
warnAboutPostsWithReports = warnAboutReports,
|
||||||
|
filterSpamFromStrangers = filterSpam
|
||||||
)
|
)
|
||||||
|
|
||||||
return a
|
return a
|
||||||
|
@ -56,6 +56,7 @@ class Account(
|
|||||||
var languagePreferences: Map<String, String> = mapOf(),
|
var languagePreferences: Map<String, String> = mapOf(),
|
||||||
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 reactionChoices: List<String> = listOf("+"),
|
||||||
var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE,
|
var defaultZapType: LnZapEvent.ZapType = LnZapEvent.ZapType.PRIVATE,
|
||||||
var defaultFileServer: ServersAvailable = ServersAvailable.NOSTR_BUILD,
|
var defaultFileServer: ServersAvailable = ServersAvailable.NOSTR_BUILD,
|
||||||
var defaultHomeFollowList: String = KIND3_FOLLOWS,
|
var defaultHomeFollowList: String = KIND3_FOLLOWS,
|
||||||
@ -143,8 +144,8 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reactionTo(note: Note): List<Note> {
|
fun reactionTo(note: Note, reaction: String): List<Note> {
|
||||||
return note.reactedBy(userProfile(), "+")
|
return note.reactedBy(userProfile(), reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasBoosted(note: Note): Boolean {
|
fun hasBoosted(note: Note): Boolean {
|
||||||
@ -155,20 +156,20 @@ class Account(
|
|||||||
return note.boostedBy(userProfile())
|
return note.boostedBy(userProfile())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasReacted(note: Note): Boolean {
|
fun hasReacted(note: Note, reaction: String): Boolean {
|
||||||
return note.hasReacted(userProfile(), "+")
|
return note.hasReacted(userProfile(), reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reactTo(note: Note) {
|
fun reactTo(note: Note, reaction: String) {
|
||||||
if (!isWriteable()) return
|
if (!isWriteable()) return
|
||||||
|
|
||||||
if (hasReacted(note)) {
|
if (hasReacted(note, reaction)) {
|
||||||
// has already liked this note
|
// has already liked this note
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
note.event?.let {
|
note.event?.let {
|
||||||
val event = ReactionEvent.createLike(it, loggedIn.privKey!!)
|
val event = ReactionEvent.create(reaction, it, loggedIn.privKey!!)
|
||||||
Client.send(event)
|
Client.send(event)
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
@ -863,6 +864,12 @@ class Account(
|
|||||||
saveable.invalidateData()
|
saveable.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeReactionTypes(newTypes: List<String>) {
|
||||||
|
reactionChoices = newTypes
|
||||||
|
live.invalidateData()
|
||||||
|
saveable.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
fun changeZapPaymentRequest(newServer: Nip47URI?) {
|
fun changeZapPaymentRequest(newServer: Nip47URI?) {
|
||||||
zapPaymentRequest = newServer
|
zapPaymentRequest = newServer
|
||||||
live.invalidateData()
|
live.invalidateData()
|
||||||
|
@ -554,17 +554,8 @@ object LocalCache {
|
|||||||
|
|
||||||
// Log.d("RE", "New Reaction ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
// Log.d("RE", "New Reaction ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
if (
|
repliesTo.forEach {
|
||||||
event.content == "" ||
|
it.addReaction(note)
|
||||||
event.content == "+" ||
|
|
||||||
event.content == "\u2764\uFE0F" || // red heart
|
|
||||||
event.content == "\uD83E\uDD19" || // call me hand
|
|
||||||
event.content == "\uD83D\uDC4D" // thumbs up
|
|
||||||
) {
|
|
||||||
// Counts the replies
|
|
||||||
repliesTo.forEach {
|
|
||||||
it.addReaction(note)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshObservers(note)
|
refreshObservers(note)
|
||||||
|
@ -43,7 +43,7 @@ open class Note(val idHex: String) {
|
|||||||
// These fields are updated every time an event related to this note is received.
|
// These fields are updated every time an event related to this note is received.
|
||||||
var replies = setOf<Note>()
|
var replies = setOf<Note>()
|
||||||
private set
|
private set
|
||||||
var reactions = setOf<Note>()
|
var reactions = mapOf<String, Set<Note>>()
|
||||||
private set
|
private set
|
||||||
var boosts = setOf<Note>()
|
var boosts = setOf<Note>()
|
||||||
private set
|
private set
|
||||||
@ -134,8 +134,20 @@ open class Note(val idHex: String) {
|
|||||||
liveSet?.boosts?.invalidateData()
|
liveSet?.boosts?.invalidateData()
|
||||||
}
|
}
|
||||||
fun removeReaction(note: Note) {
|
fun removeReaction(note: Note) {
|
||||||
reactions = reactions - note
|
val reaction = note.event?.content() ?: "+"
|
||||||
liveSet?.reactions?.invalidateData()
|
|
||||||
|
if (reaction in reactions.keys && reactions[reaction]?.contains(note) == true) {
|
||||||
|
reactions[reaction]?.let {
|
||||||
|
val newList = it.minus(note)
|
||||||
|
if (newList.isEmpty()) {
|
||||||
|
reactions = reactions.minus(reaction)
|
||||||
|
} else {
|
||||||
|
reactions = reactions + Pair(reaction, newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
liveSet?.reactions?.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeReport(deleteNote: Note) {
|
fun removeReport(deleteNote: Note) {
|
||||||
@ -203,8 +215,13 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addReaction(note: Note) {
|
fun addReaction(note: Note) {
|
||||||
if (note !in reactions) {
|
val reaction = note.event?.content() ?: "+"
|
||||||
reactions = reactions + note
|
|
||||||
|
if (reaction !in reactions.keys) {
|
||||||
|
reactions = reactions + Pair(reaction, setOf(note))
|
||||||
|
liveSet?.reactions?.invalidateData()
|
||||||
|
} else if (reactions[reaction]?.contains(note) == false) {
|
||||||
|
reactions = reactions + Pair(reaction, (reactions[reaction] ?: emptySet()) + note)
|
||||||
liveSet?.reactions?.invalidateData()
|
liveSet?.reactions?.invalidateData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -243,8 +260,10 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isReactedBy(user: User): Boolean {
|
fun isReactedBy(user: User): String? {
|
||||||
return reactions.any { it.author?.pubkeyHex == user.pubkeyHex }
|
return reactions.filter {
|
||||||
|
it.value.any { it.author?.pubkeyHex == user.pubkeyHex }
|
||||||
|
}.keys.firstOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isBoostedBy(user: User): Boolean {
|
fun isBoostedBy(user: User): Boolean {
|
||||||
@ -269,6 +288,10 @@ open class Note(val idHex: String) {
|
|||||||
}.flatten()
|
}.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun countReactions(): Int {
|
||||||
|
return reactions.values.sumOf { it.size }
|
||||||
|
}
|
||||||
|
|
||||||
fun zappedAmount(privKey: ByteArray?, walletServicePubkey: ByteArray?): BigDecimal {
|
fun zappedAmount(privKey: ByteArray?, walletServicePubkey: ByteArray?): BigDecimal {
|
||||||
// Regular Zap Receipts
|
// Regular Zap Receipts
|
||||||
val completedZaps = zaps.asSequence()
|
val completedZaps = zaps.asSequence()
|
||||||
@ -361,7 +384,11 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reactedBy(loggedIn: User, content: String): List<Note> {
|
fun reactedBy(loggedIn: User, content: String): List<Note> {
|
||||||
return reactions.filter { it.author == loggedIn && it.event?.content() == content }
|
return reactions[content]?.filter { it.author == loggedIn && it.event?.content() == content } ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reactedBy(loggedIn: User): List<String> {
|
||||||
|
return reactions.filter { it.value.any { it.author == loggedIn } }.mapNotNull { it.key }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||||
|
@ -145,7 +145,7 @@ private fun Galeries(
|
|||||||
) {
|
) {
|
||||||
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } }
|
val zapEvents by remember { derivedStateOf { multiSetCard.zapEvents } }
|
||||||
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
|
val boostEvents by remember { derivedStateOf { multiSetCard.boostEvents } }
|
||||||
val likeEvents by remember { derivedStateOf { multiSetCard.likeEvents } }
|
val likeEvents by remember { derivedStateOf { multiSetCard.likeEventsByType } }
|
||||||
|
|
||||||
val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } }
|
val hasZapEvents by remember { derivedStateOf { multiSetCard.zapEvents.isNotEmpty() } }
|
||||||
val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } }
|
val hasBoostEvents by remember { derivedStateOf { multiSetCard.boostEvents.isNotEmpty() } }
|
||||||
@ -160,38 +160,52 @@ private fun Galeries(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasLikeEvents) {
|
if (hasLikeEvents) {
|
||||||
RenderLikeGallery(likeEvents, backgroundColor, nav, accountViewModel)
|
likeEvents.forEach {
|
||||||
|
RenderLikeGallery(it.key, it.value, backgroundColor, nav, accountViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderLikeGallery(
|
fun RenderLikeGallery(
|
||||||
|
reactionType: String,
|
||||||
likeEvents: ImmutableList<Note>,
|
likeEvents: ImmutableList<Note>,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
Row(remember { Modifier.fillMaxWidth() }) {
|
val isNotEmpty = remember(likeEvents) {
|
||||||
Box(
|
likeEvents.isNotEmpty()
|
||||||
modifier = remember {
|
}
|
||||||
Modifier
|
|
||||||
.width(55.dp)
|
if (isNotEmpty) {
|
||||||
.padding(end = 5.dp)
|
Row(remember { Modifier.fillMaxWidth() }) {
|
||||||
}
|
Box(
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_liked),
|
|
||||||
null,
|
|
||||||
modifier = remember {
|
modifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
.size(16.dp)
|
.width(55.dp)
|
||||||
|
.padding(end = 5.dp)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
val modifier = remember {
|
||||||
|
Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
},
|
}
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorGallery(likeEvents, backgroundColor, nav, accountViewModel)
|
when (reactionType) {
|
||||||
|
"+" -> Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
|
null,
|
||||||
|
modifier = remember { modifier.size(18.dp) },
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
"-" -> Text(text = "\uD83D\uDC4E", modifier = modifier)
|
||||||
|
else -> Text(text = reactionType, modifier = modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorGallery(likeEvents, backgroundColor, nav, accountViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +70,7 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -190,7 +191,7 @@ private fun ReactionDetailGallery(
|
|||||||
Column() {
|
Column() {
|
||||||
val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.mapNotNull { it.value?.let { zapEvent -> CombinedZap(it.key, zapEvent) } }.toImmutableList() } }
|
val zapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.mapNotNull { it.value?.let { zapEvent -> CombinedZap(it.key, zapEvent) } }.toImmutableList() } }
|
||||||
val boostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.toImmutableList() } }
|
val boostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.toImmutableList() } }
|
||||||
val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableList() } }
|
val likeEvents by remember(reactionsState) { derivedStateOf { baseNote.reactions.toImmutableMap() } }
|
||||||
|
|
||||||
val hasZapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.isNotEmpty() } }
|
val hasZapEvents by remember(zapsState) { derivedStateOf { baseNote.zaps.isNotEmpty() } }
|
||||||
val hasBoostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.isNotEmpty() } }
|
val hasBoostEvents by remember(boostsState) { derivedStateOf { baseNote.boosts.isNotEmpty() } }
|
||||||
@ -215,12 +216,16 @@ private fun ReactionDetailGallery(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasLikeEvents) {
|
if (hasLikeEvents) {
|
||||||
RenderLikeGallery(
|
likeEvents.forEach {
|
||||||
likeEvents,
|
val reactions = remember(it.value) { it.value.toImmutableList() }
|
||||||
backgroundColor,
|
RenderLikeGallery(
|
||||||
nav,
|
it.key,
|
||||||
accountViewModel
|
reactions,
|
||||||
)
|
backgroundColor,
|
||||||
|
nav,
|
||||||
|
accountViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -429,6 +434,7 @@ fun BoostText(baseNote: Note, grayTint: Color) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun LikeReaction(
|
fun LikeReaction(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
@ -444,29 +450,50 @@ fun LikeReaction(
|
|||||||
Modifier.size(iconSize)
|
Modifier.size(iconSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(
|
var wantsToChangeReactionSymbol by remember { mutableStateOf(false) }
|
||||||
modifier = iconButtonModifier,
|
var wantsToReact by remember { mutableStateOf(false) }
|
||||||
onClick = {
|
|
||||||
if (accountViewModel.isWriteable()) {
|
Row(
|
||||||
scope.launch(Dispatchers.IO) {
|
verticalAlignment = CenterVertically,
|
||||||
if (accountViewModel.hasReactedTo(baseNote)) {
|
modifier = iconButtonModifier.combinedClickable(
|
||||||
accountViewModel.deleteReactionTo(baseNote)
|
role = Role.Button,
|
||||||
} else {
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
accountViewModel.reactTo(baseNote)
|
indication = rememberRipple(bounded = false, radius = 24.dp),
|
||||||
|
onClick = {
|
||||||
|
likeClick(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
scope,
|
||||||
|
context,
|
||||||
|
onMultipleChoices = {
|
||||||
|
wantsToReact = true
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
} else {
|
},
|
||||||
scope.launch {
|
onLongClick = {
|
||||||
Toast.makeText(
|
wantsToChangeReactionSymbol = true
|
||||||
context,
|
|
||||||
context.getString(R.string.login_with_a_private_key_to_like_posts),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
) {
|
) {
|
||||||
LikeIcon(baseNote, heartSize, grayTint, accountViewModel.userProfile())
|
LikeIcon(baseNote, heartSize, grayTint, accountViewModel.userProfile())
|
||||||
|
|
||||||
|
if (wantsToChangeReactionSymbol) {
|
||||||
|
UpdateReactionTypeDialog({ wantsToChangeReactionSymbol = false }, accountViewModel = accountViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToReact) {
|
||||||
|
ReactionChoicePopup(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
onDismiss = {
|
||||||
|
wantsToReact = false
|
||||||
|
},
|
||||||
|
onChangeAmount = {
|
||||||
|
wantsToReact = false
|
||||||
|
wantsToChangeReactionSymbol = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LikeText(baseNote, grayTint)
|
LikeText(baseNote, grayTint)
|
||||||
@ -476,15 +503,15 @@ fun LikeReaction(
|
|||||||
fun LikeIcon(baseNote: Note, iconSize: Dp = 20.dp, grayTint: Color, loggedIn: User) {
|
fun LikeIcon(baseNote: Note, iconSize: Dp = 20.dp, grayTint: Color, loggedIn: User) {
|
||||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||||
|
|
||||||
var wasReactedByLoggedIn by remember(reactionsState) {
|
var reactionType by remember(baseNote) {
|
||||||
mutableStateOf(false)
|
mutableStateOf<String?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = reactionsState) {
|
LaunchedEffect(key1 = reactionsState) {
|
||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
val newWasReactedByLoggedIn = reactionsState?.note?.isReactedBy(loggedIn) == true
|
val newReactionType = reactionsState?.note?.isReactedBy(loggedIn)
|
||||||
if (wasReactedByLoggedIn != newWasReactedByLoggedIn) {
|
if (reactionType != newReactionType) {
|
||||||
wasReactedByLoggedIn = newWasReactedByLoggedIn
|
reactionType = newReactionType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,13 +520,19 @@ fun LikeIcon(baseNote: Note, iconSize: Dp = 20.dp, grayTint: Color, loggedIn: Us
|
|||||||
Modifier.size(iconSize)
|
Modifier.size(iconSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasReactedByLoggedIn) {
|
if (reactionType != null) {
|
||||||
Icon(
|
when (reactionType) {
|
||||||
painter = painterResource(R.drawable.ic_liked),
|
"+" -> {
|
||||||
null,
|
Icon(
|
||||||
modifier = iconModifier,
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
tint = Color.Unspecified
|
null,
|
||||||
)
|
modifier = iconModifier,
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"-" -> Text(text = "\uD83D\uDC4E")
|
||||||
|
else -> Text(text = reactionType!!)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_like),
|
painter = painterResource(R.drawable.ic_like),
|
||||||
@ -520,7 +553,7 @@ fun LikeText(baseNote: Note, grayTint: Color) {
|
|||||||
|
|
||||||
LaunchedEffect(key1 = reactionsState) {
|
LaunchedEffect(key1 = reactionsState) {
|
||||||
launch(Dispatchers.Default) {
|
launch(Dispatchers.Default) {
|
||||||
val newReactionsCount = " " + showCount(reactionsState?.note?.reactions?.size)
|
val newReactionsCount = " " + showCount(reactionsState?.note?.countReactions())
|
||||||
if (reactionsCount != newReactionsCount) {
|
if (reactionsCount != newReactionsCount) {
|
||||||
reactionsCount = newReactionsCount
|
reactionsCount = newReactionsCount
|
||||||
}
|
}
|
||||||
@ -534,6 +567,47 @@ fun LikeText(baseNote: Note, grayTint: Color) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun likeClick(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
context: Context,
|
||||||
|
onMultipleChoices: () -> Unit
|
||||||
|
) {
|
||||||
|
if (accountViewModel.account.reactionChoices.isEmpty()) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.no_reaction_type_setup_long_press_to_change),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (!accountViewModel.isWriteable()) {
|
||||||
|
scope.launch {
|
||||||
|
Toast
|
||||||
|
.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.login_with_a_private_key_to_be_able_to_send_zaps),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (accountViewModel.account.reactionChoices.size == 1) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val reaction = accountViewModel.account.reactionChoices.first()
|
||||||
|
if (accountViewModel.hasReactedTo(baseNote, reaction)) {
|
||||||
|
accountViewModel.deleteReactionTo(baseNote, reaction)
|
||||||
|
} else {
|
||||||
|
accountViewModel.reactTo(baseNote, reaction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (accountViewModel.account.reactionChoices.size > 1) {
|
||||||
|
onMultipleChoices()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
fun ZapReaction(
|
fun ZapReaction(
|
||||||
@ -863,6 +937,95 @@ private fun BoostTypeChoicePopup(baseNote: Note, accountViewModel: AccountViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ReactionChoicePopup(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onChangeAmount: () -> Unit
|
||||||
|
) {
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
val account = accountState?.account ?: return
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val toRemove = remember {
|
||||||
|
baseNote.reactedBy(account.userProfile()).toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup(
|
||||||
|
alignment = Alignment.BottomCenter,
|
||||||
|
offset = IntOffset(0, -50),
|
||||||
|
onDismissRequest = { onDismiss() }
|
||||||
|
) {
|
||||||
|
FlowRow(horizontalArrangement = Arrangement.Center) {
|
||||||
|
account.reactionChoices.forEach { reactionType ->
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.reactToOrDelete(
|
||||||
|
baseNote,
|
||||||
|
reactionType
|
||||||
|
)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
shape = ButtonBorder,
|
||||||
|
colors = ButtonDefaults
|
||||||
|
.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
val thisModifier = remember(reactionType) {
|
||||||
|
Modifier.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
accountViewModel.reactToOrDelete(
|
||||||
|
baseNote,
|
||||||
|
reactionType
|
||||||
|
)
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
onChangeAmount()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val removeSymbol = remember(reactionType) {
|
||||||
|
if (reactionType in toRemove) {
|
||||||
|
" ✖"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
when (reactionType) {
|
||||||
|
"+" -> {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
|
null,
|
||||||
|
modifier = remember { thisModifier.size(16.dp) },
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
Text(text = removeSymbol, color = Color.White, textAlign = TextAlign.Center, modifier = thisModifier)
|
||||||
|
}
|
||||||
|
"-" -> Text(text = "\uD83D\uDC4E$removeSymbol", color = Color.White, textAlign = TextAlign.Center, modifier = thisModifier)
|
||||||
|
else -> Text(
|
||||||
|
"$reactionType$removeSymbol",
|
||||||
|
color = Color.White,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = thisModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ZapAmountChoicePopup(
|
fun ZapAmountChoicePopup(
|
||||||
|
@ -0,0 +1,231 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.imePadding
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.OutlinedTextField
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
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.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
|
||||||
|
class UpdateReactionTypeViewModel(val account: Account) : ViewModel() {
|
||||||
|
var nextChoice by mutableStateOf(TextFieldValue(""))
|
||||||
|
var reactionSet by mutableStateOf(listOf<String>())
|
||||||
|
|
||||||
|
fun load() {
|
||||||
|
this.reactionSet = account.reactionChoices
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toListOfChoices(commaSeparatedAmounts: String): List<Long> {
|
||||||
|
return commaSeparatedAmounts.split(",").map { it.trim().toLongOrNull() ?: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addChoice() {
|
||||||
|
val newValue = nextChoice.text.trim()
|
||||||
|
reactionSet = reactionSet + newValue
|
||||||
|
|
||||||
|
nextChoice = TextFieldValue("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeChoice(reaction: String) {
|
||||||
|
reactionSet = reactionSet - reaction
|
||||||
|
}
|
||||||
|
|
||||||
|
fun sendPost() {
|
||||||
|
account.changeReactionTypes(reactionSet)
|
||||||
|
nextChoice = TextFieldValue("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancel() {
|
||||||
|
nextChoice = TextFieldValue("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasChanged(): Boolean {
|
||||||
|
return reactionSet != account.reactionChoices
|
||||||
|
}
|
||||||
|
|
||||||
|
class Factory(val account: Account) : ViewModelProvider.Factory {
|
||||||
|
override fun <UpdateReactionTypeViewModel : ViewModel> create(modelClass: Class<UpdateReactionTypeViewModel>): UpdateReactionTypeViewModel {
|
||||||
|
return UpdateReactionTypeViewModel(account) as UpdateReactionTypeViewModel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun UpdateReactionTypeDialog(onClose: () -> Unit, nip47uri: String? = null, accountViewModel: AccountViewModel) {
|
||||||
|
val postViewModel: UpdateReactionTypeViewModel = viewModel(
|
||||||
|
key = accountViewModel.userProfile().pubkeyHex,
|
||||||
|
factory = UpdateReactionTypeViewModel.Factory(accountViewModel.account)
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(accountViewModel) {
|
||||||
|
postViewModel.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { onClose() },
|
||||||
|
properties = DialogProperties(
|
||||||
|
usePlatformDefaultWidth = false,
|
||||||
|
dismissOnClickOutside = false,
|
||||||
|
decorFitsSystemWindows = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(10.dp).imePadding()) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
CloseButton(onCancel = {
|
||||||
|
postViewModel.cancel()
|
||||||
|
onClose()
|
||||||
|
})
|
||||||
|
|
||||||
|
SaveButton(
|
||||||
|
onPost = {
|
||||||
|
postViewModel.sendPost()
|
||||||
|
onClose()
|
||||||
|
},
|
||||||
|
isActive = postViewModel.hasChanged()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.animateContentSize()) {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
postViewModel.reactionSet.forEach { reactionType ->
|
||||||
|
Button(
|
||||||
|
modifier = Modifier.padding(horizontal = 3.dp),
|
||||||
|
shape = ButtonBorder,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
),
|
||||||
|
onClick = {
|
||||||
|
postViewModel.removeChoice(reactionType)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
when (reactionType) {
|
||||||
|
"+" -> {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
|
null,
|
||||||
|
modifier = remember { Modifier.size(16.dp) },
|
||||||
|
tint = Color.White
|
||||||
|
)
|
||||||
|
Text(text = " ✖", color = Color.White, textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
"-" -> Text(text = "\uD83D\uDC4E ✖", color = Color.White, textAlign = TextAlign.Center)
|
||||||
|
else -> Text(text = "$reactionType ✖", color = Color.White, textAlign = TextAlign.Center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(10.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 5.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = stringResource(R.string.new_reaction_symbol)) },
|
||||||
|
value = postViewModel.nextChoice,
|
||||||
|
onValueChange = {
|
||||||
|
postViewModel.nextChoice = it
|
||||||
|
},
|
||||||
|
keyboardOptions = KeyboardOptions.Default.copy(
|
||||||
|
capitalization = KeyboardCapitalization.None,
|
||||||
|
keyboardType = KeyboardType.Text
|
||||||
|
),
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "\uD83D\uDCAF, \uD83C\uDF89, \uD83D\uDC4E",
|
||||||
|
color = MaterialTheme.colors.placeholderText
|
||||||
|
)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(end = 10.dp)
|
||||||
|
.weight(1f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = { postViewModel.addChoice() },
|
||||||
|
shape = ButtonBorder,
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
backgroundColor = MaterialTheme.colors.primary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(R.string.add), color = Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ import androidx.compose.runtime.Stable
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableMap
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
abstract class Card() {
|
abstract class Card() {
|
||||||
@ -41,7 +43,12 @@ class ZapUserSetCard(val user: User, val zapEvents: ImmutableList<CombinedZap>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class MultiSetCard(val note: Note, val boostEvents: ImmutableList<Note>, val likeEvents: ImmutableList<Note>, val zapEvents: ImmutableList<CombinedZap>) : Card() {
|
class MultiSetCard(
|
||||||
|
val note: Note,
|
||||||
|
val boostEvents: ImmutableList<Note>,
|
||||||
|
val likeEvents: ImmutableList<Note>,
|
||||||
|
val zapEvents: ImmutableList<CombinedZap>
|
||||||
|
) : Card() {
|
||||||
val maxCreatedAt = maxOf(
|
val maxCreatedAt = maxOf(
|
||||||
zapEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
zapEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||||
likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
likeEvents.maxOfOrNull { it.createdAt() ?: 0 } ?: 0,
|
||||||
@ -54,6 +61,10 @@ class MultiSetCard(val note: Note, val boostEvents: ImmutableList<Note>, val lik
|
|||||||
boostEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE
|
boostEvents.minOfOrNull { it.createdAt() ?: Long.MAX_VALUE } ?: Long.MAX_VALUE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val likeEventsByType = likeEvents.groupBy { it.event?.content() ?: "+" }.mapValues {
|
||||||
|
it.value.toImmutableList()
|
||||||
|
}.toImmutableMap()
|
||||||
|
|
||||||
override fun createdAt(): Long {
|
override fun createdAt(): Long {
|
||||||
return maxCreatedAt
|
return maxCreatedAt
|
||||||
}
|
}
|
||||||
|
@ -39,16 +39,25 @@ class AccountViewModel(val account: Account) : ViewModel() {
|
|||||||
return account.userProfile()
|
return account.userProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reactTo(note: Note) {
|
fun reactTo(note: Note, reaction: String) {
|
||||||
account.reactTo(note)
|
account.reactTo(note, reaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasReactedTo(baseNote: Note): Boolean {
|
fun reactToOrDelete(note: Note, reaction: String) {
|
||||||
return account.hasReacted(baseNote)
|
val currentReactions = account.reactionTo(note, reaction)
|
||||||
|
if (currentReactions.isNotEmpty()) {
|
||||||
|
account.delete(currentReactions)
|
||||||
|
} else {
|
||||||
|
account.reactTo(note, reaction)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteReactionTo(note: Note) {
|
fun hasReactedTo(baseNote: Note, reaction: String): Boolean {
|
||||||
account.delete(account.reactionTo(note))
|
return account.hasReacted(baseNote, reaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteReactionTo(note: Note, reaction: String) {
|
||||||
|
account.delete(account.reactionTo(note, reaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasBoosted(baseNote: Note): Boolean {
|
fun hasBoosted(baseNote: Note): Boolean {
|
||||||
|
@ -414,4 +414,7 @@
|
|||||||
<string name="recommended_apps">Recommends: </string>
|
<string name="recommended_apps">Recommends: </string>
|
||||||
<string name="filter_spam_from_strangers">Filter spam from strangers</string>
|
<string name="filter_spam_from_strangers">Filter spam from strangers</string>
|
||||||
<string name="warn_when_posts_have_reports_from_your_follows">Warn when posts have reports from your follows</string>
|
<string name="warn_when_posts_have_reports_from_your_follows">Warn when posts have reports from your follows</string>
|
||||||
|
|
||||||
|
<string name="new_reaction_symbol">New Reaction Symbol</string>
|
||||||
|
<string name="no_reaction_type_setup_long_press_to_change">No reaction types selected. Long Press to change</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user