mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 01:07:46 +00:00
Merge branch 'main' of https://github.com/greenart7c3/amethyst
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -194,7 +194,7 @@ private fun RenderSearch(
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
DisposableEffect(lifeCycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
println("Join Start")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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") }
|
||||
|
||||
@@ -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 { }
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<ZapReqResponse>) : View
|
||||
checkNotInMainThread()
|
||||
|
||||
LocalCache.live.newEventBundles.collect { newNotes ->
|
||||
checkNotInMainThread()
|
||||
|
||||
invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -47,7 +47,7 @@ fun rememberForeverLazyListState(
|
||||
savedOffset.roundToInt()
|
||||
)
|
||||
}
|
||||
DisposableEffect(Unit) {
|
||||
DisposableEffect(scrollState) {
|
||||
onDispose {
|
||||
val lastIndex = scrollState.firstVisibleItemIndex
|
||||
val lastOffset = scrollState.firstVisibleItemScrollOffset
|
||||
|
||||
@@ -118,6 +118,8 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel(), In
|
||||
checkNotInMainThread()
|
||||
|
||||
LocalCache.live.newEventBundles.collect { newNotes ->
|
||||
checkNotInMainThread()
|
||||
|
||||
invalidateData()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user