Creates a way to ask for donations in the Notification page.
This commit is contained in:
@@ -112,6 +112,7 @@ private object PrefKeys {
|
|||||||
const val LOGIN_WITH_EXTERNAL_SIGNER = "login_with_external_signer"
|
const val LOGIN_WITH_EXTERNAL_SIGNER = "login_with_external_signer"
|
||||||
const val AUTOMATICALLY_SHOW_PROFILE_PICTURE = "automatically_show_profile_picture"
|
const val AUTOMATICALLY_SHOW_PROFILE_PICTURE = "automatically_show_profile_picture"
|
||||||
const val SIGNER_PACKAGE_NAME = "signer_package_name"
|
const val SIGNER_PACKAGE_NAME = "signer_package_name"
|
||||||
|
const val HAS_DONATED_IN_VERSION = "has_donated_in_version"
|
||||||
|
|
||||||
const val ALL_ACCOUNT_INFO = "all_saved_accounts_info"
|
const val ALL_ACCOUNT_INFO = "all_saved_accounts_info"
|
||||||
const val SHARED_SETTINGS = "shared_settings"
|
const val SHARED_SETTINGS = "shared_settings"
|
||||||
@@ -331,6 +332,7 @@ object LocalPreferences {
|
|||||||
PrefKeys.LAST_READ_PER_ROUTE,
|
PrefKeys.LAST_READ_PER_ROUTE,
|
||||||
Event.mapper.writeValueAsString(account.lastReadPerRoute),
|
Event.mapper.writeValueAsString(account.lastReadPerRoute),
|
||||||
)
|
)
|
||||||
|
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, account.hasDonatedInVersion)
|
||||||
|
|
||||||
if (account.showSensitiveContent == null) {
|
if (account.showSensitiveContent == null) {
|
||||||
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||||
@@ -617,6 +619,8 @@ object LocalPreferences {
|
|||||||
NostrSignerInternal(keyPair)
|
NostrSignerInternal(keyPair)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
|
||||||
|
|
||||||
val account =
|
val account =
|
||||||
Account(
|
Account(
|
||||||
keyPair = keyPair,
|
keyPair = keyPair,
|
||||||
@@ -644,6 +648,7 @@ object LocalPreferences {
|
|||||||
warnAboutPostsWithReports = warnAboutReports,
|
warnAboutPostsWithReports = warnAboutReports,
|
||||||
filterSpamFromStrangers = filterSpam,
|
filterSpamFromStrangers = filterSpam,
|
||||||
lastReadPerRoute = lastReadPerRoute,
|
lastReadPerRoute = lastReadPerRoute,
|
||||||
|
hasDonatedInVersion = hasDonatedInVersion,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Loads from DB
|
// Loads from DB
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import androidx.lifecycle.asLiveData
|
|||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
import androidx.lifecycle.switchMap
|
import androidx.lifecycle.switchMap
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.service.FileHeader
|
import com.vitorpamplona.amethyst.service.FileHeader
|
||||||
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
||||||
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
||||||
@@ -96,6 +97,7 @@ import kotlinx.collections.immutable.ImmutableSet
|
|||||||
import kotlinx.collections.immutable.persistentSetOf
|
import kotlinx.collections.immutable.persistentSetOf
|
||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.collections.immutable.toPersistentSet
|
import kotlinx.collections.immutable.toPersistentSet
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
@@ -181,10 +183,9 @@ class Account(
|
|||||||
var warnAboutPostsWithReports: Boolean = true,
|
var warnAboutPostsWithReports: Boolean = true,
|
||||||
var filterSpamFromStrangers: Boolean = true,
|
var filterSpamFromStrangers: Boolean = true,
|
||||||
var lastReadPerRoute: Map<String, Long> = mapOf<String, Long>(),
|
var lastReadPerRoute: Map<String, Long> = mapOf<String, Long>(),
|
||||||
|
var hasDonatedInVersion: Set<String> = setOf<String>(),
|
||||||
|
val scope: CoroutineScope = Amethyst.instance.applicationIOScope,
|
||||||
) {
|
) {
|
||||||
// Uses a single scope for the entire application.
|
|
||||||
val scope = Amethyst.instance.applicationIOScope
|
|
||||||
|
|
||||||
var transientHiddenUsers: ImmutableSet<String> = persistentSetOf()
|
var transientHiddenUsers: ImmutableSet<String> = persistentSetOf()
|
||||||
|
|
||||||
data class PaymentRequest(
|
data class PaymentRequest(
|
||||||
@@ -2354,6 +2355,16 @@ class Account(
|
|||||||
return lastReadPerRoute[route] ?: 0
|
return lastReadPerRoute[route] ?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasDonatedInThisVersion(): Boolean {
|
||||||
|
return hasDonatedInVersion.contains(BuildConfig.VERSION_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markDonatedInThisVersion() {
|
||||||
|
hasDonatedInVersion = hasDonatedInVersion + BuildConfig.VERSION_NAME
|
||||||
|
saveable.invalidateData()
|
||||||
|
live.invalidateData()
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun registerObservers() =
|
suspend fun registerObservers() =
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
// saves contact list for the next time.
|
// saves contact list for the next time.
|
||||||
|
|||||||
@@ -49,15 +49,32 @@ import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.quartz.events.EventInterface
|
import com.vitorpamplona.quartz.events.EventInterface
|
||||||
|
import com.vitorpamplona.quartz.events.ZapSplitSetup
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayZapSplits(
|
fun DisplayZapSplits(
|
||||||
noteEvent: EventInterface,
|
noteEvent: EventInterface,
|
||||||
|
useAuthorIfEmpty: Boolean = false,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
val list = remember(noteEvent) { noteEvent.zapSplitSetup() }
|
val list =
|
||||||
|
remember(noteEvent) {
|
||||||
|
val list = noteEvent.zapSplitSetup()
|
||||||
|
if (list.isEmpty() && useAuthorIfEmpty) {
|
||||||
|
listOf<ZapSplitSetup>(
|
||||||
|
ZapSplitSetup(
|
||||||
|
lnAddressOrPubKeyHex = noteEvent.pubKey(),
|
||||||
|
relay = null,
|
||||||
|
weight = 1.0,
|
||||||
|
isLnAddress = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
list
|
||||||
|
}
|
||||||
|
}
|
||||||
if (list.isEmpty()) return
|
if (list.isEmpty()) return
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
|||||||
@@ -1123,7 +1123,7 @@ private fun NoteBody(
|
|||||||
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
|
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
|
||||||
if (zapSplits && noteEvent != null) {
|
if (zapSplits && noteEvent != null) {
|
||||||
Spacer(modifier = HalfDoubleVertSpacer)
|
Spacer(modifier = HalfDoubleVertSpacer)
|
||||||
DisplayZapSplits(noteEvent, accountViewModel, nav)
|
DisplayZapSplits(noteEvent, false, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -625,7 +625,7 @@ fun TextCount(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SlidingAnimationAmount(
|
fun SlidingAnimationAmount(
|
||||||
amount: MutableState<String>,
|
amount: MutableState<String>,
|
||||||
textColor: Color,
|
textColor: Color,
|
||||||
) {
|
) {
|
||||||
@@ -1028,7 +1028,7 @@ fun ZapReaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun zapClick(
|
fun zapClick(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
context: Context,
|
context: Context,
|
||||||
@@ -1065,7 +1065,7 @@ private fun zapClick(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ObserveZapIcon(
|
fun ObserveZapIcon(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
inner: @Composable (MutableState<Boolean>) -> Unit,
|
inner: @Composable (MutableState<Boolean>) -> Unit,
|
||||||
@@ -1088,7 +1088,7 @@ private fun ObserveZapIcon(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ObserveZapAmountText(
|
fun ObserveZapAmountText(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
inner: @Composable (MutableState<String>) -> Unit,
|
inner: @Composable (MutableState<String>) -> Unit,
|
||||||
|
|||||||
@@ -0,0 +1,459 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024 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.note
|
||||||
|
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
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.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProgressIndicatorDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
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.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.withStyle
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.ThemeType
|
||||||
|
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.ClickableText
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
|
import com.vitorpamplona.amethyst.ui.elements.DisplayZapSplits
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.routeFor
|
||||||
|
import com.vitorpamplona.amethyst.ui.navigation.routeToMessage
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size10dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.imageModifier
|
||||||
|
import com.vitorpamplona.quartz.crypto.KeyPair
|
||||||
|
import com.vitorpamplona.quartz.events.Event
|
||||||
|
import fr.acinq.secp256k1.Hex
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
fun ZapTheDevsCardPreview() {
|
||||||
|
val sharedPreferencesViewModel: SharedPreferencesViewModel = viewModel()
|
||||||
|
val myCoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
|
sharedPreferencesViewModel.init()
|
||||||
|
sharedPreferencesViewModel.updateTheme(ThemeType.DARK)
|
||||||
|
|
||||||
|
runBlocking(Dispatchers.IO) {
|
||||||
|
val releaseNotes =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"id": "0465b20da0adf45dd612024d124e1ed384f7ecd2cd7358e77998828e7bf35fa2",
|
||||||
|
"pubkey": "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||||
|
"created_at": 1708014675,
|
||||||
|
"kind": 1,
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
"p",
|
||||||
|
"ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b",
|
||||||
|
"",
|
||||||
|
"mention"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"p",
|
||||||
|
"7eb29c126b3628077e2e3d863b917a56b74293aa9d8a9abc26a40ba3f2866baf",
|
||||||
|
"",
|
||||||
|
"mention"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"t",
|
||||||
|
"Amethyst"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"t",
|
||||||
|
"amethyst"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zap",
|
||||||
|
"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||||
|
"wss://vitor.nostr1.com",
|
||||||
|
"0.6499999761581421"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zap",
|
||||||
|
"ca89cb11f1c75d5b6622268ff43d2288ea8b2cb5b9aa996ff9ff704fc904b78b",
|
||||||
|
"wss://nos.lol",
|
||||||
|
"0.25"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"zap",
|
||||||
|
"7eb29c126b3628077e2e3d863b917a56b74293aa9d8a9abc26a40ba3f2866baf",
|
||||||
|
"wss://vitor.nostr1.com",
|
||||||
|
"0.10000000149011612"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"r",
|
||||||
|
"https://github.com/vitorpamplona/amethyst/releases/download/v0.84.2/amethyst-googleplay-universal-v0.84.2.apk"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"r",
|
||||||
|
"https://github.com/vitorpamplona/amethyst/releases/download/v0.84.2/amethyst-fdroid-universal-v0.84.2.apk"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"content": "#Amethyst v0.84.2: Text alignment fix\n\nBugfixes:\n- Fixes link misalignment in posts\n\nUpdated translations: \n- Czech, German, Swedish, and Portuguese by nostr:npub1e2yuky03caw4ke3zy68lg0fz3r4gkt94hx4fjmlelacyljgyk79svn3eef\n- French by nostr:npub106efcyntxc5qwl3w8krrhyt626m59ya2nk9f40px5s968u5xdwhsjsr8fz\n\nDownload:\n- [Play Edition](https://github.com/vitorpamplona/amethyst/releases/download/v0.84.2/amethyst-googleplay-universal-v0.84.2.apk )\n- [FOSS Edition - No translations](https://github.com/vitorpamplona/amethyst/releases/download/v0.84.2/amethyst-fdroid-universal-v0.84.2.apk )",
|
||||||
|
"sig": "e036ecce534e22efd47634c56328af62576ab3a36c565f7c8c5fbea67f48cd46d4041ecfc0ca01dafa0ebe8a0b119d125527a28f88aa30356b80c26dd0953aed"
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
LocalCache.justConsume(Event.fromJson(releaseNotes), null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val accountViewModel =
|
||||||
|
AccountViewModel(
|
||||||
|
Account(
|
||||||
|
// blank keys
|
||||||
|
keyPair =
|
||||||
|
KeyPair(
|
||||||
|
privKey = Hex.decode("0f761f8a5a481e26f06605a1d9b3e9eba7a107d351f43c43a57469b788274499"),
|
||||||
|
pubKey = Hex.decode("989c3734c46abac7ce3ce229971581a5a6ee39cdd6aa7261a55823fa7f8c4799"),
|
||||||
|
forcePubKeyCheck = false,
|
||||||
|
),
|
||||||
|
scope = myCoroutineScope,
|
||||||
|
),
|
||||||
|
sharedPreferencesViewModel.sharedPrefs,
|
||||||
|
)
|
||||||
|
|
||||||
|
LoadNote(
|
||||||
|
baseNoteHex = "0465b20da0adf45dd612024d124e1ed384f7ecd2cd7358e77998828e7bf35fa2",
|
||||||
|
accountViewModel,
|
||||||
|
) { releaseNote ->
|
||||||
|
if (releaseNote != null) {
|
||||||
|
ThemeComparisonColumn(
|
||||||
|
onDark = {
|
||||||
|
ZapTheDevsCard(
|
||||||
|
releaseNote,
|
||||||
|
accountViewModel,
|
||||||
|
nav = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onLight = {
|
||||||
|
ZapTheDevsCard(
|
||||||
|
releaseNote,
|
||||||
|
accountViewModel,
|
||||||
|
nav = {},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ZapTheDevsCard(
|
||||||
|
baseNote: Note,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val releaseNoteState by baseNote.live().metadata.observeAsState()
|
||||||
|
val releaseNote = releaseNoteState?.note ?: return
|
||||||
|
|
||||||
|
Row(modifier = Modifier.padding(horizontal = Size10dp)) {
|
||||||
|
Card(
|
||||||
|
modifier = MaterialTheme.colorScheme.imageModifier,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
) {
|
||||||
|
// Title
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.zap_the_devs_title),
|
||||||
|
style =
|
||||||
|
TextStyle(
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
modifier = Size20Modifier,
|
||||||
|
onClick = { accountViewModel.markDonatedInThisVersion() },
|
||||||
|
) {
|
||||||
|
CloseIcon()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
ClickableText(
|
||||||
|
text =
|
||||||
|
buildAnnotatedString {
|
||||||
|
append(stringResource(id = R.string.zap_the_devs_description, BuildConfig.VERSION_NAME))
|
||||||
|
append(" ")
|
||||||
|
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
||||||
|
append("#value4value")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onClick = { nav("Hashtag/value4value") },
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
val noteEvent = releaseNote.event
|
||||||
|
if (noteEvent != null) {
|
||||||
|
val route =
|
||||||
|
remember(releaseNote) {
|
||||||
|
routeFor(releaseNote, accountViewModel.userProfile())
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route != null) {
|
||||||
|
ClickableText(
|
||||||
|
text =
|
||||||
|
buildAnnotatedString {
|
||||||
|
withStyle(SpanStyle(color = MaterialTheme.colorScheme.primary)) {
|
||||||
|
append(stringResource(id = R.string.version_name, BuildConfig.VERSION_NAME.substringBefore("-")))
|
||||||
|
}
|
||||||
|
append(" " + stringResource(id = R.string.brought_to_you_by))
|
||||||
|
},
|
||||||
|
onClick = { nav(route) },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Text(stringResource(id = R.string.this_version_brought_to_you_by))
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
|
||||||
|
DisplayZapSplits(
|
||||||
|
noteEvent = noteEvent,
|
||||||
|
useAuthorIfEmpty = true,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = StdVertSpacer)
|
||||||
|
}
|
||||||
|
|
||||||
|
ZapDonationButton(
|
||||||
|
baseNote = releaseNote,
|
||||||
|
grayTint = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
nav = nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ZapDonationButton(
|
||||||
|
baseNote: Note,
|
||||||
|
grayTint: Color,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
iconSize: Dp = Size20dp,
|
||||||
|
iconSizeModifier: Modifier = Size20Modifier,
|
||||||
|
animationSize: Dp = 14.dp,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
var wantsToZap by remember { mutableStateOf(false) }
|
||||||
|
var showErrorMessageDialog by remember { mutableStateOf<String?>(null) }
|
||||||
|
var wantsToPay by
|
||||||
|
remember(baseNote) {
|
||||||
|
mutableStateOf<ImmutableList<ZapPaymentHandler.Payable>>(
|
||||||
|
persistentListOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
var zappingProgress by remember { mutableFloatStateOf(0f) }
|
||||||
|
var hasZapped by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
zapClick(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
context,
|
||||||
|
onZappingProgress = { progress: Float ->
|
||||||
|
scope.launch { zappingProgress = progress }
|
||||||
|
},
|
||||||
|
onMultipleChoices = { wantsToZap = true },
|
||||||
|
onError = { _, message ->
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
showErrorMessageDialog = message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onPayViaIntent = { wantsToPay = it },
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
if (wantsToZap) {
|
||||||
|
ZapAmountChoicePopup(
|
||||||
|
baseNote = baseNote,
|
||||||
|
iconSize = iconSize,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onDismiss = {
|
||||||
|
wantsToZap = false
|
||||||
|
zappingProgress = 0f
|
||||||
|
},
|
||||||
|
onChangeAmount = {
|
||||||
|
wantsToZap = false
|
||||||
|
},
|
||||||
|
onError = { _, message ->
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
showErrorMessageDialog = message
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProgress = {
|
||||||
|
scope.launch(Dispatchers.Main) { zappingProgress = it }
|
||||||
|
},
|
||||||
|
onPayViaIntent = { wantsToPay = it },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showErrorMessageDialog != null) {
|
||||||
|
ErrorMessageDialog(
|
||||||
|
title = stringResource(id = R.string.error_dialog_zap_error),
|
||||||
|
textContent = showErrorMessageDialog ?: "",
|
||||||
|
onClickStartMessage = {
|
||||||
|
baseNote.author?.let {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
val route = routeToMessage(it, showErrorMessageDialog, accountViewModel)
|
||||||
|
nav(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDismiss = { showErrorMessageDialog = null },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantsToPay.isNotEmpty()) {
|
||||||
|
PayViaIntentDialog(
|
||||||
|
payingInvoices = wantsToPay,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
onClose = { wantsToPay = persistentListOf() },
|
||||||
|
onError = {
|
||||||
|
wantsToPay = persistentListOf()
|
||||||
|
scope.launch {
|
||||||
|
zappingProgress = 0f
|
||||||
|
showErrorMessageDialog = it
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = iconSizeModifier,
|
||||||
|
) {
|
||||||
|
if (zappingProgress > 0.00001 && zappingProgress < 0.99999) {
|
||||||
|
Spacer(ModifierWidth3dp)
|
||||||
|
|
||||||
|
CircularProgressIndicator(
|
||||||
|
progress =
|
||||||
|
animateFloatAsState(
|
||||||
|
targetValue = zappingProgress,
|
||||||
|
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec,
|
||||||
|
label = "ZapIconIndicator",
|
||||||
|
)
|
||||||
|
.value,
|
||||||
|
modifier = remember { Modifier.size(animationSize) },
|
||||||
|
strokeWidth = 2.dp,
|
||||||
|
color = grayTint,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ObserveZapIcon(
|
||||||
|
baseNote,
|
||||||
|
accountViewModel,
|
||||||
|
) { wasZappedByLoggedInUser ->
|
||||||
|
LaunchedEffect(wasZappedByLoggedInUser.value) {
|
||||||
|
hasZapped = wasZappedByLoggedInUser.value
|
||||||
|
if (wasZappedByLoggedInUser.value && !accountViewModel.account.hasDonatedInThisVersion()) {
|
||||||
|
delay(1000)
|
||||||
|
accountViewModel.markDonatedInThisVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Crossfade(targetState = wasZappedByLoggedInUser.value, label = "ZapIcon") {
|
||||||
|
if (it) {
|
||||||
|
ZappedIcon(iconSizeModifier)
|
||||||
|
} else {
|
||||||
|
ZapIcon(iconSizeModifier, grayTint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasZapped) {
|
||||||
|
Text(text = stringResource(id = R.string.thank_you))
|
||||||
|
} else {
|
||||||
|
Text(text = stringResource(id = R.string.donate_now))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
@@ -45,10 +46,12 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.LoadNote
|
||||||
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
|
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.MessageSetCompose
|
import com.vitorpamplona.amethyst.ui.note.MessageSetCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.MultiSetCompose
|
import com.vitorpamplona.amethyst.ui.note.MultiSetCompose
|
||||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||||
|
import com.vitorpamplona.amethyst.ui.note.ZapTheDevsCard
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose
|
import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
|
||||||
@@ -176,6 +179,10 @@ private fun FeedLoaded(
|
|||||||
contentPadding = FeedPadding,
|
contentPadding = FeedPadding,
|
||||||
state = listState,
|
state = listState,
|
||||||
) {
|
) {
|
||||||
|
item {
|
||||||
|
ShowDonationCard(accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
|
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
|
||||||
val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() }
|
val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() }
|
||||||
|
|
||||||
@@ -192,6 +199,28 @@ private fun FeedLoaded(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShowDonationCard(
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
val account by accountViewModel.account.live.observeAsState()
|
||||||
|
if (account?.account?.hasDonatedInThisVersion() == false) {
|
||||||
|
LoadNote(
|
||||||
|
"4d5a05aec61d8798f30f76b2efab81b98d75a03f935fb82823a1080bd56473cd",
|
||||||
|
accountViewModel,
|
||||||
|
) { loadedNoteId ->
|
||||||
|
if (loadedNoteId != null) {
|
||||||
|
ZapTheDevsCard(
|
||||||
|
loadedNoteId,
|
||||||
|
accountViewModel,
|
||||||
|
nav,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderCardItem(
|
private fun RenderCardItem(
|
||||||
item: Card,
|
item: Card,
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ fun NoteMaster(
|
|||||||
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
|
val zapSplits = remember(noteEvent) { noteEvent?.hasZapSplitSetup() ?: false }
|
||||||
if (zapSplits && noteEvent != null) {
|
if (zapSplits && noteEvent != null) {
|
||||||
Spacer(modifier = DoubleVertSpacer)
|
Spacer(modifier = DoubleVertSpacer)
|
||||||
DisplayZapSplits(noteEvent, accountViewModel, nav)
|
DisplayZapSplits(noteEvent, false, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactionsRow(note, true, accountViewModel, nav)
|
ReactionsRow(note, true, accountViewModel, nav)
|
||||||
|
|||||||
@@ -647,6 +647,12 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
|||||||
account.updateShowSensitiveContent(null)
|
account.updateShowSensitiveContent(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun markDonatedInThisVersion() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
account.markDonatedInThisVersion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun defaultZapType(): LnZapEvent.ZapType {
|
fun defaultZapType(): LnZapEvent.ZapType {
|
||||||
return account.defaultZapType
|
return account.defaultZapType
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -766,4 +766,12 @@
|
|||||||
|
|
||||||
<string name="invalid_nip19_uri">Invalid address</string>
|
<string name="invalid_nip19_uri">Invalid address</string>
|
||||||
<string name="invalid_nip19_uri_description">Amethyst received a URI to open but that uri was invalid: %1$s</string>
|
<string name="invalid_nip19_uri_description">Amethyst received a URI to open but that uri was invalid: %1$s</string>
|
||||||
|
|
||||||
|
<string name="zap_the_devs_title">Zap the Devs!</string>
|
||||||
|
<string name="zap_the_devs_description">Your donation helps us make a difference. Every sat counts!</string>
|
||||||
|
<string name="donate_now">Donate Now</string>
|
||||||
|
<string name="brought_to_you_by">was brought to you by:</string>
|
||||||
|
<string name="this_version_brought_to_you_by">This version was brought to you by:</string>
|
||||||
|
<string name="version_name">Version %1$s</string>
|
||||||
|
<string name="thank_you">Thank you!</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.encoders.toHexKey
|
|||||||
class KeyPair(
|
class KeyPair(
|
||||||
privKey: ByteArray? = null,
|
privKey: ByteArray? = null,
|
||||||
pubKey: ByteArray? = null,
|
pubKey: ByteArray? = null,
|
||||||
|
forcePubKeyCheck: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val privKey: ByteArray?
|
val privKey: ByteArray?
|
||||||
val pubKey: ByteArray
|
val pubKey: ByteArray
|
||||||
@@ -44,7 +45,12 @@ class KeyPair(
|
|||||||
} else {
|
} else {
|
||||||
// as private key is provided, ignore the public key and set keys according to private key
|
// as private key is provided, ignore the public key and set keys according to private key
|
||||||
this.privKey = privKey
|
this.privKey = privKey
|
||||||
this.pubKey = CryptoUtils.pubkeyCreate(privKey)
|
|
||||||
|
if (pubKey == null || forcePubKeyCheck) {
|
||||||
|
this.pubKey = CryptoUtils.pubkeyCreate(privKey)
|
||||||
|
} else {
|
||||||
|
this.pubKey = pubKey
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user