diff --git a/app/build.gradle b/app/build.gradle index d312576b3..cbc3868cb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index df3e1f2cb..309a16ba3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt index 7ff65c5e1..fb78a0ffb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/JoinUserOrChannelView.kt @@ -194,7 +194,7 @@ private fun RenderSearch( } } - DisposableEffect(Unit) { + DisposableEffect(lifeCycleOwner) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { println("Join Start") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 84c02a0d4..454b89824 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt index 2e154fa0d..003338d2c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/ZoomableContentView.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index 167bbaf85..56feca6ae 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -284,7 +284,7 @@ fun AppNavigation( actionableNextPage = null } - DisposableEffect(navController) { + DisposableEffect(activity) { val consumer = Consumer { intent -> val uri = intent?.data?.toString() val newPage = uriToRoute(uri) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index e44dfa9ae..ef2a1dd2f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 09def4997..ae8e2ae40 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -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 ) ) - } + ) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt index e5cd1c436..b29068ac0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/PollNoteViewModel.kt @@ -33,9 +33,12 @@ class PollNoteViewModel : ViewModel() { private var pollEvent: PollNoteEvent? = null private var pollOptions: Map? = 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 { @@ -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() } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt index e82dfc6af..526ab3417 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/UpdateZapAmountDialog.kt @@ -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") } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt index 6e76f2f3f..2ad62d8a4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/qrcode/QrCodeScanner.kt @@ -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 { } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt index e3a5af241..ee769b98c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt @@ -87,6 +87,8 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter) : View checkNotInMainThread() LocalCache.live.newEventBundles.collect { newNotes -> + checkNotInMainThread() + invalidateData() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt index f41eb6f7f..e3cdcf7fa 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt @@ -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) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt index 849551e7e..9f79daf5a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt @@ -47,7 +47,7 @@ fun rememberForeverLazyListState( savedOffset.roundToInt() ) } - DisposableEffect(Unit) { + DisposableEffect(scrollState) { onDispose { val lastIndex = scrollState.firstVisibleItemIndex val lastOffset = scrollState.firstVisibleItemScrollOffset diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index b076e54b5..608d86ce7 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -118,6 +118,8 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel(), In checkNotInMainThread() LocalCache.live.newEventBundles.collect { newNotes -> + checkNotInMainThread() + invalidateData() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 84d875069..1c1cc0333 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -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, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt index 39b60a649..0051e737c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index 7f02e78eb..0ac3bf90d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt index 011c6dd6b..bd80d9fa0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt index 65df80ee9..a9febcdca 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt index 8de031058..36bec3b1d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt @@ -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() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt index f0c8d3411..edd2cfe21 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt @@ -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() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index 7f78f7bbe..69938b796 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 3b146279b..bc0de2b7e 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 4bccf3ee7..1c47e186d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -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, 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 diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index cba29abb1..d6a644ad1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -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() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index cd22683a5..b3df05f20 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 49acad049..1b5636d72 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt index 8df68e327..57eaa662d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt @@ -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") diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 0234adcd6..d2edde0df 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -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") diff --git a/app/src/main/res/mipmap-hdpi/mutiny.png b/app/src/main/res/mipmap-hdpi/mutiny.png new file mode 100644 index 000000000..7f63351c7 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/mutiny.png differ diff --git a/app/src/main/res/mipmap-mdpi/mutiny.png b/app/src/main/res/mipmap-mdpi/mutiny.png new file mode 100644 index 000000000..8e1771a73 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/mutiny.png differ diff --git a/app/src/main/res/mipmap-xhdpi/mutiny.png b/app/src/main/res/mipmap-xhdpi/mutiny.png new file mode 100644 index 000000000..795afb6bc Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/mutiny.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/mutiny.png b/app/src/main/res/mipmap-xxhdpi/mutiny.png new file mode 100644 index 000000000..a981f6046 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/mutiny.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/mutiny.png b/app/src/main/res/mipmap-xxxhdpi/mutiny.png new file mode 100644 index 000000000..7352042ea Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/mutiny.png differ diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 7850db02f..57616d2f2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -448,6 +448,7 @@ Členové této skupiny Vysvětlení členům Změna názvu pro nové cíle. + Vložit ze schránky Pro rozhraní aplikace Tmavé, světlé nebo systémové téma Automaticky načítat obrázky a GIFy @@ -458,6 +459,7 @@ Kopírovat ID poznámky do schránky Vytvořeno Pravidla + Přihlásit se pomocí Amber Aktualizovat svůj stav Chyba při zpracování chybové zprávy 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. @@ -473,27 +475,19 @@ Chaty Celosvětové Hledání - - Přihlásit se pomocí Amber - Vložit ze schránky - Rozdělit a přeposlat Zaps Podporované klienty budou Zapy rozdělovat a přeposílat uživatelům přidaným zde místo vám Hledat a přidat uživatele Uživatelské jméno nebo zobrazované jméno Uživatel %1$s nemá nastavenou bleskovou adresu pro přijímání satoshi Procenta - 25 Rozdělování Zapů s Přeposílání Zapů na - Bleskové peněženky nenalezený Zaplaceno Peněženka %1$s Chyba při otevírání aplikace pro podpis Žádost o podpis byla zamítnuta - Nebyly nalezeny žádné peněženky pro platbu bleskové faktury (Chyba: %1$s). Prosím, nainstalujte si bleskovou peněženku pro použití Zapů Nebyly nalezeny žádné peněženky pro platbu bleskové faktury. Prosím, nainstalujte si bleskovou peněženku pro použití Zapů - diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index aaeed37f6..620bac1ed 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -446,6 +446,7 @@ anz der Bedingungen ist erforderlich Mitglieder dieser Gruppe Erklärung an Mitglieder Ändern des Namens für die neuen Ziele. + Aus Zwischenablage einfügen Für die App-Benutzeroberfläche Dunkles, helles oder Systemdesign Bilder und GIFs automatisch laden @@ -456,6 +457,7 @@ anz der Bedingungen ist erforderlich Notiz-ID in die Zwischenablage kopieren Erstellt am Regeln + Mit Amber anmelden Status aktualisieren Fehler beim Verarbeiten der Fehlermeldung 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. @@ -469,27 +471,18 @@ anz der Bedingungen ist erforderlich Startseite Nachrichten Suche - - Mit Amber anmelden - Aus Zwischenablage einfügen - Zaps aufteilen und weiterleiten Unterstützende Clients werden Zaps an die hier hinzugefügten Benutzer aufteilen und weiterleiten, anstatt an Sie Benutzer suchen und hinzufügen Benutzername oder Anzeigename Benutzer %1$s hat keine Lightning-Adresse eingerichtet, um Sats zu empfangen Prozent - 25 Zaps aufteilen mit Zaps weiterleiten an - Lightning-Wallets nicht gefunden Bezahlt - Wallet %1$s Fehler beim Öffnen der Signatur-App Signaturanfrage abgelehnt - Keine Wallets gefunden, um eine Lightning-Rechnung zu bezahlen (Fehler: %1$s). Installieren Sie eine Lightning-Wallet, um Zaps zu verwenden Keine Wallets gefunden, um eine Lightning-Rechnung zu bezahlen. Installieren Sie eine Lightning-Wallet, um Zaps zu verwenden - diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 18551bc0d..a09371f77 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -458,6 +458,7 @@ A csoport tagjai Magyarázat a csoport tagjainak Az új célok érdekében, a név megváltoztatása. + Beszúrás vágólapról Az applikáció felülete Sötét, Világos vagy Rendszer által használt téma Képek és GIF-ek automatikus betöltése @@ -468,6 +469,7 @@ A bejegyzésazonosító vágólapra másolása Létrehozva Szabályok + Bejelentkezés Amber-el Állapotod megjelenítése Hiba a hibaüzenet elemzésekor 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. @@ -484,4 +486,20 @@ Chat Globális Keresés + Zap-ek megosztása és továbbítása + A támogató kliensek a zapokat, az itt hozzáadott felhasználóknak helyetted felosztják és továbbítják + Felhasználó keresése és hozzáadása + Felhasználónév vagy megjelenítendő név + A %1$s felhasználó a sat fogadáshoz nem rendelkezik LN cím beállítással + Százalék + 25 + Zap-ek megosztása + Zap-ek továbbítása + Lightning tárca nem található + Fizetve + Tárca: %1$s + Hiba az aláíró alkalmazás megnyitásakor + Aláírási kérés elutasítva + 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 + 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 diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 5c4bbb6d1..12145b297 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -12,7 +12,6 @@ Não foi possível descriptografar a mensagem Imagem do grupo Conteúdo explícito - Spam Representação Comportamento ilegal Desconhecido @@ -36,7 +35,6 @@ Faça login com uma chave privada para poder enviar Zaps Faça login com uma chave privada para poder seguir Faça login com uma chave privada para poder remover seguidores - Zaps Contagem de visualizações Impulsionar impulsionado @@ -78,7 +76,6 @@ Falha ao enviar imagem Endereço do Relay Postagens - Bytes Erros Feed principal Feed de mensagens privadas @@ -116,7 +113,6 @@ "Seguindo" "Denúncias" Mais opções - " Relays" Site Endereço Lightning Copiar a chave Nsec (sua senha) para backup @@ -170,9 +166,6 @@ Endereço Nostr nunca agora - h - m - d Nudez Palavrões / Discurso de ódio Denunciar discurso de ódio @@ -267,7 +260,6 @@ Zap mínimo Zap máximo Consenso - (0–100)% Fechar depois dias Enquete está fechada para novos votos @@ -330,7 +322,6 @@ Não Lista de seguidores Seguindo - Global ## 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 @@ Notifica você quando chega uma mensagem privada Zaps Recebidos Notifica quando alguém te manda um zap - %1$s sats De %1$s por %1$s Notificar: @@ -384,7 +374,6 @@ Limitações Países Línguas - Tags Política de postagem Tamanho da mensagem Assinaturas @@ -408,10 +397,8 @@ Transmissão ao vivo encerrada 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? Tags Seguidas - Relays Ao vivo Comunidade - Chats Postagens Aprovadas Este grupo não tem uma descrição ou regras. Fale com o proprietário para adicionar Esta comunidade não tem uma descrição. Fale com o proprietário para adicionar @@ -442,7 +429,7 @@ Selecione um relay para continuar Encaminhar Zaps para: Os clientes que suportam encaminharão zaps para o endereço lightning ou perfil de usuário abaixo, em vez do seu - "Expor localização como " + Expor localização como Adiciona um Geohash da sua localização à postagem. O público saberá que você está a 5 km (3 milhas) do local atual 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 Novo recurso @@ -453,10 +440,11 @@ Para Assunto Tema da conversa - \@Usuário1, @Usuário2, @Usuário3 + "\@Usuário1, @Usuário2, @Usuário3" Membros deste grupo Explicação aos membros Mudando o nome dos novos objetivos. + Colar da área de transferência Para a interface do aplicativo Tema Escuro, Claro ou Sistema Carregar automaticamente imagens e GIFs @@ -467,6 +455,7 @@ Copiar ID da nota para a área de transferência Criado em Regras + Login com Amber Atualize seu status Erro ao analisar mensagem de erro 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. @@ -476,22 +465,17 @@ Falha ao acessar %1$s: %2$s Falha ao analisar o resultado de %1$s: %2$s %1$s falhou com o código %2$s - "Ativo para: " + Ativo para: Início Mensagens diretas Bate-papos - Global Pesquisa - Colar da área de transferência - Login com Amber - OK Dividir e encaminhar Zaps Os clientes que suportam dividirão e encaminharão zaps para os usuários adicionados aqui em vez dos seus Pesquisar e adicionar usuário Nome de usuário ou nome de exibição O usuário %1$s não possui um endereço lightning configurado para receber sats Percentual - 25 Dividindo zaps com Encaminhando zaps para Carteira Lightning não encontrada diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 0c44173c2..9fedf9dc2 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -456,6 +456,7 @@ Medlemmar i denna grupp Förklaring till medlemmar Ändra namnet för de nya målen. + Klistra in från urklipp För appens gränssnitt Mörkt, Ljust eller Systemtema Ladda automatiskt bilder och GIFs @@ -466,6 +467,7 @@ Kopiera anteckningens ID till urklipp Skapad den Regler + Logga in med Amber Uppdatera din status Fel vid tolkning av felmeddelande 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. @@ -482,27 +484,19 @@ Chattar Globalt Sök - - Logga in med Amber - Klistra in från urklipp - Dela och vidarebefordra Zaps Stödjande klienter kommer att dela och vidarebefordra zaps till användarna som har lagts till här istället för dig Sök och lägg till användare Användarnamn eller visningsnamn Användaren %1$s har inte ställt in en blixtfakturaadress för att ta emot satoshis Procent - 25 Delar zaps med Vidarebefordrar zaps till - Blixtpengar hittades inte Betald Plånbok %1$s Fel vid öppning av signeringsapp Underteckningsbegäran avvisad - Inga plånböcker hittades för att betala en blixtfaktura (Fel: %1$s). Installera en blixtpengaplånbok för att använda zaps Inga plånböcker hittades för att betala en blixtfaktura. Installera en blixtpengaplånbok för att använda zaps - diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 86389fc81..16b946606 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -457,6 +457,7 @@ 此群组成员 对成员的解释 为新目标更改名称。 + 粘贴自剪贴板 用于应用程序界面 暗色、亮色或系统主题 自动加载图像和 GIF @@ -467,6 +468,7 @@ 复制笔记 ID 到剪贴板 创建于 规则 + 使用 Amber 登录 更新你的状态 错误解析错误消息 投票按照打闪金额进行加权。 你可以设置最小金额以避免垃圾邮件,也可以设置最大金额以避免大型攻击者攻击民意调查。在两个字段中使用相同的金额以确保每张投票的价值相同。 留空即可接受任何金额。 @@ -483,4 +485,20 @@ 聊天 全球 搜索 + 拆分及转发打闪 + 支持的客户端将会把打闪拆分及转发到在这里添加的用户,而不是你的 + 搜索并添加用户 + 用户名或显示名 + 用户 %1$s 尚未设置接收聪的闪电地址 + 百分比 + 25 + 与此用户拆分打闪 + 将打闪转发到 + 找不到闪电钱包 + 已支付 + 钱包 %1$s + 打开签名应用时出错 + 签名请求被拒绝了 + 找不到支付闪电发票的钱包(错误:%1$s)。请安装闪电钱包来使用打闪 + 找不到支付闪电发票的钱包。请安装闪电钱包来使用打闪 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index ef1ad4e6f..cf16170a2 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -458,6 +458,7 @@ 此群組成員 對成員的解釋 為新目標更改名稱。 + 粘貼自剪貼板 用於應用程式界面 暗色、亮色或系統主題 自動加載圖像和 GIF @@ -468,6 +469,7 @@ 將筆記 ID 複製到剪貼簿 創建於 規則 + 使用 Amber 登錄 更新你的狀態 錯誤解析錯誤消息 投票按照打閃金額進行加權。你可以設置最小金額以避免垃圾郵件,也可以設置最大金額以避免大型攻擊者攻擊民意調查。在兩個字段中使用相同的金額則確保每張投票的價值相同。留空即可接受任何金額。 @@ -484,4 +486,20 @@ 聊天 全球 搜索 + 拆分及轉發打閃 + 支持的客戶端將會把打閃拆分及轉發到在這裡添加的用戶,而不是你的 + 搜索並添加用戶 + 用戶名或顯示名 + 用户 %1$s 尚未設置接收聰的閃電地址 + 百分比 + 25 + 與此用戶拆分打閃 + 將打閃轉發到 + 找不到閃電錢包 + 已支付 + 錢包:%1$s + 打開簽署程式時出錯 + 簽署請求被拒絕了 + 找不到支付閃電發票的錢包(錯誤:%1$s)。請安裝一個閃電錢包來使用打閃 + 找不到支付閃電發票的錢包。請安裝一個閃電錢包來使用打閃 diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/PollNoteEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/PollNoteEvent.kt index 5414afe3b..739964019 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/PollNoteEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/PollNoteEvent.kt @@ -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())) }