Merge branch 'main' into quote_problem
This commit is contained in:
@@ -54,8 +54,14 @@ class Account(
|
|||||||
val liveLanguages: AccountLiveData = AccountLiveData(this)
|
val liveLanguages: AccountLiveData = AccountLiveData(this)
|
||||||
val saveable: AccountLiveData = AccountLiveData(this)
|
val saveable: AccountLiveData = AccountLiveData(this)
|
||||||
|
|
||||||
|
var userProfileCache: User? = null
|
||||||
|
|
||||||
fun userProfile(): User {
|
fun userProfile(): User {
|
||||||
return LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
|
return userProfileCache ?: run {
|
||||||
|
val myUser: User = LocalCache.getOrCreateUser(loggedIn.pubKey.toHexKey())
|
||||||
|
userProfileCache = myUser
|
||||||
|
myUser
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun followingChannels(): List<Channel> {
|
fun followingChannels(): List<Channel> {
|
||||||
|
|||||||
@@ -595,7 +595,7 @@ object LocalCache {
|
|||||||
// Already processed this event.
|
// Already processed this event.
|
||||||
if (note.event != null) return
|
if (note.event != null) return
|
||||||
|
|
||||||
val zapRequest = event.containedPost()?.id?.let { getOrCreateNote(it) }
|
val zapRequest = event.zapRequest?.id?.let { getOrCreateNote(it) }
|
||||||
|
|
||||||
val author = getOrCreateUser(event.pubKey)
|
val author = getOrCreateUser(event.pubKey)
|
||||||
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
val mentions = event.zappedAuthor().mapNotNull { checkGetOrCreateUser(it) }
|
||||||
|
|||||||
@@ -53,13 +53,14 @@ open class Note(val idHex: String) {
|
|||||||
open fun idNote() = id().toNote()
|
open fun idNote() = id().toNote()
|
||||||
open fun idDisplayNote() = idNote().toShortenHex()
|
open fun idDisplayNote() = idNote().toShortenHex()
|
||||||
|
|
||||||
fun channel(): Channel? {
|
fun channelHex(): HexKey? {
|
||||||
val channelHex =
|
return (event as? ChannelMessageEvent)?.channel()
|
||||||
(event as? ChannelMessageEvent)?.channel()
|
?: (event as? ChannelMetadataEvent)?.channel()
|
||||||
?: (event as? ChannelMetadataEvent)?.channel()
|
?: (event as? ChannelCreateEvent)?.id
|
||||||
?: (event as? ChannelCreateEvent)?.id
|
}
|
||||||
|
|
||||||
return channelHex?.let { LocalCache.checkGetOrCreateChannel(it) }
|
fun channel(): Channel? {
|
||||||
|
return channelHex()?.let { LocalCache.checkGetOrCreateChannel(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun address(): ATag? = null
|
open fun address(): ATag? = null
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
is DeletionEvent -> LocalCache.consume(event)
|
is DeletionEvent -> LocalCache.consume(event)
|
||||||
|
|
||||||
is LnZapEvent -> {
|
is LnZapEvent -> {
|
||||||
event.containedPost()?.let { onEvent(it, subscriptionId, relay) }
|
event.zapRequest?.let { onEvent(it, subscriptionId, relay) }
|
||||||
LocalCache.consume(event)
|
LocalCache.consume(event)
|
||||||
}
|
}
|
||||||
is LnZapRequestEvent -> LocalCache.consume(event)
|
is LnZapRequestEvent -> LocalCache.consume(event)
|
||||||
@@ -154,7 +154,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
|
|
||||||
// Refreshes observers in batches.
|
// Refreshes observers in batches.
|
||||||
private val bundler = BundledUpdate(250, Dispatchers.IO) {
|
private val bundler = BundledUpdate(250, Dispatchers.IO) {
|
||||||
println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
|
// println("DataSource: ${this.javaClass.simpleName} InvalidateFilters")
|
||||||
|
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import android.util.Log
|
|||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
import com.vitorpamplona.amethyst.service.lnurl.LnInvoiceUtil
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import java.math.BigDecimal
|
|
||||||
|
|
||||||
class LnZapEvent(
|
class LnZapEvent(
|
||||||
id: HexKey,
|
id: HexKey,
|
||||||
@@ -14,25 +13,37 @@ class LnZapEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : LnZapEventInterface, Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : LnZapEventInterface, Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
// This event is also kept in LocalCache (same object)
|
||||||
|
@Transient val zapRequest: LnZapRequestEvent?
|
||||||
|
|
||||||
override fun zappedPost() = tags
|
private fun containedPost(): LnZapRequestEvent? = try {
|
||||||
.filter { it.firstOrNull() == "e" }
|
description()?.ifBlank { null }?.let {
|
||||||
.mapNotNull { it.getOrNull(1) }
|
fromJson(it, Client.lenient)
|
||||||
|
} as? LnZapRequestEvent
|
||||||
override fun zappedPollOption(): Int? = containedPost()?.tags
|
} catch (e: Exception) {
|
||||||
?.filter { it.firstOrNull() == POLL_OPTION }
|
Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
|
||||||
?.getOrNull(0)?.getOrNull(1)?.toInt()
|
null
|
||||||
|
|
||||||
override fun zappedAuthor() = tags
|
|
||||||
.filter { it.firstOrNull() == "p" }
|
|
||||||
.mapNotNull { it.getOrNull(1) }
|
|
||||||
|
|
||||||
override fun zappedRequestAuthor(): String? = containedPost()?.pubKey()
|
|
||||||
|
|
||||||
override fun amount(): BigDecimal? {
|
|
||||||
return amount
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
zapRequest = containedPost()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||||
|
|
||||||
|
override fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
|
|
||||||
|
override fun zappedPollOption(): Int? = try {
|
||||||
|
zapRequest?.tags?.firstOrNull { it.size > 1 && it[0] == POLL_OPTION }?.get(1)?.toInt()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("LnZapEvent", "ZappedPollOption failed to parse", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun zappedRequestAuthor(): String? = zapRequest?.pubKey()
|
||||||
|
|
||||||
|
override fun amount() = amount
|
||||||
|
|
||||||
// Keeps this as a field because it's a heavier function used everywhere.
|
// Keeps this as a field because it's a heavier function used everywhere.
|
||||||
val amount by lazy {
|
val amount by lazy {
|
||||||
try {
|
try {
|
||||||
@@ -42,29 +53,14 @@ class LnZapEvent(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun message(): String {
|
override fun message(): String {
|
||||||
return message
|
return content
|
||||||
}
|
|
||||||
val message = content
|
|
||||||
|
|
||||||
override fun containedPost(): Event? = try {
|
|
||||||
description()?.ifBlank { null }?.let {
|
|
||||||
fromJson(it, Client.lenient)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("LnZapEvent", "Failed to Parse Contained Post ${description()}", e)
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lnInvoice(): String? = tags
|
private fun lnInvoice() = tags.firstOrNull { it.size > 1 && it[0] == "bolt11" }?.get(1)
|
||||||
.filter { it.firstOrNull() == "bolt11" }
|
|
||||||
.mapNotNull { it.getOrNull(1) }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
private fun description(): String? = tags
|
private fun description() = tags.firstOrNull { it.size > 1 && it[0] == "description" }?.get(1)
|
||||||
.filter { it.firstOrNull() == "description" }
|
|
||||||
.mapNotNull { it.getOrNull(1) }
|
|
||||||
.firstOrNull()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 9735
|
const val kind = 9735
|
||||||
|
|||||||
@@ -16,7 +16,5 @@ interface LnZapEventInterface : EventInterface {
|
|||||||
|
|
||||||
fun amount(): BigDecimal?
|
fun amount(): BigDecimal?
|
||||||
|
|
||||||
fun containedPost(): Event?
|
|
||||||
|
|
||||||
fun message(): String
|
fun message(): String
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ class LnZapRequestEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun zappedPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
|
||||||
fun zappedAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun zappedPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||||
|
|
||||||
|
fun zappedAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 9734
|
const val kind = 9734
|
||||||
|
|||||||
@@ -20,24 +20,17 @@ class PollNoteEvent(
|
|||||||
content: String,
|
content: String,
|
||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : BaseTextNoteEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
fun pollOptions(): Map<Int, String> {
|
fun pollOptions() =
|
||||||
val map = mutableMapOf<Int, String>()
|
tags.filter { it.size > 2 && it[0] == POLL_OPTION }
|
||||||
tags.filter { it.first() == POLL_OPTION }
|
.associate { it[1].toInt() to it[2] }
|
||||||
.forEach { map[it[1].toInt()] = it[2] }
|
|
||||||
return map
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTagInt(property: String): Int? {
|
fun getTagInt(property: String): Int? {
|
||||||
val tagList = tags.filter {
|
val number = tags.firstOrNull() { it.size > 1 && it[0] == property }?.get(1)
|
||||||
it.firstOrNull() == property
|
|
||||||
}
|
|
||||||
val tag = tagList.getOrNull(0)
|
|
||||||
val s = tag?.getOrNull(1)
|
|
||||||
|
|
||||||
return if (s.isNullOrBlank() || s == "null") {
|
return if (number.isNullOrBlank() || number == "null") {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
s.toInt()
|
number.toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PrivateDmEvent(
|
|||||||
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
|
* nip-04 EncryptedDmEvent but may omit the recipient, too. This value can be queried and used
|
||||||
* for initial messages.
|
* for initial messages.
|
||||||
*/
|
*/
|
||||||
fun recipientPubKey() = tags.firstOrNull { it.firstOrNull() == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
|
fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.run { Hex.decode(this[1]).toHexKey() } // makes sure its a valid one
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
|
* To be fully compatible with nip-04, we read e-tags that are in violation to nip-18.
|
||||||
@@ -29,7 +29,7 @@ class PrivateDmEvent(
|
|||||||
* Nip-18 messages should refer to other events by inline references in the content like
|
* Nip-18 messages should refer to other events by inline references in the content like
|
||||||
* `[](e/c06f795e1234a9a1aecc731d768d4f3ca73e80031734767067c82d67ce82e506).
|
* `[](e/c06f795e1234a9a1aecc731d768d4f3ca73e80031734767067c82d67ce82e506).
|
||||||
*/
|
*/
|
||||||
fun replyTo() = tags.firstOrNull { it.firstOrNull() == "e" }?.getOrNull(1)
|
fun replyTo() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
|
||||||
|
|
||||||
fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? {
|
fun plainContent(privKey: ByteArray, pubKey: ByteArray): String? {
|
||||||
return try {
|
return try {
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class ReactionEvent(
|
|||||||
sig: HexKey
|
sig: HexKey
|
||||||
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||||
|
|
||||||
fun originalPost() = tags.filter { it.firstOrNull() == "e" }.mapNotNull { it.getOrNull(1) }
|
fun originalPost() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
|
||||||
fun originalAuthor() = tags.filter { it.firstOrNull() == "p" }.mapNotNull { it.getOrNull(1) }
|
fun originalAuthor() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val kind = 7
|
const val kind = 7
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class ReportEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reportedPost() = tags
|
fun reportedPost() = tags
|
||||||
.filter { it.firstOrNull() == "e" && it.getOrNull(1) != null }
|
.filter { it.size > 1 && it[0] == "e" }
|
||||||
.map {
|
.map {
|
||||||
ReportedKey(
|
ReportedKey(
|
||||||
it[1],
|
it[1],
|
||||||
@@ -39,7 +39,7 @@ class ReportEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reportedAuthor() = tags
|
fun reportedAuthor() = tags
|
||||||
.filter { it.firstOrNull() == "p" && it.getOrNull(1) != null }
|
.filter { it.size > 1 && it[0] == "p" }
|
||||||
.map {
|
.map {
|
||||||
ReportedKey(
|
ReportedKey(
|
||||||
it[1],
|
it[1],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ object GlobalFeedFilter : FeedFilter<Note>() {
|
|||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val followChannels = account.followingChannels()
|
val followChannels = account.followingChannels
|
||||||
val followUsers = account.followingKeySet()
|
val followUsers = account.followingKeySet()
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ object GlobalFeedFilter : FeedFilter<Note>() {
|
|||||||
it.event is BaseTextNoteEvent && it.replyTo.isNullOrEmpty()
|
it.event is BaseTextNoteEvent && it.replyTo.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
val channel = it.channel()
|
val channel = it.channelHex()
|
||||||
// does not show events already in the public chat list
|
// does not show events already in the public chat list
|
||||||
(channel == null || channel !in followChannels) &&
|
(channel == null || channel !in followChannels) &&
|
||||||
// does not show people the user already follows
|
// does not show people the user already follows
|
||||||
@@ -38,7 +38,7 @@ object GlobalFeedFilter : FeedFilter<Note>() {
|
|||||||
it.event is LongTextNoteEvent && it.replyTo.isNullOrEmpty()
|
it.event is LongTextNoteEvent && it.replyTo.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
.filter {
|
.filter {
|
||||||
val channel = it.channel()
|
val channel = it.channelHex()
|
||||||
// does not show events already in the public chat list
|
// does not show events already in the public chat list
|
||||||
(channel == null || channel !in followChannels) &&
|
(channel == null || channel !in followChannels) &&
|
||||||
// does not show people the user already follows
|
// does not show people the user already follows
|
||||||
|
|||||||
@@ -246,30 +246,32 @@ fun ChatroomMessageCompose(
|
|||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
val event = note.event
|
val event = note.event
|
||||||
if (event is ChannelCreateEvent) {
|
if (event is ChannelCreateEvent) {
|
||||||
|
val channelInfo = event.channelInfo()
|
||||||
Text(
|
Text(
|
||||||
text = note.author?.toBestDisplayName()
|
text = note.author?.toBestDisplayName()
|
||||||
.toString() + " ${stringResource(R.string.created)} " + (
|
.toString() + " ${stringResource(R.string.created)} " + (
|
||||||
event.channelInfo().name
|
channelInfo.name
|
||||||
?: ""
|
?: ""
|
||||||
) + " ${stringResource(R.string.with_description_of)} '" + (
|
) + " ${stringResource(R.string.with_description_of)} '" + (
|
||||||
event.channelInfo().about
|
channelInfo.about
|
||||||
?: ""
|
?: ""
|
||||||
) + "', ${stringResource(R.string.and_picture)} '" + (
|
) + "', ${stringResource(R.string.and_picture)} '" + (
|
||||||
event.channelInfo().picture
|
channelInfo.picture
|
||||||
?: ""
|
?: ""
|
||||||
) + "'"
|
) + "'"
|
||||||
)
|
)
|
||||||
} else if (event is ChannelMetadataEvent) {
|
} else if (event is ChannelMetadataEvent) {
|
||||||
|
val channelInfo = event.channelInfo()
|
||||||
Text(
|
Text(
|
||||||
text = note.author?.toBestDisplayName()
|
text = note.author?.toBestDisplayName()
|
||||||
.toString() + " ${stringResource(R.string.changed_chat_name_to)} '" + (
|
.toString() + " ${stringResource(R.string.changed_chat_name_to)} '" + (
|
||||||
event.channelInfo().name
|
channelInfo.name
|
||||||
?: ""
|
?: ""
|
||||||
) + "$', {stringResource(R.string.description_to)} '" + (
|
) + "$', {stringResource(R.string.description_to)} '" + (
|
||||||
event.channelInfo().about
|
channelInfo.about
|
||||||
?: ""
|
?: ""
|
||||||
) + "', ${stringResource(R.string.and_picture_to)} '" + (
|
) + "', ${stringResource(R.string.and_picture_to)} '" + (
|
||||||
event.channelInfo().picture
|
channelInfo.picture
|
||||||
?: ""
|
?: ""
|
||||||
) + "'"
|
) + "'"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
import com.vitorpamplona.amethyst.NotificationCache
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
|
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -46,7 +48,7 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
|||||||
} else {
|
} else {
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = messageSetCard) {
|
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
isNew =
|
isNew =
|
||||||
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
||||||
@@ -64,14 +66,29 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier.background(backgroundColor).combinedClickable(
|
modifier = Modifier.background(backgroundColor).combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (noteEvent !is ChannelMessageEvent) {
|
if (noteEvent is ChannelMessageEvent) {
|
||||||
navController.navigate("Note/${note.idHex}") {
|
|
||||||
launchSingleTop = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
note.channel()?.let {
|
note.channel()?.let {
|
||||||
navController.navigate("Channel/${it.idHex}")
|
navController.navigate("Channel/${it.idHex}")
|
||||||
}
|
}
|
||||||
|
} else if (noteEvent is PrivateDmEvent) {
|
||||||
|
val replyAuthorBase =
|
||||||
|
(note.event as? PrivateDmEvent)
|
||||||
|
?.recipientPubKey()
|
||||||
|
?.let { LocalCache.getOrCreateUser(it) }
|
||||||
|
|
||||||
|
var userToComposeOn = note.author!!
|
||||||
|
|
||||||
|
if (replyAuthorBase != null) {
|
||||||
|
if (note.author == accountViewModel.userProfile()) {
|
||||||
|
userToComposeOn = replyAuthorBase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
navController.navigate("Room/${userToComposeOn.pubkeyHex}")
|
||||||
|
} else {
|
||||||
|
navController.navigate("Note/${note.idHex}") {
|
||||||
|
launchSingleTop = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = { popupExpanded = true }
|
onLongClick = { popupExpanded = true }
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
|||||||
} else {
|
} else {
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = multiSetCard) {
|
LaunchedEffect(key1 = multiSetCard.createdAt()) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ fun FastNoteAuthorPicture(
|
|||||||
val userState by author.live().metadata.observeAsState()
|
val userState by author.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val user = userState?.user ?: return
|
||||||
|
|
||||||
val showFollowingMark = userAccount.isFollowingCached(user) || user == userAccount
|
val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount
|
||||||
|
|
||||||
UserPicture(
|
UserPicture(
|
||||||
userHex = user.pubkeyHex,
|
userHex = user.pubkeyHex,
|
||||||
|
|||||||
@@ -96,7 +96,6 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
|
|||||||
|
|
||||||
user.nip05()?.let { nip05 ->
|
user.nip05()?.let { nip05 ->
|
||||||
if (nip05.split("@").size == 2) {
|
if (nip05.split("@").size == 2) {
|
||||||
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
|
||||||
Column(modifier = columnModifier) {
|
Column(modifier = columnModifier) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (nip05.split("@")[0] != "_") {
|
if (nip05.split("@")[0] != "_") {
|
||||||
@@ -108,6 +107,7 @@ fun ObserveDisplayNip05Status(baseUser: User, columnModifier: Modifier = Modifie
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nip05Verified by nip05VerificationAsAState(user.info!!, user.pubkeyHex)
|
||||||
if (nip05Verified == null) {
|
if (nip05Verified == null) {
|
||||||
Icon(
|
Icon(
|
||||||
tint = Color.Yellow,
|
tint = Color.Yellow,
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ fun NoteCompose(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("Time", "Note Compose in $elapsed for ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
|
Log.d("Time", "Note Compose in $elapsed for ${baseNote.idHex} ${baseNote.event?.kind()} ${baseNote.event?.content()?.split("\n")?.get(0)?.take(100)}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@@ -582,7 +582,9 @@ fun DisplayFollowingHashtagsInPost(
|
|||||||
var firstTag by remember { mutableStateOf<String?>(null) }
|
var firstTag by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteEvent) {
|
LaunchedEffect(key1 = noteEvent) {
|
||||||
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
|
withContext(Dispatchers.IO) {
|
||||||
|
firstTag = noteEvent.firstIsTaggedHashes(account.followingTagSet())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column() {
|
Column() {
|
||||||
@@ -1108,12 +1110,14 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = note) {
|
LaunchedEffect(key1 = note) {
|
||||||
state = DropDownParams(
|
withContext(Dispatchers.IO) {
|
||||||
accountViewModel.isFollowing(note.author),
|
state = DropDownParams(
|
||||||
accountViewModel.isInPrivateBookmarks(note),
|
accountViewModel.isFollowing(note.author),
|
||||||
accountViewModel.isInPublicBookmarks(note),
|
accountViewModel.isInPrivateBookmarks(note),
|
||||||
accountViewModel.isLoggedUser(note.author)
|
accountViewModel.isInPublicBookmarks(note),
|
||||||
)
|
accountViewModel.isLoggedUser(note.author)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ fun PollNote(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
val account = accountState?.account ?: return
|
|
||||||
|
|
||||||
val zapsState by baseNote.live().zaps.observeAsState()
|
val zapsState by baseNote.live().zaps.observeAsState()
|
||||||
val zappedNote = zapsState?.note ?: return
|
val zappedNote = zapsState?.note ?: return
|
||||||
|
|
||||||
@@ -72,7 +69,7 @@ fun PollNote(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(vertical = 3.dp)
|
modifier = Modifier.padding(vertical = 3.dp)
|
||||||
) {
|
) {
|
||||||
if (zappedNote.author == account.userProfile() || zappedNote.isZappedBy(account.userProfile())) {
|
if (accountViewModel.isLoggedUser(zappedNote.author) || zappedNote.isZappedBy(accountViewModel.userProfile())) {
|
||||||
ZapVote(
|
ZapVote(
|
||||||
baseNote,
|
baseNote,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
@@ -90,7 +87,7 @@ fun PollNote(
|
|||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
modifier = Modifier.matchParentSize(),
|
modifier = Modifier.matchParentSize(),
|
||||||
color = color,
|
color = color,
|
||||||
progress = optionTally
|
progress = optionTally.toFloat()
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
@@ -101,7 +98,7 @@ fun PollNote(
|
|||||||
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
|
modifier = Modifier.padding(horizontal = 10.dp).width(40.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${(optionTally * 100).roundToInt()}%",
|
text = "${(optionTally.toFloat() * 100).roundToInt()}%",
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -210,7 +207,7 @@ fun ZapVote(
|
|||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} else if (zappedNote?.author == account.userProfile()) {
|
} else if (accountViewModel.isLoggedUser(zappedNote?.author)) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Toast
|
Toast
|
||||||
.makeText(
|
.makeText(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.Note
|
|||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
import java.math.RoundingMode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class PollNoteViewModel {
|
class PollNoteViewModel {
|
||||||
@@ -16,7 +17,9 @@ class PollNoteViewModel {
|
|||||||
var valueMaximum: Int? = null
|
var valueMaximum: Int? = null
|
||||||
var valueMinimum: Int? = null
|
var valueMinimum: Int? = null
|
||||||
private var closedAt: Int? = null
|
private var closedAt: Int? = null
|
||||||
var consensusThreshold: Float? = null
|
var consensusThreshold: BigDecimal? = null
|
||||||
|
|
||||||
|
var totalZapped: BigDecimal = BigDecimal.ZERO
|
||||||
|
|
||||||
fun load(note: Note?) {
|
fun load(note: Note?) {
|
||||||
pollNote = note
|
pollNote = note
|
||||||
@@ -24,8 +27,10 @@ class PollNoteViewModel {
|
|||||||
pollOptions = pollEvent?.pollOptions()
|
pollOptions = pollEvent?.pollOptions()
|
||||||
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
|
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
|
||||||
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
|
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
|
||||||
consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)
|
consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
|
||||||
closedAt = pollEvent?.getTagInt(CLOSED_AT)
|
closedAt = pollEvent?.getTagInt(CLOSED_AT)
|
||||||
|
|
||||||
|
totalZapped = totalZapped()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
||||||
@@ -73,47 +78,39 @@ class PollNoteViewModel {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun optionVoteTally(op: Int): Float {
|
fun optionVoteTally(op: Int): BigDecimal {
|
||||||
val tally = zappedPollOptionAmount(op).toFloat().div(zappedVoteTotal())
|
return if (totalZapped.compareTo(BigDecimal.ZERO) > 0) {
|
||||||
return if (tally.isNaN()) { // catch div by 0
|
zappedPollOptionAmount(op).divide(totalZapped, 2, RoundingMode.HALF_UP)
|
||||||
0f
|
} else {
|
||||||
} else { tally }
|
BigDecimal.ZERO
|
||||||
}
|
|
||||||
|
|
||||||
private fun zappedVoteTotal(): Float {
|
|
||||||
var total = 0f
|
|
||||||
pollOptions?.keys?.forEach {
|
|
||||||
total += zappedPollOptionAmount(it).toFloat()
|
|
||||||
}
|
}
|
||||||
return total
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
fun isPollOptionZappedBy(option: Int, user: User): Boolean {
|
||||||
if (pollNote?.zaps?.any { it.key.author == user } == true) {
|
if (pollNote?.zaps?.any { it.key.author === user } == true) {
|
||||||
pollNote!!.zaps.mapNotNull { it.value?.event }
|
pollNote!!.zaps
|
||||||
.filterIsInstance<LnZapEvent>()
|
.any {
|
||||||
.map {
|
val event = it.value?.event as? LnZapEvent
|
||||||
val zappedOption = it.zappedPollOption()
|
event?.zappedPollOption() == option && event.zappedRequestAuthor() == user.pubkeyHex
|
||||||
if (zappedOption == option && it.zappedRequestAuthor() == user.pubkeyHex) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
fun zappedPollOptionAmount(option: Int): BigDecimal {
|
||||||
return if (pollNote != null) {
|
return pollNote?.zaps?.values?.sumOf {
|
||||||
pollNote!!.zaps.mapNotNull { it.value?.event }
|
val event = it?.event as? LnZapEvent
|
||||||
.filterIsInstance<LnZapEvent>()
|
if (event?.zappedPollOption() == option) {
|
||||||
.mapNotNull {
|
event.amount ?: BigDecimal(0)
|
||||||
val zappedOption = it.zappedPollOption()
|
} else {
|
||||||
if (zappedOption == option) {
|
BigDecimal(0)
|
||||||
it.amount
|
}
|
||||||
} else { null }
|
} ?: BigDecimal(0)
|
||||||
}.sumOf { it }
|
}
|
||||||
} else {
|
|
||||||
BigDecimal(0)
|
fun totalZapped(): BigDecimal {
|
||||||
}
|
return pollNote?.zaps?.values?.sumOf {
|
||||||
|
(it?.event as? LnZapEvent)?.amount ?: BigDecimal(0)
|
||||||
|
} ?: BigDecimal(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
|
|||||||
|
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = zapSetCard) {
|
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||||
|
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -19,6 +20,7 @@ import androidx.compose.material.rememberScaffoldState
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
@@ -34,10 +36,12 @@ import com.vitorpamplona.amethyst.ui.navigation.Route
|
|||||||
import com.vitorpamplona.amethyst.ui.navigation.currentRoute
|
import com.vitorpamplona.amethyst.ui.navigation.currentRoute
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountState
|
import com.vitorpamplona.amethyst.ui.screen.AccountState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
|
fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel, startingPage: String? = null) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
|
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
|
||||||
val sheetState = rememberModalBottomSheetState(
|
val sheetState = rememberModalBottomSheetState(
|
||||||
@@ -64,6 +68,9 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
|
|||||||
},
|
},
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
DrawerContent(navController, scaffoldState, sheetState, accountViewModel)
|
DrawerContent(navController, scaffoldState, sheetState, accountViewModel)
|
||||||
|
BackHandler(enabled = scaffoldState.drawerState.isOpen) {
|
||||||
|
coroutineScope.launch { scaffoldState.drawerState.close() }
|
||||||
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingButtons(navController, accountStateViewModel)
|
FloatingButtons(navController, accountStateViewModel)
|
||||||
|
|||||||
@@ -263,9 +263,30 @@
|
|||||||
<string name="wallet_connect_service_secret_placeholder">nsec / hex privát kulcs</string>
|
<string name="wallet_connect_service_secret_placeholder">nsec / hex privát kulcs</string>
|
||||||
|
|
||||||
<string name="pledge_amount_in_sats">Hozzájárulás összege sats-ban</string>
|
<string name="pledge_amount_in_sats">Hozzájárulás összege sats-ban</string>
|
||||||
|
<string name="post_poll">Szavazás Létrehozása</string>
|
||||||
|
<string name="poll_heading_required">Szükséges mezők:</string>
|
||||||
|
<string name="poll_zap_recipients">Zap-et kapják</string>
|
||||||
|
<string name="poll_primary_description">Szavazás elsődleges leírása…</string>
|
||||||
|
<string name="poll_option_index">Szavazás %s megoszlása</string>
|
||||||
|
<string name="poll_option_description">Szavazás opcióinak leírása</string>
|
||||||
|
<string name="poll_heading_optional">Kiegészítő mezők:</string>
|
||||||
|
<string name="poll_zap_value_min">Zap minimum</string>
|
||||||
|
<string name="poll_zap_value_max">Zap maximum</string>
|
||||||
|
<string name="poll_consensus_threshold">Konszenzus</string>
|
||||||
|
<string name="poll_consensus_threshold_percent">(0–100)%</string>
|
||||||
|
<string name="poll_closing_time">Szavazás lezárása</string>
|
||||||
|
<string name="poll_closing_time_days">napok</string>
|
||||||
|
<string name="poll_is_closed">A szavazásra nem lehet már új szavazatot leadni</string>
|
||||||
|
<string name="poll_zap_amount">Zap összege</string>
|
||||||
|
<string name="one_vote_per_user_on_atomic_votes">Az ilyen típusú szavazásokon felhasználónként csak egy szavazat engedélyezett</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="looking_for_event">"%1$s esemény keresése"</string>
|
<string name="looking_for_event">"%1$s esemény keresése"</string>
|
||||||
|
|
||||||
<string name="custom_zaps_add_a_message">Nyilvános üzenet hozzáadása</string>
|
<string name="custom_zaps_add_a_message">Nyilvános üzenet hozzáadása</string>
|
||||||
<string name="custom_zaps_add_a_message_example">Köszönöm a kemény munkát!</string>
|
<string name="custom_zaps_add_a_message_example">Köszönöm a kemény munkát!</string>
|
||||||
|
|
||||||
|
<string name="lightning_create_and_add_invoice">Létrehoz és Hozzáad</string>
|
||||||
|
<string name="poll_author_no_vote">A szavazás létrehozója sajátjára nem szavazhat.</string>
|
||||||
|
<string name="poll_hashtag" translatable="false">#zappoll</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+2
-2
@@ -10,8 +10,8 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '7.4.2' apply false
|
id 'com.android.application' version '8.0.0' apply false
|
||||||
id 'com.android.library' version '7.4.2' apply false
|
id 'com.android.library' version '8.0.0' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
id 'org.jetbrains.kotlin.android' version '1.8.10' apply false
|
||||||
id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false
|
id 'org.jetbrains.kotlin.jvm' version '1.8.10' apply false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,3 +22,5 @@ kotlin.code.style=official
|
|||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
android.enableR8.fullMode=true
|
android.enableR8.fullMode=true
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonFinalResIds=false
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
#Wed Jan 04 09:23:50 EST 2023
|
#Wed Jan 04 09:23:50 EST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
Reference in New Issue
Block a user