This commit is contained in:
greenart7c3 2023-09-23 12:40:17 -03:00
commit ed4c51b664
43 changed files with 358 additions and 227 deletions

View File

@ -13,8 +13,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 34
versionCode 304
versionName "0.77.7"
versionCode 305
versionName "0.77.8"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {

View File

@ -145,6 +145,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
}
override fun consume(event: Event, relay: Relay) {
checkNotInMainThread()
if (LocalCache.justVerify(event)) {
if (event is GiftWrapEvent) {
val privateKey = account.keyPair.privKey

View File

@ -194,7 +194,7 @@ private fun RenderSearch(
}
}
DisposableEffect(Unit) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Join Start")

View File

@ -32,7 +32,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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
@ -206,34 +205,34 @@ fun VideoViewInner(
)
}
val mediaItem = remember(videoUri) {
MediaItem.Builder()
.setMediaId(videoUri)
.setUri(videoUri)
.setMediaMetadata(
MediaMetadata.Builder()
.setArtist(authorName?.ifBlank { null })
.setTitle(title?.ifBlank { null } ?: videoUri)
.setArtworkUri(
try {
if (artworkUri != null) {
Uri.parse(artworkUri)
} else {
null
}
} catch (e: Exception) {
null
}
)
.build()
)
.build()
}
if (!automaticallyStartPlayback.value) {
ImageUrlWithDownloadButton(url = videoUri, showImage = automaticallyStartPlayback)
} else {
VideoPlayerActiveMutex(videoUri) { activeOnScreen ->
val mediaItem = remember(videoUri) {
MediaItem.Builder()
.setMediaId(videoUri)
.setUri(videoUri)
.setMediaMetadata(
MediaMetadata.Builder()
.setArtist(authorName?.ifBlank { null })
.setTitle(title?.ifBlank { null } ?: videoUri)
.setArtworkUri(
try {
if (artworkUri != null) {
Uri.parse(artworkUri)
} else {
null
}
} catch (e: Exception) {
null
}
)
.build()
)
.build()
}
GetVideoController(
mediaItem = mediaItem,
videoUri = videoUri,
@ -282,56 +281,52 @@ fun GetVideoController(
UUID.randomUUID().toString()
}
val scope = rememberCoroutineScope()
// Prepares a VideoPlayer from the foreground service.
DisposableEffect(key1 = videoUri) {
// If it is not null, the user might have come back from a playing video, like clicking on
// the notification of the video player.
if (controller.value == null) {
scope.launch(Dispatchers.IO) {
PlaybackClientController.prepareController(
uid,
videoUri,
nostrUriCallback,
context
) {
// checks again because of race conditions.
if (controller.value == null) { // still prone to race conditions.
controller.value = it
PlaybackClientController.prepareController(
uid,
videoUri,
nostrUriCallback,
context
) {
// checks again because of race conditions.
if (controller.value == null) { // still prone to race conditions.
controller.value = it
if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) {
if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) {
// There is a video playing, start this one on mute.
controller.value?.volume = 0f
} else {
// There is no other video playing. Use the default mute state to
// decide if sound is on or not.
controller.value?.volume = if (defaultToStart) 0f else 1f
}
}
controller.value?.setMediaItem(mediaItem)
controller.value?.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one
it.stop()
it.release()
controller.value?.let {
if (it.playbackState == Player.STATE_IDLE || it.playbackState == Player.STATE_ENDED) {
if (it.isPlaying) {
// There is a video playing, start this one on mute.
controller.value?.volume = 0f
it.volume = 0f
} else {
// There is no other video playing. Use the default mute state to
// decide if sound is on or not.
controller.value?.volume = if (defaultToStart) 0f else 1f
it.volume = if (defaultToStart) 0f else 1f
}
}
controller.value?.setMediaItem(mediaItem)
controller.value?.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one
it.stop()
it.release()
controller.value?.let {
if (it.playbackState == Player.STATE_IDLE || it.playbackState == Player.STATE_ENDED) {
if (it.isPlaying) {
// There is a video playing, start this one on mute.
it.volume = 0f
} else {
// There is no other video playing. Use the default mute state to
// decide if sound is on or not.
it.volume = if (defaultToStart) 0f else 1f
}
it.setMediaItem(mediaItem)
it.prepare()
}
it.setMediaItem(mediaItem)
it.prepare()
}
}
}
@ -372,35 +367,33 @@ fun GetVideoController(
// if the controller is null, restarts the controller with a new one
// if the controller is not null, just continue playing what the controller was playing
if (controller.value == null) {
scope.launch(Dispatchers.IO) {
PlaybackClientController.prepareController(
uid,
videoUri,
nostrUriCallback,
context
) {
// checks again to make sure no other thread has created a controller.
if (controller.value == null) {
controller.value = it
PlaybackClientController.prepareController(
uid,
videoUri,
nostrUriCallback,
context
) {
// checks again to make sure no other thread has created a controller.
if (controller.value == null) {
controller.value = it
if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) {
// There is a video playing, start this one on mute.
controller.value?.volume = 0f
} else {
// There is no other video playing. Use the default mute state to
// decide if sound is on or not.
controller.value?.volume = if (defaultToStart) 0f else 1f
}
if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) {
// There is a video playing, start this one on mute.
controller.value?.volume = 0f
} else {
// There is no other video playing. Use the default mute state to
// decide if sound is on or not.
controller.value?.volume = if (defaultToStart) 0f else 1f
}
controller.value?.setMediaItem(mediaItem)
controller.value?.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one
it.stop()
it.release()
}
controller.value?.setMediaItem(mediaItem)
controller.value?.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one
it.stop()
it.release()
}
}
}
@ -697,7 +690,7 @@ fun ControlWhenPlayerIsActive(
val view = LocalView.current
// Keeps the screen on while playing and viewing videos.
DisposableEffect(key1 = controller) {
DisposableEffect(key1 = controller, key2 = view) {
val listener = object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
// doesn't consider the mutex because the screen can turn off if the video

View File

@ -614,7 +614,7 @@ fun ZoomableImageDialog(
) {
val view = LocalView.current
DisposableEffect(key1 = Unit) {
DisposableEffect(key1 = view) {
if (Build.VERSION.SDK_INT >= 30) {
view.windowInsetsController?.hide(
android.view.WindowInsets.Type.systemBars()

View File

@ -284,7 +284,7 @@ fun AppNavigation(
actionableNextPage = null
}
DisposableEffect(navController) {
DisposableEffect(activity) {
val consumer = Consumer<Intent> { intent ->
val uri = intent?.data?.toString()
val newPage = uriToRoute(uri)

View File

@ -64,7 +64,6 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
import com.vitorpamplona.amethyst.model.LiveActivitiesChannel
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.service.NostrAccountDataSource
import com.vitorpamplona.amethyst.service.NostrChannelDataSource
@ -110,7 +109,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.LongChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LongRoomHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.RoomNameOnlyDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShortChannelHeader
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowVideoStreaming
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
@ -355,11 +353,6 @@ private fun ChannelTopBar(
) {
LoadChannel(baseChannelHex = id, accountViewModel) { baseChannel ->
FlexibleTopBarWithBackButton(
prefixRow = {
if (baseChannel is LiveActivitiesChannel) {
ShowVideoStreaming(baseChannel, accountViewModel)
}
},
title = {
ShortChannelHeader(
baseChannel = baseChannel,

View File

@ -3101,26 +3101,28 @@ fun DisplayUncitedHashtags(
eventContent: String,
nav: (String) -> Unit
) {
val hasHashtags = remember {
val hasHashtags = remember(eventContent) {
hashtags.isNotEmpty()
}
if (hasHashtags) {
val unusedHashtags = remember(eventContent) {
hashtags.filter { !eventContent.contains(it, true) }
}
FlowRow(
modifier = remember { Modifier.padding(top = 5.dp) }
) {
hashtags.forEach { hashtag ->
if (!eventContent.contains(hashtag, true)) {
ClickableText(
text = AnnotatedString("#$hashtag "),
onClick = { nav("Hashtag/$hashtag") },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
unusedHashtags.forEach { hashtag ->
ClickableText(
text = remember { AnnotatedString("#$hashtag ") },
onClick = { nav("Hashtag/$hashtag") },
style = LocalTextStyle.current.copy(
color = MaterialTheme.colors.primary.copy(
alpha = 0.52f
)
)
}
)
}
}
}

View File

@ -33,9 +33,12 @@ class PollNoteViewModel : ViewModel() {
private var pollEvent: PollNoteEvent? = null
private var pollOptions: Map<Int, String>? = null
private var valueMaximum: Int? = null
private var valueMinimum: Int? = null
private var closedAt: Int? = null
private var valueMaximum: Long? = null
private var valueMinimum: Long? = null
private var valueMaximumBD: BigDecimal? = null
private var valueMinimumBD: BigDecimal? = null
private var closedAt: Long? = null
private var consensusThreshold: BigDecimal? = null
private var totalZapped: BigDecimal = BigDecimal.ZERO
@ -49,10 +52,12 @@ class PollNoteViewModel : ViewModel() {
pollNote = note
pollEvent = pollNote?.event as PollNoteEvent
pollOptions = pollEvent?.pollOptions()
valueMaximum = pollEvent?.getTagInt(VALUE_MAXIMUM)
valueMinimum = pollEvent?.getTagInt(VALUE_MINIMUM)
consensusThreshold = pollEvent?.getTagInt(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
closedAt = pollEvent?.getTagInt(CLOSED_AT)
valueMaximum = pollEvent?.getTagLong(VALUE_MAXIMUM)
valueMinimum = pollEvent?.getTagLong(VALUE_MINIMUM)
valueMinimumBD = valueMinimum?.let { BigDecimal(it) }
valueMaximumBD = valueMaximum?.let { BigDecimal(it) }
consensusThreshold = pollEvent?.getTagLong(CONSENSUS_THRESHOLD)?.toFloat()?.div(100)?.toBigDecimal()
closedAt = pollEvent?.getTagLong(CLOSED_AT)
}
fun refreshTallies() {
@ -110,6 +115,29 @@ class PollNoteViewModel : ViewModel() {
} catch (e: Exception) { null }
}
fun isValidInputVoteAmount(amount: BigDecimal?): Boolean {
if (amount == null) {
return false
} else if (valueMinimum == null && valueMaximum == null) {
if (amount > BigDecimal.ZERO) {
return true
}
} else if (valueMinimum == null) {
if (amount > BigDecimal.ZERO && amount <= valueMaximumBD!!) {
return true
}
} else if (valueMaximum == null) {
if (amount >= valueMinimumBD!!) {
return true
}
} else {
if ((valueMinimumBD!! <= amount) && (amount <= valueMaximumBD!!)) {
return true
}
}
return false
}
fun isValidInputVoteAmount(amount: Long?): Boolean {
if (amount == null) {
return false
@ -145,24 +173,29 @@ class PollNoteViewModel : ViewModel() {
private fun zappedPollOptionAmount(option: Int): BigDecimal {
return pollNote?.zaps?.values?.sumOf {
val event = it?.event as? LnZapEvent
if (event?.zappedPollOption() == option) {
event.amount ?: BigDecimal(0)
val zapAmount = event?.amount ?: BigDecimal.ZERO
val isValidAmount = isValidInputVoteAmount(event?.amount)
if (isValidAmount && event?.zappedPollOption() == option) {
zapAmount
} else {
BigDecimal(0)
BigDecimal.ZERO
}
} ?: BigDecimal(0)
} ?: BigDecimal.ZERO
}
private fun totalZapped(): BigDecimal {
return pollNote?.zaps?.values?.sumOf {
val zapEvent = (it?.event as? LnZapEvent)
val zapAmount = zapEvent?.amount ?: BigDecimal.ZERO
val isValidAmount = isValidInputVoteAmount(zapEvent?.amount)
if (zapEvent?.zappedPollOption() != null) {
zapEvent.amount ?: BigDecimal(0)
if (isValidAmount && zapEvent?.zappedPollOption() != null) {
zapAmount
} else {
BigDecimal(0)
BigDecimal.ZERO
}
} ?: BigDecimal(0)
} ?: BigDecimal.ZERO
}
fun createZapOptionsThatMatchThePollingParameters(): List<Long> {
@ -176,8 +209,8 @@ class PollNoteViewModel : ViewModel() {
}
}
}
valueMinimum?.let { options.add(it.toLong()) }
valueMaximum?.let { options.add(it.toLong()) }
valueMinimum?.let { options.add(it) }
valueMaximum?.let { options.add(it) }
return options.toSet().sorted()
}

View File

@ -386,6 +386,19 @@ fun UpdateZapAmountDialog(
Modifier.weight(1f)
)
/* TODO: Find a way to open this in the PWA
IconButton(onClick = {
onClose()
runCatching { uri.openUri("https://app.mutinywallet.com/settings/connections?callbackUri=nostr+walletconnect&name=Amethyst") }
}) {
Icon(
painter = painterResource(R.mipmap.mutiny),
null,
modifier = Modifier.size(24.dp),
tint = Color.Unspecified
)
}*/
IconButton(onClick = {
onClose()
runCatching { uri.openUri("https://nwc.getalby.com/apps/new?c=Amethyst") }

View File

@ -4,7 +4,6 @@ import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import com.google.zxing.client.android.Intents
import com.journeyapps.barcodescanner.ScanContract
@ -40,8 +39,6 @@ fun NIP19QrCodeScanner(onScan: (String?) -> Unit) {
@Composable
fun SimpleQrCodeScanner(onScan: (String?) -> Unit) {
val lifecycleOwner = LocalLifecycleOwner.current
val qrLauncher =
rememberLauncherForActivityResult(ScanContract()) {
if (it.contents != null) {
@ -59,7 +56,7 @@ fun SimpleQrCodeScanner(onScan: (String?) -> Unit) {
addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
}
DisposableEffect(lifecycleOwner) {
DisposableEffect(Unit) {
qrLauncher.launch(scanOptions)
onDispose { }
}

View File

@ -87,6 +87,8 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<ZapReqResponse>) : View
checkNotInMainThread()
LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread()
invalidateData()
}
}

View File

@ -70,16 +70,20 @@ class RelayFeedViewModel : ViewModel() {
}
fun subscribeTo(user: User) {
currentUser = user
user.live().relays.observeForever(listener)
user.live().relayInfo.observeForever(listener)
invalidateData()
if (currentUser != user) {
currentUser = user
user.live().relays.observeForever(listener)
user.live().relayInfo.observeForever(listener)
invalidateData()
}
}
fun unsubscribeTo(user: User) {
user.live().relays.removeObserver(listener)
user.live().relayInfo.removeObserver(listener)
currentUser = null
if (currentUser == user) {
user.live().relays.removeObserver(listener)
user.live().relayInfo.removeObserver(listener)
currentUser = null
}
}
private val bundler = BundledUpdate(250, Dispatchers.IO)

View File

@ -47,7 +47,7 @@ fun rememberForeverLazyListState(
savedOffset.roundToInt()
)
}
DisposableEffect(Unit) {
DisposableEffect(scrollState) {
onDispose {
val lastIndex = scrollState.firstVisibleItemIndex
val lastOffset = scrollState.firstVisibleItemScrollOffset

View File

@ -118,6 +118,8 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel(), In
checkNotInMainThread()
LocalCache.live.newEventBundles.collect { newNotes ->
checkNotInMainThread()
invalidateData()
}
}

View File

@ -196,9 +196,6 @@ fun ChannelScreen(
val lifeCycleOwner = LocalLifecycleOwner.current
LaunchedEffect(Unit) {
NostrChannelDataSource.start()
feedViewModel.invalidateData(true)
launch(Dispatchers.IO) {
newPostModel.imageUploadingError.collect { error ->
withContext(Dispatchers.Main) {
@ -209,6 +206,17 @@ fun ChannelScreen(
}
DisposableEffect(accountViewModel) {
NostrChannelDataSource.loadMessagesBetween(accountViewModel.account, channel)
NostrChannelDataSource.start()
feedViewModel.invalidateData(true)
onDispose {
NostrChannelDataSource.clear()
NostrChannelDataSource.stop()
}
}
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Channel Start")
@ -240,6 +248,9 @@ fun ChannelScreen(
.weight(1f, true)
}
) {
if (channel is LiveActivitiesChannel) {
ShowVideoStreaming(channel, accountViewModel)
}
RefreshingChatroomFeedView(
viewModel = feedViewModel,
accountViewModel = accountViewModel,

View File

@ -67,7 +67,7 @@ fun ChatroomListScreen(
WatchAccountForListScreen(knownFeedViewModel, newFeedViewModel, accountViewModel)
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
NostrChatroomListDataSource.start()

View File

@ -234,9 +234,6 @@ fun ChatroomScreen(
LaunchedEffect(room, accountViewModel) {
launch(Dispatchers.IO) {
NostrChatroomDataSource.start()
feedViewModel.invalidateData()
newPostModel.imageUploadingError.collect { error ->
withContext(Dispatchers.Main) {
Toast.makeText(context, error, Toast.LENGTH_SHORT).show()
@ -246,6 +243,16 @@ fun ChatroomScreen(
}
DisposableEffect(room, accountViewModel) {
NostrChatroomDataSource.loadMessagesBetween(accountViewModel.account, room)
NostrChatroomDataSource.start()
feedViewModel.invalidateData()
onDispose {
NostrChatroomDataSource.stop()
}
}
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Private Message Start")

View File

@ -56,7 +56,7 @@ fun CommunityScreen(note: AddressableNote, feedViewModel: NostrCommunityFeedView
feedViewModel.invalidateData()
}
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Community Start")

View File

@ -88,7 +88,7 @@ fun DiscoverScreen(
accountViewModel = accountViewModel
)
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Discovery Start")

View File

@ -65,12 +65,16 @@ fun GeoHashScreen(tag: String, feedViewModel: NostrGeoHashFeedViewModel, account
NostrGeohashDataSource.loadHashtag(tag)
LaunchedEffect(tag) {
DisposableEffect(tag) {
NostrGeohashDataSource.start()
feedViewModel.invalidateData()
onDispose {
NostrGeohashDataSource.loadHashtag(null)
NostrGeohashDataSource.stop()
}
}
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Hashtag Start")
@ -88,8 +92,6 @@ fun GeoHashScreen(tag: String, feedViewModel: NostrGeoHashFeedViewModel, account
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
NostrGeohashDataSource.loadHashtag(null)
NostrGeohashDataSource.stop()
}
}

View File

@ -12,7 +12,6 @@ import androidx.compose.material.Divider
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
@ -61,12 +60,17 @@ fun HashtagScreen(tag: String, feedViewModel: NostrHashtagFeedViewModel, account
NostrHashtagDataSource.loadHashtag(tag)
LaunchedEffect(tag) {
DisposableEffect(tag) {
NostrHashtagDataSource.start()
feedViewModel.invalidateData()
onDispose {
NostrHashtagDataSource.loadHashtag(null)
NostrHashtagDataSource.stop()
}
}
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Hashtag Start")
@ -84,8 +88,6 @@ fun HashtagScreen(tag: String, feedViewModel: NostrHashtagFeedViewModel, account
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
NostrHashtagDataSource.loadHashtag(null)
NostrHashtagDataSource.stop()
}
}

View File

@ -61,7 +61,7 @@ fun HiddenUsersScreen(
) {
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Hidden Users Start")

View File

@ -63,7 +63,7 @@ fun HomeScreen(
}
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
NostrHomeDataSource.invalidateFilters()

View File

@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
@ -33,7 +34,10 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.compose.currentBackStackEntryAsState
@ -323,6 +327,20 @@ fun WatchNavStateToUpdateBarVisibility(navState: State<NavBackStackEntry?>, bott
LaunchedEffect(key1 = navState.value) {
bottomBarOffsetHeightPx.value = 0f
}
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
bottomBarOffsetHeightPx.value = 0f
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
}
}
}
@Composable

View File

@ -75,7 +75,7 @@ fun NotificationScreen(
CheckifItNeedsToRequestNotificationPermission()
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
NostrAccountDataSource.invalidateFilters()

View File

@ -238,11 +238,15 @@ fun ProfileScreen(
val lifeCycleOwner = LocalLifecycleOwner.current
LaunchedEffect(Unit) {
DisposableEffect(accountViewModel) {
NostrUserProfileDataSource.start()
onDispose {
NostrUserProfileDataSource.loadUserProfile(null)
NostrUserProfileDataSource.stop()
}
}
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Profidle Start")
@ -259,9 +263,6 @@ fun ProfileScreen(
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose {
lifeCycleOwner.lifecycle.removeObserver(observer)
println("Profile Dispose")
NostrUserProfileDataSource.loadUserProfile(null)
NostrUserProfileDataSource.stop()
}
}
@ -954,7 +955,17 @@ private fun DrawAdditionalInfo(
ClickableText(
text = AnnotatedString(website.removePrefix("https://")),
onClick = { website.let { runCatching { uri.openUri(it) } } },
onClick = {
website.let {
runCatching {
if (it.contains("://")) {
uri.openUri(it)
} else {
uri.openUri("http://$it")
}
}
}
},
style = LocalTextStyle.current.copy(color = MaterialTheme.colors.primary),
modifier = Modifier.padding(top = 1.dp, bottom = 1.dp, start = 5.dp)
)
@ -1567,6 +1578,13 @@ fun TabRelays(user: User, accountViewModel: AccountViewModel, nav: (String) -> U
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(user) {
feedViewModel.subscribeTo(user)
onDispose {
feedViewModel.unsubscribeTo(user)
}
}
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Profile Relay Start")

View File

@ -104,7 +104,7 @@ fun SearchScreen(
WatchAccountForSearchScreen(accountViewModel)
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Search Start")

View File

@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
@ -28,11 +27,15 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (Stri
NostrThreadDataSource.loadThread(noteId)
LaunchedEffect(noteId) {
DisposableEffect(noteId) {
feedViewModel.invalidateData(true)
onDispose {
NostrThreadDataSource.loadThread(null)
NostrThreadDataSource.stop()
}
}
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Thread Start")

View File

@ -89,7 +89,7 @@ fun VideoScreen(
WatchAccountForVideoScreen(videoFeedView = videoFeedView, accountViewModel = accountViewModel)
DisposableEffect(accountViewModel) {
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Video Start")

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -448,6 +448,7 @@
<string name="messages_group_descriptor">Členové této skupiny</string>
<string name="messages_new_subject_message">Vysvětlení členům</string>
<string name="messages_new_subject_message_placeholder">Změna názvu pro nové cíle.</string>
<string name="paste_from_clipboard">Vložit ze schránky</string>
<string name="language_description">Pro rozhraní aplikace</string>
<string name="theme_description">Tmavé, světlé nebo systémové téma</string>
<string name="automatically_load_images_gifs_description">Automaticky načítat obrázky a GIFy</string>
@ -458,6 +459,7 @@
<string name="copy_the_note_id_to_the_clipboard">Kopírovat ID poznámky do schránky</string>
<string name="created_at">Vytvořeno</string>
<string name="rules">Pravidla</string>
<string name="login_with_external_signer">Přihlásit se pomocí Amber</string>
<string name="status_update">Aktualizovat svůj stav</string>
<string name="lightning_wallets_not_found">Chyba při zpracování chybové zprávy</string>
<string name="poll_zap_value_min_max_explainer">Hlasy jsou váženy podle hodnoty zapu. Můžete nastavit minimální částku, abyste se vyhnuli spammerům, a maximální částku, abyste zabránili velkým zapperům, kteří by mohli ovládnout hlasování. Použijte stejnou částku v obou polích, aby byla hodnota každého hlasu stejná. Nechte prázdné pro přijetí libovolné částky.</string>
@ -473,27 +475,19 @@
<string name="active_for_chats">Chaty</string>
<string name="active_for_global">Celosvětové</string>
<string name="active_for_search">Hledání</string>
<string name="login_with_external_signer">Přihlásit se pomocí Amber</string>
<string name="paste_from_clipboard">Vložit ze schránky</string>
<string name="zap_split_title">Rozdělit a přeposlat Zaps</string>
<string name="zap_split_explainer">Podporované klienty budou Zapy rozdělovat a přeposílat uživatelům přidaným zde místo vám</string>
<string name="zap_split_serarch_and_add_user">Hledat a přidat uživatele</string>
<string name="zap_split_serarch_and_add_user_placeholder">Uživatelské jméno nebo zobrazované jméno</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">Uživatel %1$s nemá nastavenou bleskovou adresu pro přijímání satoshi</string>
<string name="zap_split_weight">Procenta</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">Rozdělování Zapů s</string>
<string name="forwarding_zaps_to">Přeposílání Zapů na</string>
<string name="lightning_wallets_not_found2">Bleskové peněženky nenalezený</string>
<string name="paid">Zaplaceno</string>
<string name="wallet_number">Peněženka %1$s</string>
<string name="error_opening_external_signer">Chyba při otevírání aplikace pro podpis</string>
<string name="sign_request_rejected">Žádost o podpis byla zamítnuta</string>
<string name="no_wallet_found_with_error">Nebyly nalezeny žádné peněženky pro platbu bleskové faktury (Chyba: %1$s). Prosím, nainstalujte si bleskovou peněženku pro použití Zapů</string>
<string name="no_wallet_found">Nebyly nalezeny žádné peněženky pro platbu bleskové faktury. Prosím, nainstalujte si bleskovou peněženku pro použití Zapů</string>
</resources>

View File

@ -446,6 +446,7 @@ anz der Bedingungen ist erforderlich</string>
<string name="messages_group_descriptor">Mitglieder dieser Gruppe</string>
<string name="messages_new_subject_message">Erklärung an Mitglieder</string>
<string name="messages_new_subject_message_placeholder">Ändern des Namens für die neuen Ziele.</string>
<string name="paste_from_clipboard">Aus Zwischenablage einfügen</string>
<string name="language_description">Für die App-Benutzeroberfläche</string>
<string name="theme_description">Dunkles, helles oder Systemdesign</string>
<string name="automatically_load_images_gifs_description">Bilder und GIFs automatisch laden</string>
@ -456,6 +457,7 @@ anz der Bedingungen ist erforderlich</string>
<string name="copy_the_note_id_to_the_clipboard">Notiz-ID in die Zwischenablage kopieren</string>
<string name="created_at">Erstellt am</string>
<string name="rules">Regeln</string>
<string name="login_with_external_signer">Mit Amber anmelden</string>
<string name="status_update">Status aktualisieren</string>
<string name="lightning_wallets_not_found">Fehler beim Verarbeiten der Fehlermeldung</string>
<string name="poll_zap_value_min_max_explainer">Die Abstimmungen werden nach der Höhe des Zaps gewichtet. Sie können einen Mindestbetrag festlegen, um Spam zu verhindern, und einen Höchstbetrag, um zu verhindern, dass große Zapper die Abstimmung dominieren. Verwenden Sie denselben Betrag in beiden Feldern, um sicherzustellen, dass jeder Stimme der gleiche Wert zukommt. Lassen Sie es leer, um jeden Betrag zu akzeptieren.</string>
@ -469,27 +471,18 @@ anz der Bedingungen ist erforderlich</string>
<string name="active_for_home">Startseite</string>
<string name="active_for_msg">Nachrichten</string>
<string name="active_for_search">Suche</string>
<string name="login_with_external_signer">Mit Amber anmelden</string>
<string name="paste_from_clipboard">Aus Zwischenablage einfügen</string>
<string name="zap_split_title">Zaps aufteilen und weiterleiten</string>
<string name="zap_split_explainer">Unterstützende Clients werden Zaps an die hier hinzugefügten Benutzer aufteilen und weiterleiten, anstatt an Sie</string>
<string name="zap_split_serarch_and_add_user">Benutzer suchen und hinzufügen</string>
<string name="zap_split_serarch_and_add_user_placeholder">Benutzername oder Anzeigename</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">Benutzer %1$s hat keine Lightning-Adresse eingerichtet, um Sats zu empfangen</string>
<string name="zap_split_weight">Prozent</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">Zaps aufteilen mit</string>
<string name="forwarding_zaps_to">Zaps weiterleiten an</string>
<string name="lightning_wallets_not_found2">Lightning-Wallets nicht gefunden</string>
<string name="paid">Bezahlt</string>
<string name="wallet_number">Wallet %1$s</string>
<string name="error_opening_external_signer">Fehler beim Öffnen der Signatur-App</string>
<string name="sign_request_rejected">Signaturanfrage abgelehnt</string>
<string name="no_wallet_found_with_error">Keine Wallets gefunden, um eine Lightning-Rechnung zu bezahlen (Fehler: %1$s). Installieren Sie eine Lightning-Wallet, um Zaps zu verwenden</string>
<string name="no_wallet_found">Keine Wallets gefunden, um eine Lightning-Rechnung zu bezahlen. Installieren Sie eine Lightning-Wallet, um Zaps zu verwenden</string>
</resources>

View File

@ -458,6 +458,7 @@
<string name="messages_group_descriptor">A csoport tagjai</string>
<string name="messages_new_subject_message">Magyarázat a csoport tagjainak</string>
<string name="messages_new_subject_message_placeholder">Az új célok érdekében, a név megváltoztatása.</string>
<string name="paste_from_clipboard">Beszúrás vágólapról</string>
<string name="language_description">Az applikáció felülete</string>
<string name="theme_description">Sötét, Világos vagy Rendszer által használt téma</string>
<string name="automatically_load_images_gifs_description">Képek és GIF-ek automatikus betöltése</string>
@ -468,6 +469,7 @@
<string name="copy_the_note_id_to_the_clipboard">A bejegyzésazonosító vágólapra másolása</string>
<string name="created_at">Létrehozva</string>
<string name="rules">Szabályok</string>
<string name="login_with_external_signer">Bejelentkezés Amber-el</string>
<string name="status_update">Állapotod megjelenítése</string>
<string name="lightning_wallets_not_found">Hiba a hibaüzenet elemzésekor</string>
<string name="poll_zap_value_min_max_explainer">A szavazatokat a Zap-ek összegével súlyozzuk. Beállíthatsz egy minimális összeget, hogy a kéretlen leveleket elkerüld, és egy maximális összeget annak elkerülésére, hogy a szavazás feletti irányítást a nagy Zapperek vegyék át. Mindkét mezőben ugyanazt az összeget használd, hogy minden szavazat azonos értéket kapjon. Bármilyen összeg elfogadásához, hagyjd üresen.</string>
@ -484,4 +486,20 @@
<string name="active_for_chats">Chat</string>
<string name="active_for_global">Globális</string>
<string name="active_for_search">Keresés</string>
<string name="zap_split_title">Zap-ek megosztása és továbbítása</string>
<string name="zap_split_explainer">A támogató kliensek a zapokat, az itt hozzáadott felhasználóknak helyetted felosztják és továbbítják</string>
<string name="zap_split_serarch_and_add_user">Felhasználó keresése és hozzáadása</string>
<string name="zap_split_serarch_and_add_user_placeholder">Felhasználónév vagy megjelenítendő név</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">A %1$s felhasználó a sat fogadáshoz nem rendelkezik LN cím beállítással</string>
<string name="zap_split_weight">Százalék</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">Zap-ek megosztása</string>
<string name="forwarding_zaps_to">Zap-ek továbbítása</string>
<string name="lightning_wallets_not_found2">Lightning tárca nem található</string>
<string name="paid">Fizetve</string>
<string name="wallet_number">Tárca: %1$s</string>
<string name="error_opening_external_signer">Hiba az aláíró alkalmazás megnyitásakor</string>
<string name="sign_request_rejected">Aláírási kérés elutasítva</string>
<string name="no_wallet_found_with_error">Nem található tárca a Lighning számla kifizetésére (Hiba: %1$s). Kérjük, a zap-ek használatához telepítsen egy Lightning pénztárcát</string>
<string name="no_wallet_found">Nem található tárca a Lighning számla kifizetésére. Kérjük, a zap-ek használatához telepítsen egy Lightning pénztárcát</string>
</resources>

View File

@ -12,7 +12,6 @@
<string name="could_not_decrypt_the_message">Não foi possível descriptografar a mensagem</string>
<string name="group_picture">Imagem do grupo</string>
<string name="explicit_content">Conteúdo explícito</string>
<string name="spam">Spam</string>
<string name="impersonation">Representação</string>
<string name="illegal_behavior">Comportamento ilegal</string>
<string name="unknown">Desconhecido</string>
@ -36,7 +35,6 @@
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Faça login com uma chave privada para poder enviar Zaps</string>
<string name="login_with_a_private_key_to_be_able_to_follow">Faça login com uma chave privada para poder seguir</string>
<string name="login_with_a_private_key_to_be_able_to_unfollow">Faça login com uma chave privada para poder remover seguidores</string>
<string name="zaps">Zaps</string>
<string name="view_count">Contagem de visualizações</string>
<string name="boost">Impulsionar</string>
<string name="boosted">impulsionado</string>
@ -78,7 +76,6 @@
<string name="failed_to_upload_the_image">Falha ao enviar imagem</string>
<string name="relay_address">Endereço do Relay</string>
<string name="posts">Postagens</string>
<string name="bytes">Bytes</string>
<string name="errors">Erros</string>
<string name="home_feed">Feed principal</string>
<string name="private_message_feed">Feed de mensagens privadas</string>
@ -116,7 +113,6 @@
<string name="follows">"Seguindo"</string>
<string name="reports">"Denúncias"</string>
<string name="more_options">Mais opções</string>
<string name="relays">" Relays"</string>
<string name="website">Site</string>
<string name="lightning_address">Endereço Lightning</string>
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Copiar a chave Nsec (sua senha) para backup</string>
@ -170,9 +166,6 @@
<string name="nip_05">Endereço Nostr</string>
<string name="never">nunca</string>
<string name="now">agora</string>
<string name="h">h</string>
<string name="m">m</string>
<string name="d">d</string>
<string name="nudity">Nudez</string>
<string name="profanity_hateful_speech">Palavrões / Discurso de ódio</string>
<string name="report_hateful_speech">Denunciar discurso de ódio</string>
@ -267,7 +260,6 @@
<string name="poll_zap_value_min">Zap mínimo</string>
<string name="poll_zap_value_max">Zap máximo</string>
<string name="poll_consensus_threshold">Consenso</string>
<string name="poll_consensus_threshold_percent">(0100)%</string>
<string name="poll_closing_time">Fechar depois</string>
<string name="poll_closing_time_days">dias</string>
<string name="poll_is_closed">Enquete está fechada para novos votos</string>
@ -330,7 +322,6 @@
<string name="no">Não</string>
<string name="follow_list_selection">Lista de seguidores</string>
<string name="follow_list_kind3follows">Seguindo</string>
<string name="follow_list_global">Global</string>
<string name="connect_through_your_orbot_setup_markdown"> ## Conecte-se através do Tor com o Orbot
\n\n1. Instale o [Orbot](https://play.google.com/store/apps/details?id=org.torproject.android)
\n2. Iniciar Orbot
@ -347,7 +338,6 @@
<string name="app_notification_dms_channel_description">Notifica você quando chega uma mensagem privada</string>
<string name="app_notification_zaps_channel_name">Zaps Recebidos</string>
<string name="app_notification_zaps_channel_description">Notifica quando alguém te manda um zap</string>
<string name="app_notification_zaps_channel_message">%1$s sats</string>
<string name="app_notification_zaps_channel_message_from">De %1$s</string>
<string name="app_notification_zaps_channel_message_for">por %1$s</string>
<string name="reply_notify">Notificar: </string>
@ -384,7 +374,6 @@
<string name="limitations">Limitações</string>
<string name="countries">Países</string>
<string name="languages">Línguas</string>
<string name="tags">Tags</string>
<string name="posting_policy">Política de postagem</string>
<string name="message_length">Tamanho da mensagem</string>
<string name="subscriptions">Assinaturas</string>
@ -408,10 +397,8 @@
<string name="live_stream_has_ended">Transmissão ao vivo encerrada</string>
<string name="are_you_sure_you_want_to_log_out">Sair exclui todas as suas informações locais. Certifique-se de fazer backup de suas chaves privadas para evitar a perda de sua conta. Você quer continuar?</string>
<string name="followed_tags">Tags Seguidas</string>
<string name="relay_setup">Relays</string>
<string name="discover_live">Ao vivo</string>
<string name="discover_community">Comunidade</string>
<string name="discover_chat">Chats</string>
<string name="community_approved_posts">Postagens Aprovadas</string>
<string name="groups_no_descriptor">Este grupo não tem uma descrição ou regras. Fale com o proprietário para adicionar</string>
<string name="community_no_descriptor">Esta comunidade não tem uma descrição. Fale com o proprietário para adicionar</string>
@ -442,7 +429,7 @@
<string name="select_a_relay_to_continue">Selecione um relay para continuar</string>
<string name="zap_forward_title">Encaminhar Zaps para:</string>
<string name="zap_forward_explainer">Os clientes que suportam encaminharão zaps para o endereço lightning ou perfil de usuário abaixo, em vez do seu</string>
<string name="geohash_title">"Expor localização como "</string>
<string name="geohash_title">Expor localização como </string>
<string name="geohash_explainer">Adiciona um Geohash da sua localização à postagem. O público saberá que você está a 5 km (3 milhas) do local atual</string>
<string name="add_sensitive_content_explainer">Adiciona aviso de conteúdo sensível antes de mostrar seu conteúdo. Isso é ideal para qualquer conteúdo NSFW ou conteúdo que algumas pessoas possam considerar ofensivo ou perturbador</string>
<string name="new_feature_nip24_might_not_be_available_title">Novo recurso</string>
@ -453,10 +440,11 @@
<string name="messages_new_message_to">Para</string>
<string name="messages_new_message_subject">Assunto</string>
<string name="messages_new_message_subject_caption">Tema da conversa</string>
<string name="messages_new_message_to_caption">\@Usuário1, @Usuário2, @Usuário3</string>
<string name="messages_new_message_to_caption">"\@Usuário1, @Usuário2, @Usuário3"</string>
<string name="messages_group_descriptor">Membros deste grupo</string>
<string name="messages_new_subject_message">Explicação aos membros</string>
<string name="messages_new_subject_message_placeholder">Mudando o nome dos novos objetivos.</string>
<string name="paste_from_clipboard">Colar da área de transferência</string>
<string name="language_description">Para a interface do aplicativo</string>
<string name="theme_description">Tema Escuro, Claro ou Sistema</string>
<string name="automatically_load_images_gifs_description">Carregar automaticamente imagens e GIFs</string>
@ -467,6 +455,7 @@
<string name="copy_the_note_id_to_the_clipboard">Copiar ID da nota para a área de transferência</string>
<string name="created_at">Criado em</string>
<string name="rules">Regras</string>
<string name="login_with_external_signer">Login com Amber</string>
<string name="status_update">Atualize seu status</string>
<string name="lightning_wallets_not_found">Erro ao analisar mensagem de erro</string>
<string name="poll_zap_value_min_max_explainer">Os votos são ponderados pelo valor do zap. Você pode definir um valor mínimo para evitar spammers e um valor máximo para evitar que grandes zappers assumam o controle da enquete. Use o mesmo valor em ambos os campos para garantir que cada voto tenha o mesmo valor. Deixe em branco para aceitar qualquer valor.</string>
@ -476,22 +465,17 @@
<string name="relay_information_document_error_reach_server">Falha ao acessar %1$s: %2$s</string>
<string name="relay_information_document_error_parse_result">Falha ao analisar o resultado de %1$s: %2$s</string>
<string name="relay_information_document_error_http_status">%1$s falhou com o código %2$s</string>
<string name="active_for">"Ativo para: "</string>
<string name="active_for">Ativo para: </string>
<string name="active_for_home">Início</string>
<string name="active_for_msg">Mensagens diretas</string>
<string name="active_for_chats">Bate-papos</string>
<string name="active_for_global">Global</string>
<string name="active_for_search">Pesquisa</string>
<string name="paste_from_clipboard">Colar da área de transferência</string>
<string name="login_with_external_signer">Login com Amber</string>
<string name="error_dialog_button_ok">OK</string>
<string name="zap_split_title">Dividir e encaminhar Zaps</string>
<string name="zap_split_explainer">Os clientes que suportam dividirão e encaminharão zaps para os usuários adicionados aqui em vez dos seus</string>
<string name="zap_split_serarch_and_add_user">Pesquisar e adicionar usuário</string>
<string name="zap_split_serarch_and_add_user_placeholder">Nome de usuário ou nome de exibição</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">O usuário %1$s não possui um endereço lightning configurado para receber sats</string>
<string name="zap_split_weight">Percentual</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">Dividindo zaps com</string>
<string name="forwarding_zaps_to">Encaminhando zaps para</string>
<string name="lightning_wallets_not_found2">Carteira Lightning não encontrada</string>

View File

@ -456,6 +456,7 @@
<string name="messages_group_descriptor">Medlemmar i denna grupp</string>
<string name="messages_new_subject_message">Förklaring till medlemmar</string>
<string name="messages_new_subject_message_placeholder">Ändra namnet för de nya målen.</string>
<string name="paste_from_clipboard">Klistra in från urklipp</string>
<string name="language_description">För appens gränssnitt</string>
<string name="theme_description">Mörkt, Ljust eller Systemtema</string>
<string name="automatically_load_images_gifs_description">Ladda automatiskt bilder och GIFs</string>
@ -466,6 +467,7 @@
<string name="copy_the_note_id_to_the_clipboard">Kopiera anteckningens ID till urklipp</string>
<string name="created_at">Skapad den</string>
<string name="rules">Regler</string>
<string name="login_with_external_signer">Logga in med Amber</string>
<string name="status_update">Uppdatera din status</string>
<string name="lightning_wallets_not_found">Fel vid tolkning av felmeddelande</string>
<string name="poll_zap_value_min_max_explainer">Röster viktas av zap-beloppet. Du kan ställa in en minsta mängd för att undvika spammare och en maximal mängd för att undvika att en stor zapper tar över omröstningen. Använd samma belopp i båda fälten för att se till att varje röst värderas till samma belopp. Lämna tomt för att acceptera valfritt belopp.</string>
@ -482,27 +484,19 @@
<string name="active_for_chats">Chattar</string>
<string name="active_for_global">Globalt</string>
<string name="active_for_search">Sök</string>
<string name="login_with_external_signer">Logga in med Amber</string>
<string name="paste_from_clipboard">Klistra in från urklipp</string>
<string name="zap_split_title">Dela och vidarebefordra Zaps</string>
<string name="zap_split_explainer">Stödjande klienter kommer att dela och vidarebefordra zaps till användarna som har lagts till här istället för dig</string>
<string name="zap_split_serarch_and_add_user">Sök och lägg till användare</string>
<string name="zap_split_serarch_and_add_user_placeholder">Användarnamn eller visningsnamn</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">Användaren %1$s har inte ställt in en blixtfakturaadress för att ta emot satoshis</string>
<string name="zap_split_weight">Procent</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">Delar zaps med</string>
<string name="forwarding_zaps_to">Vidarebefordrar zaps till</string>
<string name="lightning_wallets_not_found2">Blixtpengar hittades inte</string>
<string name="paid">Betald</string>
<string name="wallet_number">Plånbok %1$s</string>
<string name="error_opening_external_signer">Fel vid öppning av signeringsapp</string>
<string name="sign_request_rejected">Underteckningsbegäran avvisad</string>
<string name="no_wallet_found_with_error">Inga plånböcker hittades för att betala en blixtfaktura (Fel: %1$s). Installera en blixtpengaplånbok för att använda zaps</string>
<string name="no_wallet_found">Inga plånböcker hittades för att betala en blixtfaktura. Installera en blixtpengaplånbok för att använda zaps</string>
</resources>

View File

@ -457,6 +457,7 @@
<string name="messages_group_descriptor">此群组成员</string>
<string name="messages_new_subject_message">对成员的解释</string>
<string name="messages_new_subject_message_placeholder">为新目标更改名称。</string>
<string name="paste_from_clipboard">粘贴自剪贴板</string>
<string name="language_description">用于应用程序界面</string>
<string name="theme_description">暗色、亮色或系统主题</string>
<string name="automatically_load_images_gifs_description">自动加载图像和 GIF</string>
@ -467,6 +468,7 @@
<string name="copy_the_note_id_to_the_clipboard">复制笔记 ID 到剪贴板</string>
<string name="created_at">创建于</string>
<string name="rules">规则</string>
<string name="login_with_external_signer">使用 Amber 登录</string>
<string name="status_update">更新你的状态</string>
<string name="lightning_wallets_not_found">错误解析错误消息</string>
<string name="poll_zap_value_min_max_explainer">投票按照打闪金额进行加权。 你可以设置最小金额以避免垃圾邮件,也可以设置最大金额以避免大型攻击者攻击民意调查。在两个字段中使用相同的金额以确保每张投票的价值相同。 留空即可接受任何金额。</string>
@ -483,4 +485,20 @@
<string name="active_for_chats">聊天</string>
<string name="active_for_global">全球</string>
<string name="active_for_search">搜索</string>
<string name="zap_split_title">拆分及转发打闪</string>
<string name="zap_split_explainer">支持的客户端将会把打闪拆分及转发到在这里添加的用户,而不是你的</string>
<string name="zap_split_serarch_and_add_user">搜索并添加用户</string>
<string name="zap_split_serarch_and_add_user_placeholder">用户名或显示名</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">用户 %1$s 尚未设置接收聪的闪电地址</string>
<string name="zap_split_weight">百分比</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">与此用户拆分打闪</string>
<string name="forwarding_zaps_to">将打闪转发到</string>
<string name="lightning_wallets_not_found2">找不到闪电钱包</string>
<string name="paid">已支付</string>
<string name="wallet_number">钱包 %1$s</string>
<string name="error_opening_external_signer">打开签名应用时出错</string>
<string name="sign_request_rejected">签名请求被拒绝了</string>
<string name="no_wallet_found_with_error">找不到支付闪电发票的钱包(错误:%1$s。请安装闪电钱包来使用打闪</string>
<string name="no_wallet_found">找不到支付闪电发票的钱包。请安装闪电钱包来使用打闪</string>
</resources>

View File

@ -458,6 +458,7 @@
<string name="messages_group_descriptor">此群組成員</string>
<string name="messages_new_subject_message">對成員的解釋</string>
<string name="messages_new_subject_message_placeholder">為新目標更改名稱。</string>
<string name="paste_from_clipboard">粘貼自剪貼板</string>
<string name="language_description">用於應用程式界面</string>
<string name="theme_description">暗色、亮色或系統主題</string>
<string name="automatically_load_images_gifs_description">自動加載圖像和 GIF</string>
@ -468,6 +469,7 @@
<string name="copy_the_note_id_to_the_clipboard">將筆記 ID 複製到剪貼簿</string>
<string name="created_at">創建於</string>
<string name="rules">規則</string>
<string name="login_with_external_signer">使用 Amber 登錄</string>
<string name="status_update">更新你的狀態</string>
<string name="lightning_wallets_not_found">錯誤解析錯誤消息</string>
<string name="poll_zap_value_min_max_explainer">投票按照打閃金額進行加權。你可以設置最小金額以避免垃圾郵件,也可以設置最大金額以避免大型攻擊者攻擊民意調查。在兩個字段中使用相同的金額則確保每張投票的價值相同。留空即可接受任何金額。</string>
@ -484,4 +486,20 @@
<string name="active_for_chats">聊天</string>
<string name="active_for_global">全球</string>
<string name="active_for_search">搜索</string>
<string name="zap_split_title">拆分及轉發打閃</string>
<string name="zap_split_explainer">支持的客戶端將會把打閃拆分及轉發到在這裡添加的用戶,而不是你的</string>
<string name="zap_split_serarch_and_add_user">搜索並添加用戶</string>
<string name="zap_split_serarch_and_add_user_placeholder">用戶名或顯示名</string>
<string name="user_x_does_not_have_a_lightning_address_setup_to_receive_sats">用户 %1$s 尚未設置接收聰的閃電地址</string>
<string name="zap_split_weight">百分比</string>
<string name="zap_split_weight_placeholder">25</string>
<string name="splitting_zaps_with">與此用戶拆分打閃</string>
<string name="forwarding_zaps_to">將打閃轉發到</string>
<string name="lightning_wallets_not_found2">找不到閃電錢包</string>
<string name="paid">已支付</string>
<string name="wallet_number">錢包:%1$s</string>
<string name="error_opening_external_signer">打開簽署程式時出錯</string>
<string name="sign_request_rejected">簽署請求被拒絕了</string>
<string name="no_wallet_found_with_error">找不到支付閃電發票的錢包(錯誤:%1$s。請安裝一個閃電錢包來使用打閃</string>
<string name="no_wallet_found">找不到支付閃電發票的錢包。請安裝一個閃電錢包來使用打閃</string>
</resources>

View File

@ -27,13 +27,16 @@ class PollNoteEvent(
tags.filter { it.size > 2 && it[0] == POLL_OPTION }
.associate { it[1].toInt() to it[2] }
fun getTagInt(property: String): Int? {
fun minimumAmount() = tags.firstOrNull() { it.size > 1 && it[0] == VALUE_MINIMUM }?.getOrNull(1)?.toLongOrNull()
fun maximumAmount() = tags.firstOrNull() { it.size > 1 && it[0] == VALUE_MAXIMUM }?.getOrNull(1)?.toLongOrNull()
fun getTagLong(property: String): Long? {
val number = tags.firstOrNull() { it.size > 1 && it[0] == property }?.get(1)
return if (number.isNullOrBlank() || number == "null") {
null
} else {
number.toInt()
number.toLong()
}
}
@ -71,11 +74,18 @@ class PollNoteEvent(
pollOptions.forEach { poll_op ->
tags.add(listOf(POLL_OPTION, poll_op.key.toString(), poll_op.value))
}
tags.add(listOf(VALUE_MAXIMUM, valueMaximum.toString()))
tags.add(listOf(VALUE_MINIMUM, valueMinimum.toString()))
tags.add(listOf(CONSENSUS_THRESHOLD, consensusThreshold.toString()))
tags.add(listOf(CLOSED_AT, closedAt.toString()))
valueMaximum?.let {
tags.add(listOf(VALUE_MAXIMUM, valueMaximum.toString()))
}
valueMinimum?.let {
tags.add(listOf(VALUE_MINIMUM, valueMinimum.toString()))
}
consensusThreshold?.let {
tags.add(listOf(CONSENSUS_THRESHOLD, consensusThreshold.toString()))
}
closedAt?.let {
tags.add(listOf(CLOSED_AT, closedAt.toString()))
}
zapReceiver?.forEach {
tags.add(listOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
}