Merge pull request #1764 from greenart7c3/claude/reactions-settings-HhrO0
Add customizable reactions row settings screen
This commit is contained in:
@@ -468,6 +468,12 @@ class Account(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun changeReactionRowItems(items: List<ReactionRowItem>) {
|
||||||
|
if (settings.changeReactionRowItems(items)) {
|
||||||
|
sendNewAppSpecificData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun updateZapAmounts(
|
suspend fun updateZapAmounts(
|
||||||
amountSet: List<Long>,
|
amountSet: List<Long>,
|
||||||
selectedZapType: LnZapEvent.ZapType,
|
selectedZapType: LnZapEvent.ZapType,
|
||||||
|
|||||||
@@ -235,6 +235,15 @@ class AccountSettings(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeReactionRowItems(newItems: List<ReactionRowItem>): Boolean {
|
||||||
|
if (syncedSettings.reactions.reactionRowItems.value != newItems) {
|
||||||
|
syncedSettings.reactions.reactionRowItems.tryEmit(newItems.toImmutableList())
|
||||||
|
saveAccountSettings()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
fun changeZapPaymentRequest(newServer: Nip47WalletConnect.Nip47URINorm?): Boolean {
|
fun changeZapPaymentRequest(newServer: Nip47WalletConnect.Nip47URINorm?): Boolean {
|
||||||
if (zapPaymentRequest.value != newServer) {
|
if (zapPaymentRequest.value != newServer) {
|
||||||
zapPaymentRequest.tryEmit(newServer)
|
zapPaymentRequest.tryEmit(newServer)
|
||||||
|
|||||||
@@ -33,7 +33,11 @@ import java.util.Locale
|
|||||||
class AccountSyncedSettings(
|
class AccountSyncedSettings(
|
||||||
internalSettings: AccountSyncedSettingsInternal,
|
internalSettings: AccountSyncedSettingsInternal,
|
||||||
) {
|
) {
|
||||||
val reactions = AccountReactionPreferences(MutableStateFlow(internalSettings.reactions.reactionChoices.toImmutableList()))
|
val reactions =
|
||||||
|
AccountReactionPreferences(
|
||||||
|
MutableStateFlow(internalSettings.reactions.reactionChoices.toImmutableList()),
|
||||||
|
MutableStateFlow(internalSettings.reactions.reactionRowItems.toImmutableList()),
|
||||||
|
)
|
||||||
val zaps =
|
val zaps =
|
||||||
AccountZapPreferences(
|
AccountZapPreferences(
|
||||||
MutableStateFlow(internalSettings.zaps.zapAmountChoices.toImmutableList()),
|
MutableStateFlow(internalSettings.zaps.zapAmountChoices.toImmutableList()),
|
||||||
@@ -54,7 +58,7 @@ class AccountSyncedSettings(
|
|||||||
|
|
||||||
fun toInternal(): AccountSyncedSettingsInternal =
|
fun toInternal(): AccountSyncedSettingsInternal =
|
||||||
AccountSyncedSettingsInternal(
|
AccountSyncedSettingsInternal(
|
||||||
reactions = AccountReactionPreferencesInternal(reactions.reactionChoices.value),
|
reactions = AccountReactionPreferencesInternal(reactions.reactionChoices.value, reactions.reactionRowItems.value),
|
||||||
zaps =
|
zaps =
|
||||||
AccountZapPreferencesInternal(
|
AccountZapPreferencesInternal(
|
||||||
zaps.zapAmountChoices.value,
|
zaps.zapAmountChoices.value,
|
||||||
@@ -80,6 +84,11 @@ class AccountSyncedSettings(
|
|||||||
reactions.reactionChoices.tryEmit(newReactionChoices)
|
reactions.reactionChoices.tryEmit(newReactionChoices)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val newReactionRowItems = syncedSettingsInternal.reactions.reactionRowItems.toImmutableList()
|
||||||
|
if (!equalImmutableLists(reactions.reactionRowItems.value, newReactionRowItems)) {
|
||||||
|
reactions.reactionRowItems.tryEmit(newReactionRowItems)
|
||||||
|
}
|
||||||
|
|
||||||
val newZapChoices = syncedSettingsInternal.zaps.zapAmountChoices.toImmutableList()
|
val newZapChoices = syncedSettingsInternal.zaps.zapAmountChoices.toImmutableList()
|
||||||
if (!equalImmutableLists(zaps.zapAmountChoices.value, newZapChoices)) {
|
if (!equalImmutableLists(zaps.zapAmountChoices.value, newZapChoices)) {
|
||||||
zaps.zapAmountChoices.tryEmit(newZapChoices)
|
zaps.zapAmountChoices.tryEmit(newZapChoices)
|
||||||
@@ -120,6 +129,7 @@ class AccountSyncedSettings(
|
|||||||
@Stable
|
@Stable
|
||||||
class AccountReactionPreferences(
|
class AccountReactionPreferences(
|
||||||
var reactionChoices: MutableStateFlow<ImmutableList<String>>,
|
var reactionChoices: MutableStateFlow<ImmutableList<String>>,
|
||||||
|
var reactionRowItems: MutableStateFlow<ImmutableList<ReactionRowItem>>,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|||||||
+26
@@ -39,6 +39,31 @@ val DefaultReactions =
|
|||||||
|
|
||||||
val DefaultZapAmounts = listOf(100L, 500L, 1000L)
|
val DefaultZapAmounts = listOf(100L, 500L, 1000L)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class ReactionRowAction {
|
||||||
|
Reply,
|
||||||
|
Boost,
|
||||||
|
Like,
|
||||||
|
Zap,
|
||||||
|
Share,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReactionRowItem(
|
||||||
|
val action: ReactionRowAction,
|
||||||
|
val enabled: Boolean = true,
|
||||||
|
val showCounter: Boolean = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
val DefaultReactionRowItems =
|
||||||
|
listOf(
|
||||||
|
ReactionRowItem(ReactionRowAction.Reply),
|
||||||
|
ReactionRowItem(ReactionRowAction.Boost),
|
||||||
|
ReactionRowItem(ReactionRowAction.Like),
|
||||||
|
ReactionRowItem(ReactionRowAction.Zap),
|
||||||
|
ReactionRowItem(ReactionRowAction.Share, showCounter = false),
|
||||||
|
)
|
||||||
|
|
||||||
fun getLanguagesSpokenByUser(): Set<String> {
|
fun getLanguagesSpokenByUser(): Set<String> {
|
||||||
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration())
|
val languageList = ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration())
|
||||||
val codedList = mutableSetOf<String>()
|
val codedList = mutableSetOf<String>()
|
||||||
@@ -59,6 +84,7 @@ class AccountSyncedSettingsInternal(
|
|||||||
@Serializable
|
@Serializable
|
||||||
class AccountReactionPreferencesInternal(
|
class AccountReactionPreferencesInternal(
|
||||||
var reactionChoices: List<String> = DefaultReactions,
|
var reactionChoices: List<String> = DefaultReactions,
|
||||||
|
var reactionRowItems: List<ReactionRowItem> = DefaultReactionRowItems,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.RelayInformationScre
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.search.SearchScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AllSettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.AllSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.NIP47SetupScreen
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.ReactionsSettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SecurityFiltersScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsScreen
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.UpdateZapAmountScreen
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.UpdateZapAmountScreen
|
||||||
@@ -175,6 +176,7 @@ fun AppNavigation(
|
|||||||
composableFromEnd<Route.Drafts> { DraftListScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Drafts> { DraftListScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.Settings> { SettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.Settings> { SettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEnd<Route.UserSettings> { UserSettingsScreen(accountViewModel, nav) }
|
composableFromEnd<Route.UserSettings> { UserSettingsScreen(accountViewModel, nav) }
|
||||||
|
composableFromEnd<Route.ReactionsSettings> { ReactionsSettingsScreen(accountViewModel, nav) }
|
||||||
composableFromEndArgs<Route.Nip47NWCSetup> { NIP47SetupScreen(accountViewModel, nav, it.nip47) }
|
composableFromEndArgs<Route.Nip47NWCSetup> { NIP47SetupScreen(accountViewModel, nav, it.nip47) }
|
||||||
composableFromEndArgs<Route.UpdateZapAmount> { UpdateZapAmountScreen(accountViewModel, nav, it.nip47) }
|
composableFromEndArgs<Route.UpdateZapAmount> { UpdateZapAmountScreen(accountViewModel, nav, it.nip47) }
|
||||||
composableFromEndArgs<Route.EditRelays> { AllRelayListScreen(accountViewModel, nav) }
|
composableFromEndArgs<Route.EditRelays> { AllRelayListScreen(accountViewModel, nav) }
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ sealed class Route {
|
|||||||
|
|
||||||
@Serializable object UserSettings : Route()
|
@Serializable object UserSettings : Route()
|
||||||
|
|
||||||
|
@Serializable object ReactionsSettings : Route()
|
||||||
|
|
||||||
@Serializable object Lists : Route()
|
@Serializable object Lists : Route()
|
||||||
|
|
||||||
@Serializable data class MyPeopleListView(
|
@Serializable data class MyPeopleListView(
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
import com.vitorpamplona.amethyst.commons.emojicoder.EmojiCoder
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.ReactionRowAction
|
||||||
|
import com.vitorpamplona.amethyst.model.ReactionRowItem
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteReactionCount
|
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteReactionCount
|
||||||
@@ -212,6 +214,7 @@ private fun InnerReactionRow(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val voiceRecordingState = remember(baseNote.idHex) { mutableStateOf(false) }
|
val voiceRecordingState = remember(baseNote.idHex) { mutableStateOf(false) }
|
||||||
|
val reactionRowItems by accountViewModel.reactionRowItemsFlow().collectAsStateWithLifecycle()
|
||||||
|
|
||||||
GenericInnerReactionRow(
|
GenericInnerReactionRow(
|
||||||
showReactionDetail = showReactionDetail,
|
showReactionDetail = showReactionDetail,
|
||||||
@@ -222,16 +225,21 @@ private fun InnerReactionRow(
|
|||||||
RenderShowIndividualReactionsButton(wantsToSeeReactions, accountViewModel)
|
RenderShowIndividualReactionsButton(wantsToSeeReactions, accountViewModel)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
two = {
|
reactions = reactionRowItems,
|
||||||
|
renderReaction = { item ->
|
||||||
|
when (item.action) {
|
||||||
|
ReactionRowAction.Reply -> {
|
||||||
ReplyReactionWithDialog(
|
ReplyReactionWithDialog(
|
||||||
baseNote,
|
baseNote,
|
||||||
MaterialTheme.colorScheme.placeholderText,
|
MaterialTheme.colorScheme.placeholderText,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
|
showCounter = item.showCounter,
|
||||||
voiceRecordingState = voiceRecordingState,
|
voiceRecordingState = voiceRecordingState,
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
three = {
|
|
||||||
|
ReactionRowAction.Boost -> {
|
||||||
val isDM = baseNote.event is ChatroomKeyable
|
val isDM = baseNote.event is ChatroomKeyable
|
||||||
if (!isDM) {
|
if (!isDM) {
|
||||||
BoostWithDialog(
|
BoostWithDialog(
|
||||||
@@ -240,20 +248,38 @@ private fun InnerReactionRow(
|
|||||||
MaterialTheme.colorScheme.placeholderText,
|
MaterialTheme.colorScheme.placeholderText,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
|
showCounter = item.showCounter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
four = {
|
|
||||||
LikeReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
|
ReactionRowAction.Like -> {
|
||||||
},
|
LikeReaction(
|
||||||
five = {
|
baseNote,
|
||||||
ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
|
MaterialTheme.colorScheme.placeholderText,
|
||||||
},
|
accountViewModel,
|
||||||
six = {
|
nav,
|
||||||
|
showCounter = item.showCounter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactionRowAction.Zap -> {
|
||||||
|
ZapReaction(
|
||||||
|
baseNote,
|
||||||
|
MaterialTheme.colorScheme.placeholderText,
|
||||||
|
accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
showCounter = item.showCounter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ReactionRowAction.Share -> {
|
||||||
ShareReaction(
|
ShareReaction(
|
||||||
note = baseNote,
|
note = baseNote,
|
||||||
grayTint = MaterialTheme.colorScheme.placeholderText,
|
grayTint = MaterialTheme.colorScheme.placeholderText,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -301,12 +327,12 @@ private fun GenericInnerReactionRow(
|
|||||||
addPadding: Boolean,
|
addPadding: Boolean,
|
||||||
weightTwo: Float = 1f,
|
weightTwo: Float = 1f,
|
||||||
one: @Composable () -> Unit,
|
one: @Composable () -> Unit,
|
||||||
two: @Composable () -> Unit,
|
reactions: ImmutableList<ReactionRowItem>,
|
||||||
three: @Composable () -> Unit,
|
renderReaction: @Composable (ReactionRowItem) -> Unit,
|
||||||
four: @Composable () -> Unit,
|
|
||||||
five: @Composable () -> Unit,
|
|
||||||
six: @Composable () -> Unit,
|
|
||||||
) {
|
) {
|
||||||
|
val enabledReactions = remember(reactions) { reactions.filter { it.enabled } }
|
||||||
|
val lastIndex = enabledReactions.lastIndex
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = CenterVertically,
|
verticalAlignment = CenterVertically,
|
||||||
horizontalArrangement = RowColSpacing,
|
horizontalArrangement = RowColSpacing,
|
||||||
@@ -321,15 +347,18 @@ private fun GenericInnerReactionRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = CenterVertically, horizontalArrangement = RowColSpacing, modifier = Modifier.weight(weightTwo)) { two() }
|
enabledReactions.forEachIndexed { index, item ->
|
||||||
|
val isLast = index == lastIndex
|
||||||
Row(verticalAlignment = CenterVertically, horizontalArrangement = RowColSpacing, modifier = Modifier.weight(1f)) { three() }
|
val itemWeight = if (item.action == ReactionRowAction.Reply) weightTwo else 1f
|
||||||
|
val mod = if (isLast) Modifier else Modifier.weight(itemWeight)
|
||||||
Row(verticalAlignment = CenterVertically, horizontalArrangement = RowColSpacing, modifier = Modifier.weight(1f)) { four() }
|
Row(
|
||||||
|
verticalAlignment = CenterVertically,
|
||||||
Row(verticalAlignment = CenterVertically, modifier = Modifier.weight(1f)) { five() }
|
horizontalArrangement = RowColSpacing,
|
||||||
|
modifier = mod,
|
||||||
Row(verticalAlignment = CenterVertically, modifier = Modifier) { six() }
|
) {
|
||||||
|
renderReaction(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,11 +577,13 @@ private fun BoostWithDialog(
|
|||||||
grayTint: Color,
|
grayTint: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
|
showCounter: Boolean = true,
|
||||||
) {
|
) {
|
||||||
BoostReaction(
|
BoostReaction(
|
||||||
baseNote,
|
baseNote,
|
||||||
grayTint,
|
grayTint,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
|
showCounter = showCounter,
|
||||||
onQuotePress = {
|
onQuotePress = {
|
||||||
nav.nav {
|
nav.nav {
|
||||||
Route.NewShortNote(
|
Route.NewShortNote(
|
||||||
@@ -597,6 +628,7 @@ private fun ReplyReactionWithDialog(
|
|||||||
grayTint: Color,
|
grayTint: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
|
showCounter: Boolean = true,
|
||||||
voiceRecordingState: MutableState<Boolean>? = null,
|
voiceRecordingState: MutableState<Boolean>? = null,
|
||||||
) {
|
) {
|
||||||
if (baseNote.event is BaseVoiceEvent) {
|
if (baseNote.event is BaseVoiceEvent) {
|
||||||
@@ -605,10 +637,11 @@ private fun ReplyReactionWithDialog(
|
|||||||
grayTint,
|
grayTint,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
|
showCounter = showCounter,
|
||||||
voiceRecordingState = voiceRecordingState,
|
voiceRecordingState = voiceRecordingState,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ReplyReaction(baseNote, grayTint, accountViewModel) {
|
ReplyReaction(baseNote, grayTint, accountViewModel, showCounter = showCounter) {
|
||||||
nav.nav { routeReplyTo(baseNote, accountViewModel.account) }
|
nav.nav { routeReplyTo(baseNote, accountViewModel.account) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,6 +833,7 @@ fun BoostReaction(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
iconSizeModifier: Modifier = Size19Modifier,
|
iconSizeModifier: Modifier = Size19Modifier,
|
||||||
iconSize: Dp = Size20dp,
|
iconSize: Dp = Size20dp,
|
||||||
|
showCounter: Boolean = true,
|
||||||
onQuotePress: () -> Unit,
|
onQuotePress: () -> Unit,
|
||||||
onForkPress: () -> Unit,
|
onForkPress: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -836,7 +870,9 @@ fun BoostReaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCounter) {
|
||||||
BoostText(baseNote, grayTint, accountViewModel)
|
BoostText(baseNote, grayTint, accountViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -870,6 +906,7 @@ fun LikeReaction(
|
|||||||
iconSize: Dp = Size18dp,
|
iconSize: Dp = Size18dp,
|
||||||
heartSizeModifier: Modifier = Size18Modifier,
|
heartSizeModifier: Modifier = Size18Modifier,
|
||||||
iconFontSize: TextUnit = Font14SP,
|
iconFontSize: TextUnit = Font14SP,
|
||||||
|
showCounter: Boolean = true,
|
||||||
) {
|
) {
|
||||||
var wantsToReact by remember { mutableStateOf(false) }
|
var wantsToReact by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
@@ -908,7 +945,9 @@ fun LikeReaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCounter) {
|
||||||
ObserveLikeText(baseNote, accountViewModel) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint, accountViewModel) }
|
ObserveLikeText(baseNote, accountViewModel) { reactionCount -> SlidingAnimationCount(reactionCount, grayTint, accountViewModel) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -1027,6 +1066,7 @@ fun ZapReaction(
|
|||||||
iconSize: Dp = Size20dp,
|
iconSize: Dp = Size20dp,
|
||||||
iconSizeModifier: Modifier = Size20Modifier,
|
iconSizeModifier: Modifier = Size20Modifier,
|
||||||
animationModifier: Modifier = Size14Modifier,
|
animationModifier: Modifier = Size14Modifier,
|
||||||
|
showCounter: Boolean = true,
|
||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
var wantsToZap by remember { mutableStateOf(false) }
|
var wantsToZap by remember { mutableStateOf(false) }
|
||||||
@@ -1194,9 +1234,11 @@ fun ZapReaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showCounter) {
|
||||||
ObserveZapAmountText(baseNote, accountViewModel) { zapAmountTxt ->
|
ObserveZapAmountText(baseNote, accountViewModel) { zapAmountTxt ->
|
||||||
SlidingAnimationAmount(zapAmountTxt, grayTint, accountViewModel)
|
SlidingAnimationAmount(zapAmountTxt, grayTint, accountViewModel)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun zapClick(
|
fun zapClick(
|
||||||
|
|||||||
+7
@@ -986,6 +986,13 @@ class AccountViewModel(
|
|||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reactionRowItemsFlow() = account.settings.syncedSettings.reactions.reactionRowItems
|
||||||
|
|
||||||
|
fun changeReactionRowItems(items: List<com.vitorpamplona.amethyst.model.ReactionRowItem>) =
|
||||||
|
launchSigner {
|
||||||
|
account.changeReactionRowItems(items)
|
||||||
|
}
|
||||||
|
|
||||||
fun updateZapAmounts(
|
fun updateZapAmounts(
|
||||||
amountSet: List<Long>,
|
amountSet: List<Long>,
|
||||||
selectedZapType: LnZapEvent.ZapType,
|
selectedZapType: LnZapEvent.ZapType,
|
||||||
|
|||||||
+8
@@ -33,6 +33,7 @@ import androidx.compose.material.icons.outlined.FavoriteBorder
|
|||||||
import androidx.compose.material.icons.outlined.Key
|
import androidx.compose.material.icons.outlined.Key
|
||||||
import androidx.compose.material.icons.outlined.Security
|
import androidx.compose.material.icons.outlined.Security
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.ThumbUp
|
||||||
import androidx.compose.material.icons.outlined.Translate
|
import androidx.compose.material.icons.outlined.Translate
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -153,6 +154,13 @@ fun AllSettingsScreen(
|
|||||||
tint = tint,
|
tint = tint,
|
||||||
onClick = { nav.nav(Route.Settings) },
|
onClick = { nav.nav(Route.Settings) },
|
||||||
)
|
)
|
||||||
|
HorizontalDivider()
|
||||||
|
SettingsNavigationRow(
|
||||||
|
title = R.string.reactions_settings,
|
||||||
|
icon = Icons.Outlined.ThumbUp,
|
||||||
|
tint = tint,
|
||||||
|
onClick = { nav.nav(Route.ReactionsSettings) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+280
@@ -0,0 +1,280 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||||
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
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.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.ReactionRowAction
|
||||||
|
import com.vitorpamplona.amethyst.model.ReactionRowItem
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReactionsSettingsScreen(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: INav,
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopBarWithBackButton(stringRes(id = R.string.reactions_settings), nav::popBack)
|
||||||
|
},
|
||||||
|
) { padding ->
|
||||||
|
Column(Modifier.padding(padding)) {
|
||||||
|
ReactionsSettingsContent(accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ReactionsSettingsContent(accountViewModel: AccountViewModel) {
|
||||||
|
val reactionRowItems by accountViewModel.reactionRowItemsFlow().collectAsStateWithLifecycle()
|
||||||
|
var items by remember(reactionRowItems) { mutableStateOf(reactionRowItems.toMutableList()) }
|
||||||
|
|
||||||
|
fun save(newItems: List<ReactionRowItem>) {
|
||||||
|
items = newItems.toMutableList()
|
||||||
|
accountViewModel.changeReactionRowItems(newItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = Size20dp)
|
||||||
|
.verticalScroll(rememberScrollState()),
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.reactions_settings_description),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = Color.Gray,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp),
|
||||||
|
)
|
||||||
|
|
||||||
|
items.forEachIndexed { index, item ->
|
||||||
|
ReactionRowItemCard(
|
||||||
|
item = item,
|
||||||
|
canMoveUp = index > 0,
|
||||||
|
canMoveDown = index < items.lastIndex,
|
||||||
|
onToggleEnabled = {
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
newItems[index] = item.copy(enabled = !item.enabled)
|
||||||
|
save(newItems)
|
||||||
|
},
|
||||||
|
onToggleCounter = {
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
newItems[index] = item.copy(showCounter = !item.showCounter)
|
||||||
|
save(newItems)
|
||||||
|
},
|
||||||
|
onMoveUp = {
|
||||||
|
if (index > 0) {
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
val temp = newItems[index - 1]
|
||||||
|
newItems[index - 1] = newItems[index]
|
||||||
|
newItems[index] = temp
|
||||||
|
save(newItems)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onMoveDown = {
|
||||||
|
if (index < items.lastIndex) {
|
||||||
|
val newItems = items.toMutableList()
|
||||||
|
val temp = newItems[index + 1]
|
||||||
|
newItems[index + 1] = newItems[index]
|
||||||
|
newItems[index] = temp
|
||||||
|
save(newItems)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (index < items.lastIndex) {
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ReactionRowItemCard(
|
||||||
|
item: ReactionRowItem,
|
||||||
|
canMoveUp: Boolean,
|
||||||
|
canMoveDown: Boolean,
|
||||||
|
onToggleEnabled: () -> Unit,
|
||||||
|
onToggleCounter: () -> Unit,
|
||||||
|
onMoveUp: () -> Unit,
|
||||||
|
onMoveDown: () -> Unit,
|
||||||
|
) {
|
||||||
|
val actionName = reactionActionName(item.action)
|
||||||
|
val actionDescription = reactionActionDescription(item.action)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 8.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = actionName,
|
||||||
|
style = MaterialTheme.typography.bodyLarge,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = actionDescription,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = Color.Gray,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = onMoveUp,
|
||||||
|
enabled = canMoveUp,
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.KeyboardArrowUp,
|
||||||
|
contentDescription = stringRes(R.string.reactions_settings_move_up),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = if (canMoveUp) MaterialTheme.colorScheme.onSurface else Color.Gray,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = onMoveDown,
|
||||||
|
enabled = canMoveDown,
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.KeyboardArrowDown,
|
||||||
|
contentDescription = stringRes(R.string.reactions_settings_move_down),
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
|
tint = if (canMoveDown) MaterialTheme.colorScheme.onSurface else Color.Gray,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = item.enabled,
|
||||||
|
onCheckedChange = { onToggleEnabled() },
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.reactions_settings_enabled),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.action != ReactionRowAction.Share) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = item.showCounter,
|
||||||
|
onCheckedChange = { onToggleCounter() },
|
||||||
|
enabled = item.enabled,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.reactions_settings_show_counter),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = if (item.enabled) MaterialTheme.colorScheme.onSurface else Color.Gray,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun reactionActionName(action: ReactionRowAction): String =
|
||||||
|
when (action) {
|
||||||
|
ReactionRowAction.Reply -> stringRes(R.string.reactions_settings_reply)
|
||||||
|
ReactionRowAction.Boost -> stringRes(R.string.reactions_settings_boost)
|
||||||
|
ReactionRowAction.Like -> stringRes(R.string.reactions_settings_like)
|
||||||
|
ReactionRowAction.Zap -> stringRes(R.string.reactions_settings_zap)
|
||||||
|
ReactionRowAction.Share -> stringRes(R.string.reactions_settings_share)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun reactionActionDescription(action: ReactionRowAction): String =
|
||||||
|
when (action) {
|
||||||
|
ReactionRowAction.Reply -> stringRes(R.string.reactions_settings_reply_description)
|
||||||
|
ReactionRowAction.Boost -> stringRes(R.string.reactions_settings_boost_description)
|
||||||
|
ReactionRowAction.Like -> stringRes(R.string.reactions_settings_like_description)
|
||||||
|
ReactionRowAction.Zap -> stringRes(R.string.reactions_settings_zap_description)
|
||||||
|
ReactionRowAction.Share -> stringRes(R.string.reactions_settings_share_description)
|
||||||
|
}
|
||||||
@@ -1234,6 +1234,23 @@
|
|||||||
|
|
||||||
<string name="change_reaction">Change Quick Reactions</string>
|
<string name="change_reaction">Change Quick Reactions</string>
|
||||||
|
|
||||||
|
<string name="reactions_settings">Reactions Settings</string>
|
||||||
|
<string name="reactions_settings_description">Configure which reaction buttons are shown, their order, and whether to display counters.</string>
|
||||||
|
<string name="reactions_settings_enabled">Enabled</string>
|
||||||
|
<string name="reactions_settings_show_counter">Show Count</string>
|
||||||
|
<string name="reactions_settings_move_up">Move up</string>
|
||||||
|
<string name="reactions_settings_move_down">Move down</string>
|
||||||
|
<string name="reactions_settings_reply">Reply</string>
|
||||||
|
<string name="reactions_settings_reply_description">Reply to this note</string>
|
||||||
|
<string name="reactions_settings_boost">Boost</string>
|
||||||
|
<string name="reactions_settings_boost_description">Repost or quote this note</string>
|
||||||
|
<string name="reactions_settings_like">Like</string>
|
||||||
|
<string name="reactions_settings_like_description">React to this note with an emoji</string>
|
||||||
|
<string name="reactions_settings_zap">Zap</string>
|
||||||
|
<string name="reactions_settings_zap_description">Send a lightning payment to the author</string>
|
||||||
|
<string name="reactions_settings_share">Share</string>
|
||||||
|
<string name="reactions_settings_share_description">Share this note externally</string>
|
||||||
|
|
||||||
<string name="profile_image_of_user">Profile Picture of %1$s</string>
|
<string name="profile_image_of_user">Profile Picture of %1$s</string>
|
||||||
<string name="relay_info">Relay %1$s</string>
|
<string name="relay_info">Relay %1$s</string>
|
||||||
<string name="expand_relay_list">Expand relay list</string>
|
<string name="expand_relay_list">Expand relay list</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user