Compare commits

..
29 Commits
Author SHA1 Message Date
Vitor Pamplona 9ec6e6d955 v0.80.4 2023-10-30 17:31:50 -04:00
Vitor Pamplona 6b7ec2caa9 Starting videos from Main, but in a thread. 2023-10-30 17:31:33 -04:00
Vitor Pamplona 17130c81c8 v0.80.3 2023-10-30 17:05:02 -04:00
Vitor Pamplona 0571312dca Fixing new line of Loading Animation and Download buttons for images. 2023-10-30 16:39:39 -04:00
Vitor Pamplona 717c3bc45e Generalizing InnerReaction Row 2023-10-30 15:44:15 -04:00
Vitor Pamplona c304f7c69d Typo 2023-10-30 15:43:25 -04:00
Vitor Pamplona 6d2a1ec1b9 Remember's modifiers in animations 2023-10-30 14:57:57 -04:00
Vitor Pamplona 59f7f5257b Adds check in main thread to mark as seen on relays 2023-10-30 14:33:22 -04:00
Vitor Pamplona ba1f2dc17d Minimizes Jittering when loading videos. 2023-10-30 14:05:36 -04:00
Vitor Pamplona 820def620c Improving rendering of reaction types 2023-10-30 12:52:19 -04:00
Vitor Pamplona 9fef3e9e3e Only updates notification dots once at every 3 seconds. 2023-10-30 12:27:59 -04:00
Vitor Pamplona e8998a26d7 Follow yourself for new users. 2023-10-30 12:10:31 -04:00
Vitor Pamplona 221ab1ca1a Slight performance improvements 2023-10-30 12:04:10 -04:00
Vitor Pamplona 2f1e67779b Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2023-10-30 09:35:23 -04:00
Vitor Pamplona 96663b822c Fixing colors of edit buttons 2023-10-30 09:34:43 -04:00
Vitor PamplonaandGitHub c7895d1cdd Merge pull request #674 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2023-10-30 08:58:22 -04:00
Crowdin Bot 85762c581a New Crowdin translations by GitHub Action 2023-10-30 12:44:53 +00:00
Vitor PamplonaandGitHub e189a4d68d Merge pull request #673 from greenart7c3/main
fix floatingactionbutton not showing again after changing screens
2023-10-30 08:43:19 -04:00
greenart7c3 3306dd88d2 fix floatingactionbutton not showing again after changing screens 2023-10-30 08:11:38 -03:00
Vitor PamplonaandGitHub 18952bc8a7 Merge pull request #671 from matata2140/patch-1
Update strings.xml // values-nl
2023-10-28 22:31:43 -04:00
Vitor Pamplona f6cfba6e1f Login and Logout already in IO threads 2023-10-28 21:26:36 -04:00
matata@nostrplebs.comandGitHub fb37ccd14f Update strings.xml // values-nl
fixed a couple translations, typo's and shortened a sentence for a nicer ux
2023-10-29 01:33:07 +02:00
Vitor Pamplona 3858c57816 More Stability improvements to Compose 2023-10-28 18:23:12 -04:00
Vitor Pamplona 572c11e847 Adjusting Stability of composables 2023-10-28 18:19:21 -04:00
Vitor Pamplona bd70bd708b Stabilizing Note LiveData 2023-10-28 17:51:33 -04:00
Vitor Pamplona 9a2ecfc85d Adds images and posts without and extra line. 2023-10-28 16:58:39 -04:00
Vitor Pamplona f20fd1aebe Fixes: https://github.com/vitorpamplona/amethyst/issues/629 2023-10-28 16:58:17 -04:00
Vitor Pamplona 2f2914077b Speeding up the boost method 2023-10-28 16:38:12 -04:00
Vitor Pamplona 712d498788 Fixing weird alignment of the Hightligh post title. 2023-10-28 15:38:59 -04:00
69 changed files with 908 additions and 806 deletions
+2 -2
View File
@@ -13,8 +13,8 @@ android {
applicationId "com.vitorpamplona.amethyst"
minSdk 26
targetSdk 34
versionCode 325
versionName "0.80.2"
versionCode 327
versionName "0.80.4"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
@@ -32,14 +32,8 @@ class Amethyst : Application() {
)
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectActivityLeaks()
.detectFileUriExposure()
.detectContentUriWithoutPermission()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.detectLeakedSqlLiteObjects()
.detectAll()
.penaltyLog()
// .penaltyDeath()
.build()
)
}
@@ -134,8 +134,6 @@ object LocalPreferences {
)
}
println("AAA migrated: $migrated")
_savedAccounts = migrated
}
}
@@ -809,6 +809,7 @@ class NoteLiveSet(u: Note) {
}
}
@Stable
class NoteBundledRefresherLiveData(val note: Note) : LiveData<NoteState>(NoteState(note)) {
// Refreshes observers in batches.
private val bundler = BundledUpdate(500, Dispatchers.IO)
@@ -837,6 +838,7 @@ class NoteBundledRefresherLiveData(val note: Note) : LiveData<NoteState>(NoteSta
}
}
@Stable
class NoteLoadingLiveData<Y>(val note: Note, initialValue: Y?) : MediatorLiveData<Y>(initialValue) {
override fun onActive() {
super.onActive()
@@ -210,6 +210,8 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
}
override fun markAsSeenOnRelay(eventId: String, relay: Relay) {
checkNotInMainThread()
super.markAsSeenOnRelay(eventId, relay)
val note = LocalCache.getNoteIfExists(eventId) ?: return
@@ -226,19 +226,19 @@ class Relay(
}
"OK" -> listeners.forEach {
val eventId = msgArray[1].asText()
val sucess = msgArray[2].asBoolean()
val success = msgArray[2].asBoolean()
val message = msgArray[3].asText()
if (authResponse.containsKey(eventId)) {
val wasAlreadyAuthenticated = authResponse.get(eventId)
authResponse.put(eventId, sucess)
if (wasAlreadyAuthenticated != true && sucess) {
authResponse.put(eventId, success)
if (wasAlreadyAuthenticated != true && success) {
renewFilters()
}
}
Log.w("Relay", "Relay on OK $url, $eventId, $sucess, $message")
it.onSendResponse(this@Relay, eventId, sucess, message)
Log.w("Relay", "Relay on OK $url, $eventId, $success, $message")
it.onSendResponse(this@Relay, eventId, success, message)
}
"AUTH" -> listeners.forEach {
// Log.w("Relay", "Relay$url, ${msg[1].asString}")
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.actions
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@@ -64,6 +63,8 @@ import com.vitorpamplona.amethyst.ui.note.SearchIcon
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchBarViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@@ -313,10 +314,7 @@ private fun RenderSearchResults(
) {
LazyColumn(
modifier = Modifier.fillMaxHeight(),
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(
@@ -411,7 +409,7 @@ fun UserComposeForChat(
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -81,7 +81,9 @@ fun NewMediaView(uri: Uri, onClose: () -> Unit, postViewModel: NewMediaModel, ac
var showRelaysDialog by remember {
mutableStateOf(false)
}
var relayList = account.activeWriteRelays()
var relayList = remember {
accountViewModel.account.activeWriteRelays().toImmutableList()
}
Dialog(
onDismissRequest = { onClose() },
@@ -172,8 +172,6 @@ fun NewPostView(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
val account = remember(accountViewModel) { accountViewModel.account }
val postViewModel: NewPostViewModel = viewModel()
postViewModel.wantsDirectMessage = enableMessageInterface
@@ -184,7 +182,9 @@ fun NewPostView(
var showRelaysDialog by remember {
mutableStateOf(false)
}
var relayList = account.activeWriteRelays()
var relayList = remember {
accountViewModel.account.activeWriteRelays().toImmutableList()
}
LaunchedEffect(Unit) {
postViewModel.load(accountViewModel, baseReplyTo, quote)
@@ -387,10 +387,10 @@ fun NewPostView(
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)) {
ImageVideoDescription(
url,
account.defaultFileServer,
accountViewModel.account.defaultFileServer,
onAdd = { alt, server, sensitiveContent ->
postViewModel.upload(url, alt, sensitiveContent, server, context, relayList)
account.changeDefaultFileServer(server)
accountViewModel.account.changeDefaultFileServer(server)
},
onCancel = {
postViewModel.contentToAddUrl = null
@@ -414,7 +414,7 @@ fun NewPostView(
InvoiceRequest(
lud16,
user.pubkeyHex,
account,
accountViewModel.account,
stringResource(id = R.string.lightning_invoice),
stringResource(id = R.string.lightning_create_and_add_invoice),
onSuccess = {
@@ -151,7 +151,7 @@ open class NewPostViewModel() : ViewModel() {
}
quote?.let {
message = TextFieldValue(message.text + "\n\nnostr:${it.toNEvent()}")
message = TextFieldValue(message.text + "\nnostr:${it.toNEvent()}")
urlPreview = findUrlInMessage()
}
@@ -283,6 +283,8 @@ open class NewPostViewModel() : ViewModel() {
(originalNote?.event as? TextNoteEvent)?.root() // if it has a marker as root
?: originalNote?.replyTo?.firstOrNull { it.event != null && it.replyTo?.isEmpty() == true }?.idHex // if it has loaded events with zero replies in the reply list
?: originalNote?.replyTo?.firstOrNull()?.idHex // old rules, first item is root.
?: originalNote?.idHex
val replyId = originalNote?.idHex
account?.sendPost(
@@ -335,7 +337,7 @@ open class NewPostViewModel() : ViewModel() {
createNIP94Record(imageUrl, mimeType, alt, sensitiveContent)
} else {
isUploadingImage = false
message = TextFieldValue(message.text + "\n\n" + imageUrl)
message = TextFieldValue(message.text + "\n" + imageUrl)
urlPreview = findUrlInMessage()
}
},
@@ -559,9 +561,9 @@ open class NewPostViewModel() : ViewModel() {
isUploadingImage = false
if (note == null) {
message = TextFieldValue(message.text + "\n\n" + imageUrl)
message = TextFieldValue(message.text + "\n" + imageUrl)
} else {
message = TextFieldValue(message.text + "\n\nnostr:" + note.toNEvent())
message = TextFieldValue(message.text + "\nnostr:" + note.toNEvent())
}
urlPreview = findUrlInMessage()
@@ -591,7 +593,7 @@ open class NewPostViewModel() : ViewModel() {
isUploadingImage = false
note?.let {
message = TextFieldValue(message.text + "\n\nnostr:" + it.toNEvent())
message = TextFieldValue(message.text + "\nnostr:" + it.toNEvent())
}
urlPreview = findUrlInMessage()
@@ -6,7 +6,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -67,6 +66,8 @@ import com.vitorpamplona.amethyst.service.relays.FeedType
import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.HalfHorzPadding
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
@@ -102,8 +103,7 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re
TopAppBar(
title = {
Row(
modifier = Modifier
.fillMaxWidth(),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
@@ -154,10 +154,7 @@ fun NewRelayListView(onClose: () -> Unit, accountViewModel: AccountViewModel, re
) {
Row(modifier = Modifier.weight(1f), verticalAlignment = Alignment.CenterVertically) {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
)
contentPadding = FeedPadding
) {
itemsIndexed(feedState, key = { _, item -> item.url }) { index, item ->
ServerConfig(
@@ -252,7 +249,7 @@ fun ServerConfigHeader() {
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -409,7 +406,7 @@ fun ServerConfigClickableLine(
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -4,7 +4,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@@ -32,6 +31,9 @@ import com.vitorpamplona.amethyst.model.RelayInformation
import com.vitorpamplona.amethyst.service.Nip11Retriever
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
data class RelayList(
val relay: Relay,
@@ -46,9 +48,9 @@ data class RelayInfoDialog(
@Composable
fun RelaySelectionDialog(
preSelectedList: List<Relay>,
preSelectedList: ImmutableList<Relay>,
onClose: () -> Unit,
onPost: (list: List<Relay>) -> Unit,
onPost: (list: ImmutableList<Relay>) -> Unit,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
@@ -124,7 +126,7 @@ fun RelaySelectionDialog(
SaveButton(
onPost = {
val selectedRelays = relays.filter { it.isSelected }
onPost(selectedRelays.map { it.relay })
onPost(selectedRelays.map { it.relay }.toImmutableList())
onClose()
},
isActive = hasSelectedRelay
@@ -143,10 +145,7 @@ fun RelaySelectionDialog(
)
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
)
contentPadding = FeedPadding
) {
itemsIndexed(
relays,
@@ -1,6 +1,5 @@
package com.vitorpamplona.amethyst.buttons
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
@@ -21,6 +20,8 @@ import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.actions.NewChannelView
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
@Composable
fun NewChannelButton(accountViewModel: AccountViewModel) {
@@ -34,10 +35,10 @@ fun NewChannelButton(accountViewModel: AccountViewModel) {
OutlinedButton(
onClick = { wantsToPost = true },
modifier = Modifier.size(55.dp),
modifier = Size55Modifier,
shape = CircleShape,
colors = ButtonDefaults.outlinedButtonColors(containerColor = MaterialTheme.colorScheme.primary),
contentPadding = PaddingValues(0.dp)
contentPadding = ZeroPadding
) {
Icon(
imageVector = Icons.Outlined.Add,
@@ -685,15 +685,17 @@ fun InLineIconRenderer(
}
}.associate { it.first to it.second }
val annotatedText = buildAnnotatedString {
wordsInOrder.forEachIndexed { idx, value ->
withStyle(
style
) {
if (value is TextType) {
append(value.text)
} else if (value is ImageUrlType) {
appendInlineContent("inlineContent$idx", "[icon]")
val annotatedText = remember {
buildAnnotatedString {
wordsInOrder.forEachIndexed { idx, value ->
withStyle(
style
) {
if (value is TextType) {
append(value.text)
} else if (value is ImageUrlType) {
appendInlineContent("inlineContent$idx", "[icon]")
}
}
}
}
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -27,6 +26,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.getGradient
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
import com.vitorpamplona.quartz.events.ImmutableListOfLists
@@ -114,7 +114,7 @@ fun ShowMoreButton(onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondaryButtonBackground
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.show_more), color = Color.White)
}
@@ -556,32 +556,32 @@ private fun RenderHashtag(
nav: (String) -> Unit
) {
val primary = MaterialTheme.colorScheme.primary
val background = MaterialTheme.colorScheme.onBackground
val hashtagIcon = remember(segment.hashtag) {
checkForHashtagWithIcon(segment.hashtag, primary)
}
val regularText =
SpanStyle(color = MaterialTheme.colorScheme.onBackground)
val regularText = remember { SpanStyle(color = background) }
val clickableTextStyle = remember { SpanStyle(color = primary) }
val clickableTextStyle =
SpanStyle(color = primary)
val annotatedTermsString = buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToHashtag", "")
append("#${segment.hashtag}")
}
if (hashtagIcon != null) {
val annotatedTermsString = remember {
buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToHashtag", "")
appendInlineContent("inlineContent", "[icon]")
append("#${segment.hashtag}")
}
}
segment.extras?.ifBlank { "" }?.let {
withStyle(regularText) {
append(it)
if (hashtagIcon != null) {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToHashtag", "")
appendInlineContent("inlineContent", "[icon]")
}
}
segment.extras?.ifBlank { "" }?.let {
withStyle(regularText) {
append(it)
}
}
}
}
@@ -592,8 +592,10 @@ private fun RenderHashtag(
emptyMap()
}
val pressIndicator = Modifier.clickable {
nav("Hashtag/${segment.hashtag}")
val pressIndicator = remember {
Modifier.clickable {
nav("Hashtag/${segment.hashtag}")
}
}
Text(
@@ -4,7 +4,6 @@ import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -37,6 +36,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.quartz.events.EventInterface
@Composable
@@ -143,7 +143,7 @@ fun ContentWarningNote(onDismiss: () -> Unit) {
.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(
text = stringResource(R.string.show_anyway),
@@ -32,6 +32,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import kotlinx.collections.immutable.ImmutableList
@@ -143,7 +144,7 @@ fun <T> SpinnerSelectionDialog(
fontWeight = FontWeight.Bold
)
}
Divider(color = Color.LightGray, thickness = 0.25.dp)
Divider(color = Color.LightGray, thickness = DividerThickness)
}
}
itemsIndexed(options) { index, item ->
@@ -160,7 +161,7 @@ fun <T> SpinnerSelectionDialog(
}
}
if (index < options.lastIndex) {
Divider(color = Color.LightGray, thickness = 0.25.dp)
Divider(color = Color.LightGray, thickness = DividerThickness)
}
}
}
@@ -209,7 +209,7 @@ fun VideoViewInner(
if (!automaticallyStartPlayback.value) {
ImageUrlWithDownloadButton(url = videoUri, showImage = automaticallyStartPlayback)
} else {
VideoPlayerActiveMutex(videoUri) { activeOnScreen ->
VideoPlayerActiveMutex(videoUri) { modifier, activeOnScreen ->
val mediaItem = remember(videoUri) {
mutableStateOf(
MediaItem.Builder()
@@ -251,6 +251,7 @@ fun VideoViewInner(
keepPlaying = keepPlaying,
automaticallyStartPlayback = automaticallyStartPlayback,
activeOnScreen = activeOnScreen,
modifier = modifier,
onControllerVisibilityChanged = onControllerVisibilityChanged,
onDialog = onDialog
)
@@ -300,43 +301,45 @@ fun GetVideoController(
nostrUriCallback,
context
) {
// REQUIRED TO BE RUN IN THE MAIN THREAD
scope.launch(Dispatchers.Main) {
// REQUIRED TO BE RUN IN THE MAIN THREAD
// checks again because of race conditions.
if (controller.value == null) { // still prone to race conditions.
controller.value = it
// 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) {
// 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.value)
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) {
if (!it.isPlaying) {
if (keepPlayingMutex?.isPlaying == true) {
// There is a video playing, start this one on mute.
it.volume = 0f
controller.value?.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
controller.value?.volume = if (defaultToStart) 0f else 1f
}
}
it.setMediaItem(mediaItem.value)
it.prepare()
controller.value?.setMediaItem(mediaItem.value)
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.value)
it.prepare()
}
}
}
}
@@ -344,18 +347,20 @@ fun GetVideoController(
}
} else {
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
}
scope.launch(Dispatchers.Main) {
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.value)
it.prepare()
it.setMediaItem(mediaItem.value)
it.prepare()
}
}
}
}
@@ -385,29 +390,32 @@ fun GetVideoController(
nostrUriCallback,
context
) {
// REQUIRED TO BE RUN IN THE MAIN THREAD
scope.launch(Dispatchers.Main) {
// REQUIRED TO BE RUN IN THE MAIN THREAD
// checks again to make sure no other thread has created a controller.
if (controller.value == null) {
controller.value = it
// 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.value)
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.value)
controller.value?.prepare()
} else if (controller.value != it) {
// discards the new controller because there is an existing one
it.stop()
it.release()
}
}
}
}
@@ -450,7 +458,7 @@ class VisibilityData() {
* the screen wins the mutex.
*/
@Composable
fun VideoPlayerActiveMutex(videoUri: String, inner: @Composable (MutableState<Boolean>) -> Unit) {
fun VideoPlayerActiveMutex(videoUri: String, inner: @Composable (Modifier, MutableState<Boolean>) -> Unit) {
val myCache = remember(videoUri) {
VisibilityData()
}
@@ -499,9 +507,7 @@ fun VideoPlayerActiveMutex(videoUri: String, inner: @Composable (MutableState<Bo
}
}
Box(modifier = myModifier) {
inner(active)
}
inner(myModifier, active)
}
@Stable
@@ -520,6 +526,7 @@ private fun RenderVideoPlayer(
keepPlaying: MutableState<Boolean>,
automaticallyStartPlayback: State<Boolean>,
activeOnScreen: MutableState<Boolean>,
modifier: Modifier,
onControllerVisibilityChanged: ((Boolean) -> Unit)? = null,
onDialog: ((Boolean) -> Unit)?
) {
@@ -552,11 +559,13 @@ private fun RenderVideoPlayer(
val myModifier = remember {
if (roundedCorner) {
borders
.defaultMinSize(minHeight = 100.dp)
.align(Alignment.Center)
modifier.then(
borders
.defaultMinSize(minHeight = 100.dp)
.align(Alignment.Center)
)
} else {
Modifier
modifier
.fillMaxWidth()
.defaultMinSize(minHeight = 100.dp)
.align(Alignment.Center)
@@ -607,7 +616,7 @@ private fun RenderVideoPlayer(
controller.volume < 0.001
}
val spaceModifier =
val spaceModifier = remember {
if (topPaddingForControllers.isSpecified && videoPlaybackSize.value.height > 0) {
val space = (abs(parentVideoPlaybackSize.value.height - videoPlaybackSize.value.height) / 2).dp
if (space > topPaddingForControllers) {
@@ -618,6 +627,7 @@ private fun RenderVideoPlayer(
} else {
Modifier
}
}
MuteButton(
controllerVisible,
@@ -837,8 +847,8 @@ private fun MuteButton(
AnimatedVisibility(
visible = holdOn.value || controllerVisible.value,
modifier = modifier,
enter = fadeIn(),
exit = fadeOut()
enter = remember { fadeIn() },
exit = remember { fadeOut() }
) {
Box(modifier = VolumeBottomIconSize) {
Box(
@@ -878,8 +888,8 @@ private fun KeepPlayingButton(
AnimatedVisibility(
visible = controllerVisible.value,
modifier = alignment,
enter = fadeIn(),
exit = fadeOut()
enter = remember { fadeIn() },
exit = remember { fadeOut() }
) {
Box(modifier = PinBottomIconSize) {
Box(
@@ -41,7 +41,6 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
@@ -62,11 +61,13 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.Dp
@@ -305,7 +306,9 @@ private fun LocalImageView(
}
val verifierModifier = if (topPaddingForControllers.isSpecified) {
Modifier.padding(top = topPaddingForControllers).align(Alignment.TopEnd)
Modifier
.padding(top = topPaddingForControllers)
.align(Alignment.TopEnd)
} else {
Modifier.align(Alignment.TopEnd)
}
@@ -373,7 +376,9 @@ private fun UrlImageView(
}
val verifierModifier = if (topPaddingForControllers.isSpecified) {
Modifier.padding(top = topPaddingForControllers).align(Alignment.TopEnd)
Modifier
.padding(top = topPaddingForControllers)
.align(Alignment.TopEnd)
} else {
Modifier.align(Alignment.TopEnd)
}
@@ -405,11 +410,58 @@ private fun UrlImageView(
}
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun ImageUrlWithDownloadButton(url: String, showImage: MutableState<Boolean>) {
FlowRow() {
ClickableUrl(urlText = url, url = url)
val uri = LocalUriHandler.current
val primary = MaterialTheme.colorScheme.primary
val background = MaterialTheme.colorScheme.onBackground
val regularText = remember { SpanStyle(color = background) }
val clickableTextStyle = remember { SpanStyle(color = primary) }
val annotatedTermsString = remember {
buildAnnotatedString {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToImage", "")
append("$url ")
}
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToImage", "")
appendInlineContent("inlineContent", "[icon]")
}
withStyle(regularText) {
append(" ")
}
}
}
val inlineContent = mapOf("inlineContent" to InlineDownloadIcon(showImage))
val pressIndicator = remember {
Modifier.clickable {
runCatching { uri.openUri(url) }
}
}
Text(
text = annotatedTermsString,
modifier = pressIndicator,
inlineContent = inlineContent
)
}
@Composable
private fun InlineDownloadIcon(showImage: MutableState<Boolean>) =
InlineTextContent(
Placeholder(
width = Font17SP,
height = Font17SP,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
)
) {
IconButton(
modifier = Modifier.size(Size20dp),
onClick = { showImage.value = true }
@@ -417,7 +469,6 @@ fun ImageUrlWithDownloadButton(url: String, showImage: MutableState<Boolean>) {
DownloadForOfflineIcon(Size24dp)
}
}
}
@Composable
@OptIn(ExperimentalLayoutApi::class)
@@ -548,43 +599,69 @@ private fun DisplayUrlWithLoadingSymbol(content: ZoomableContent) {
@Composable
private fun DisplayUrlWithLoadingSymbolWait(content: ZoomableContent) {
if (content is ZoomableUrlContent) {
ClickableUrl(urlText = remember { "${content.url} " }, url = content.url)
} else {
Text("Loading content... ")
}
val uri = LocalUriHandler.current
val myId = "inlineContent"
val emptytext = buildAnnotatedString {
withStyle(
LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.primary).toSpanStyle()
) {
append("")
appendInlineContent(myId, "[icon]")
val primary = MaterialTheme.colorScheme.primary
val background = MaterialTheme.colorScheme.onBackground
val regularText = remember { SpanStyle(color = background) }
val clickableTextStyle = remember { SpanStyle(color = primary) }
val annotatedTermsString = remember {
buildAnnotatedString {
if (content is ZoomableUrlContent) {
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToImage", "")
append(content.url + " ")
}
} else {
withStyle(regularText) {
append("Loading content...")
}
}
withStyle(clickableTextStyle) {
pushStringAnnotation("routeToImage", "")
appendInlineContent("inlineContent", "[icon]")
}
withStyle(regularText) {
append(" ")
}
}
}
val inlineContent = mapOf("inlineContent" to InlineLoadingIcon())
val pressIndicator = remember {
if (content is ZoomableUrlContent) {
Modifier.clickable {
runCatching { uri.openUri(content.url) }
}
} else {
Modifier
}
}
val inlineContent = mapOf(
Pair(
myId,
InlineTextContent(
Placeholder(
width = Font17SP,
height = Font17SP,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
)
) {
LoadingAnimation()
}
)
)
// Empty Text for Size of Icon
Text(
text = emptytext,
text = annotatedTermsString,
modifier = pressIndicator,
inlineContent = inlineContent
)
}
@Composable
private fun InlineLoadingIcon() =
InlineTextContent(
Placeholder(
width = Font17SP,
height = Font17SP,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center
)
) {
LoadingAnimation()
}
@Composable
private fun DisplayBlurHash(
blurhash: String?,
@@ -726,8 +803,8 @@ private fun DialogContent(
AnimatedVisibility(
visible = holdOn.value || controllerVisible.value,
enter = fadeIn(),
exit = fadeOut()
enter = remember { fadeIn() },
exit = remember { fadeOut() }
) {
Row(
modifier = Modifier
@@ -30,14 +30,14 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavBackStackEntry
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Font12SP
import com.vitorpamplona.amethyst.ui.theme.Size0dp
import com.vitorpamplona.amethyst.ui.theme.Size10Modifier
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import kotlinx.collections.immutable.persistentListOf
@@ -86,9 +86,18 @@ fun keyboardAsState(): State<Keyboard> {
}
@Composable
fun AppBottomBar(accountViewModel: AccountViewModel, navEntryState: State<NavBackStackEntry?>, nav: (Route, Boolean) -> Unit) {
fun IfKeyboardClosed(
inner: @Composable () -> Unit
) {
val isKeyboardOpen by keyboardAsState()
if (isKeyboardOpen == Keyboard.Closed) {
inner()
}
}
@Composable
fun AppBottomBar(accountViewModel: AccountViewModel, navEntryState: State<NavBackStackEntry?>, nav: (Route, Boolean) -> Unit) {
IfKeyboardClosed {
RenderBottomMenu(accountViewModel, navEntryState, nav)
}
}
@@ -124,30 +133,30 @@ private fun RowScope.HasNewItemsIcon(
}
}
val size = remember {
if ("Home" == route.base) 25.dp else 23.dp
}
val iconSize = remember {
if ("Home" == route.base) 24.dp else 20.dp
}
NavigationBarItem(
icon = {
val hasNewItems = accountViewModel.notificationDots.hasNewItems[route]?.collectAsStateWithLifecycle()
NotifiableIcon(
route.icon,
size,
iconSize,
selected,
hasNewItems
)
ObserveNewItems(route, accountViewModel) { hasNewItems ->
NotifiableIcon(
route.icon,
route.notifSize,
route.iconSize,
selected,
hasNewItems
)
}
},
selected = selected,
onClick = { nav(route, selected) }
)
}
@Composable
fun ObserveNewItems(route: Route, accountViewModel: AccountViewModel, inner: @Composable (hasNewItems: State<Boolean>?) -> Unit) {
val hasNewItems = accountViewModel.notificationDots.hasNewItems[route]?.collectAsStateWithLifecycle()
inner(hasNewItems)
}
@Composable
private fun NotifiableIcon(
icon: Int,
@@ -177,9 +186,7 @@ private fun NotificationDotIcon(modifier: Modifier) {
Box(modifier.size(Size10dp)) {
Box(
modifier = remember {
Modifier
.size(Size10dp)
.clip(shape = CircleShape)
Size10Modifier.clip(shape = CircleShape)
}.background(MaterialTheme.colorScheme.primary),
contentAlignment = Alignment.TopEnd
) {
@@ -187,7 +194,7 @@ private fun NotificationDotIcon(modifier: Modifier) {
"",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = 12.sp,
fontSize = Font12SP,
modifier = remember {
Modifier
.wrapContentHeight()
@@ -813,7 +813,7 @@ fun TopBarWithBackButton(caption: String, popBack: () -> Unit) {
},
actions = {}
)
Divider(thickness = 0.25.dp)
Divider(thickness = DividerThickness)
}
}
@@ -835,7 +835,7 @@ fun FlexibleTopBarWithBackButton(
actions = {}
)
Spacer(modifier = HalfVertSpacer)
Divider(thickness = 0.25.dp)
Divider(thickness = DividerThickness)
}
}
@@ -77,6 +77,7 @@ import com.vitorpamplona.amethyst.ui.note.LoadStatuses
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountBackupDialog
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ConnectOrbotDialog
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size16dp
@@ -114,7 +115,7 @@ fun DrawerContent(
nav
)
Divider(
thickness = 0.25.dp,
thickness = DividerThickness,
modifier = Modifier.padding(top = 20.dp)
)
ListContent(
@@ -761,7 +762,7 @@ fun BottomContent(user: User, drawerState: DrawerState, loadProfilePicture: Bool
Column(modifier = Modifier) {
Divider(
modifier = Modifier.padding(top = 15.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
Row(
modifier = Modifier
@@ -5,6 +5,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.ui.unit.Dp
import androidx.navigation.NamedNavArgument
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination
@@ -21,6 +22,10 @@ import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveNowFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
import com.vitorpamplona.amethyst.ui.theme.Size20dp
import com.vitorpamplona.amethyst.ui.theme.Size23dp
import com.vitorpamplona.amethyst.ui.theme.Size24dp
import com.vitorpamplona.amethyst.ui.theme.Size25dp
import com.vitorpamplona.quartz.events.ChatroomKeyable
import com.vitorpamplona.quartz.events.LiveActivitiesEvent
import kotlinx.collections.immutable.ImmutableList
@@ -30,16 +35,18 @@ import kotlinx.collections.immutable.toImmutableList
@Immutable
sealed class Route(
val route: String,
val base: String = route.substringBefore("?"),
val icon: Int,
val notifSize: Dp = Size23dp,
val iconSize: Dp = Size20dp,
val hasNewItems: (Account, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _ -> false },
val arguments: ImmutableList<NamedNavArgument> = persistentListOf()
) {
val base: String
get() = route.substringBefore("?")
object Home : Route(
route = "Home?nip47={nip47}",
icon = R.drawable.ic_home,
notifSize = Size25dp,
iconSize = Size24dp,
arguments = listOf(
navArgument("nip47") { type = NavType.StringType; nullable = true; defaultValue = null }
).toImmutableList(),
@@ -35,6 +35,7 @@ import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.newItemBackgroundColor
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.launch
@@ -161,7 +162,7 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
@@ -22,6 +21,8 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import kotlinx.collections.immutable.ImmutableSet
@@ -50,7 +51,7 @@ fun BlankNote(modifier: Modifier = Modifier, showDivider: Boolean = false, idHex
if (!showDivider) {
Divider(
modifier = Modifier.padding(vertical = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -110,7 +111,7 @@ fun HiddenNote(
.buttonColors(
contentColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.show_anyway), color = Color.White)
}
@@ -118,7 +119,7 @@ fun HiddenNote(
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -819,7 +819,7 @@ fun RenderChannelThumb(baseNote: Note, channel: Channel, accountViewModel: Accou
@OptIn(ExperimentalLayoutApi::class)
@Composable
fun Gallery(users: List<User>, accountViewModel: AccountViewModel) {
fun Gallery(users: ImmutableList<User>, accountViewModel: AccountViewModel) {
FlowRow(verticalArrangement = Arrangement.Center) {
users.take(6).forEach {
ClickableUserPicture(it, Size35dp, accountViewModel)
@@ -53,6 +53,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment.Companion.BottomEnd
import androidx.compose.ui.Alignment.Companion.CenterVertically
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -70,6 +71,7 @@ import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.graphics.drawable.toBitmap
@@ -581,7 +583,7 @@ fun CommunityHeader(
if (showBottomDiviser) {
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -2861,21 +2863,16 @@ private fun RepostNoteAuthorPicture(
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
Box(modifier = Size55Modifier) {
Box(Size35Modifier.align(Alignment.TopStart)) {
GenericRepostSection(
baseAuthorPicture = {
NoteAuthorPicture(
baseNote = baseNote,
nav = nav,
accountViewModel = accountViewModel,
size = Size34dp
)
}
Box(Size18Modifier.align(Alignment.BottomStart).padding(1.dp)) {
RepostedIcon(modifier = Size18Modifier, MaterialTheme.colorScheme.placeholderText)
}
Box(Size35Modifier.align(Alignment.BottomEnd)) {
},
repostAuthorPicture = {
NoteAuthorPicture(
baseNote = baseRepost,
nav = nav,
@@ -2883,6 +2880,39 @@ private fun RepostNoteAuthorPicture(
size = Size34dp
)
}
)
}
@Composable
@Preview
private fun GenericRepostSectionPreview() {
GenericRepostSection(
baseAuthorPicture = {
Text("ab")
},
repostAuthorPicture = {
Text("cd")
}
)
}
@Composable
private fun GenericRepostSection(
baseAuthorPicture: @Composable () -> Unit,
repostAuthorPicture: @Composable () -> Unit
) {
Box(modifier = Size55Modifier) {
Box(remember { Size35Modifier.align(Alignment.TopStart) }) {
baseAuthorPicture()
}
Box(remember { Size18Modifier.align(Alignment.BottomStart).padding(1.dp) }) {
RepostedIcon(modifier = Size18Modifier, MaterialTheme.colorScheme.placeholderText)
}
Box(remember { Size35Modifier.align(Alignment.BottomEnd) }, contentAlignment = BottomEnd) {
repostAuthorPicture()
}
}
}
@@ -2919,6 +2949,7 @@ fun DisplayHighlight(
DisplayQuoteAuthor(authorHex ?: "", url, postAddress, accountViewModel, nav)
}
@OptIn(ExperimentalLayoutApi::class)
@Composable
private fun DisplayQuoteAuthor(
authorHex: String,
@@ -2938,7 +2969,7 @@ private fun DisplayQuoteAuthor(
}
MeasureSpaceWidth {
Row(horizontalArrangement = Arrangement.spacedBy(it), verticalAlignment = Alignment.CenterVertically) {
FlowRow(horizontalArrangement = Arrangement.spacedBy(it), verticalArrangement = Arrangement.Center) {
userBase?.let { userBase ->
LoadAndDisplayUser(userBase, nav)
}
@@ -1,7 +1,6 @@
package com.vitorpamplona.amethyst.ui.note
import android.content.Context
import android.util.Log
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.ContentTransform
@@ -106,16 +105,17 @@ import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.amethyst.ui.theme.placeholderTextColorFilter
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.DecimalFormat
import kotlin.math.roundToInt
import kotlin.time.measureTimedValue
@Composable
fun ReactionsRow(
@@ -149,6 +149,50 @@ private fun InnerReactionRow(
wantsToSeeReactions: MutableState<Boolean>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
GenericInnerReactionRow(
showReactionDetail = showReactionDetail,
one = {
WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) {
RenderShowIndividualReactionsButton(wantsToSeeReactions)
}
},
two = {
ReplyReactionWithDialog(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
},
three = {
BoostWithDialog(
baseNote,
MaterialTheme.colorScheme.placeholderText,
accountViewModel,
nav
)
},
four = {
LikeReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
},
five = {
ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
},
six = {
ViewCountReaction(
note = baseNote,
grayTint = MaterialTheme.colorScheme.placeholderText,
viewCountColorFilter = MaterialTheme.colorScheme.placeholderTextColorFilter
)
}
)
}
@Composable
private fun GenericInnerReactionRow(
showReactionDetail: Boolean,
one: @Composable () -> Unit,
two: @Composable () -> Unit,
three: @Composable () -> Unit,
four: @Composable () -> Unit,
five: @Composable () -> Unit,
six: @Composable () -> Unit
) {
Row(verticalAlignment = CenterVertically, modifier = ReactionRowHeight) {
if (showReactionDetail) {
@@ -156,14 +200,9 @@ private fun InnerReactionRow(
verticalArrangement = Arrangement.Center,
modifier = ReactionRowExpandButton
) {
val (value, elapsed) = measureTimedValue {
Row(verticalAlignment = CenterVertically) {
WatchReactionsZapsBoostsAndDisplayIfExists(baseNote) {
RenderShowIndividualReactionsButton(wantsToSeeReactions)
}
}
Row(verticalAlignment = CenterVertically) {
one()
}
Log.d("Rendering Metrics", "Reactions Button: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed")
}
}
@@ -171,69 +210,45 @@ private fun InnerReactionRow(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
) {
val (value, elapsed) = measureTimedValue {
Row(verticalAlignment = CenterVertically) {
ReplyReactionWithDialog(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
}
Row(verticalAlignment = CenterVertically) {
two()
}
Log.d("Rendering Metrics", "Reaction Reply: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed")
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
) {
val (value, elapsed) = measureTimedValue {
Row(verticalAlignment = CenterVertically) {
BoostWithDialog(
baseNote,
MaterialTheme.colorScheme.placeholderText,
accountViewModel,
nav
)
}
Row(verticalAlignment = CenterVertically) {
three()
}
Log.d("Rendering Metrics", "Reaction Boost: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed")
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
) {
val (value, elapsed) = measureTimedValue {
Row(verticalAlignment = CenterVertically) {
LikeReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav)
}
Row(verticalAlignment = CenterVertically) {
four()
}
Log.d("Rendering Metrics", "Reaction Likes: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed")
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
) {
val (value, elapsed) = measureTimedValue {
Row(verticalAlignment = CenterVertically) {
ZapReaction(baseNote, MaterialTheme.colorScheme.placeholderText, accountViewModel, nav = nav)
}
Row(verticalAlignment = CenterVertically) {
five()
}
Log.d("Rendering Metrics", "Reaction Zaps: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed")
}
Column(
verticalArrangement = Arrangement.Center,
modifier = remember { Modifier.weight(1f) }
) {
val (value, elapsed) = measureTimedValue {
Row(verticalAlignment = CenterVertically) {
ViewCountReaction(
note = baseNote,
grayTint = MaterialTheme.colorScheme.placeholderText,
viewCountColorFilter = MaterialTheme.colorScheme.placeholderTextColorFilter
)
}
Row(verticalAlignment = CenterVertically) {
six()
}
Log.d("Rendering Metrics", "Reaction Views: ${baseNote.event?.content()?.split("\n")?.getOrNull(0)?.take(15)}.. $elapsed")
}
}
}
@@ -290,9 +305,11 @@ fun RenderZapRaiser(baseNote: Note, zapraiserAmount: Long, details: Boolean, acc
}
LinearProgressIndicator(
modifier = Modifier
.fillMaxWidth()
.height(if (details) 24.dp else 4.dp),
modifier = remember(details) {
Modifier
.fillMaxWidth()
.height(if (details) 24.dp else 4.dp)
},
color = color,
progress = zapraiserStatus.progress
)
@@ -372,7 +389,7 @@ private fun RenderShowIndividualReactionsButton(wantsToSeeReactions: MutableStat
},
modifier = Size20Modifier
) {
Crossfade(targetState = wantsToSeeReactions.value) {
Crossfade(targetState = wantsToSeeReactions.value, label = "RenderShowIndividualReactionsButton") {
if (it) {
ExpandLessIcon(modifier = Size22Modifier)
} else {
@@ -645,25 +662,8 @@ fun BoostReaction(
IconButton(
modifier = iconButtonModifier,
onClick = {
if (accountViewModel.isWriteable()) {
if (accountViewModel.hasBoosted(baseNote)) {
accountViewModel.deleteBoostsTo(baseNote)
} else {
wantsToBoost = true
}
} else {
if (accountViewModel.loggedInWithExternalSigner()) {
if (accountViewModel.hasBoosted(baseNote)) {
accountViewModel.deleteBoostsTo(baseNote)
} else {
wantsToBoost = true
}
} else {
accountViewModel.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_boost_posts
)
}
accountViewModel.tryBoost(baseNote) {
wantsToBoost = true
}
}
) {
@@ -697,7 +697,9 @@ fun BoostIcon(baseNote: Note, iconSize: Dp = Size20dp, grayTint: Color, accountV
baseNote.live().boosts.map {
if (it.note.isBoostedBy(accountViewModel.userProfile())) Color.Unspecified else grayTint
}.distinctUntilChanged()
}.observeAsState(grayTint)
}.observeAsState(
if (baseNote.isBoostedBy(accountViewModel.userProfile())) Color.Unspecified else grayTint
)
val iconModifier = remember {
Modifier.size(iconSize)
@@ -834,13 +836,16 @@ private fun RenderReactionType(
iconSize: Dp = Size20dp,
iconFontSize: TextUnit
) {
if (reactionType.startsWith(":")) {
val noStartColon = reactionType.removePrefix(":")
val url = noStartColon.substringAfter(":")
val isCustom = remember(reactionType) {
reactionType.startsWith(":")
}
val renderable = listOf(
ImageUrlType(url)
).toImmutableList()
if (isCustom) {
val renderable = remember(reactionType) {
listOf(
ImageUrlType(reactionType.removePrefix(":").substringAfter(":"))
).toImmutableList()
}
InLineIconRenderer(
renderable,
@@ -1277,7 +1282,7 @@ fun ReactionChoicePopup(
val account = accountState?.account ?: return
val toRemove = remember {
baseNote.reactedBy(account.userProfile()).toSet()
baseNote.reactedBy(account.userProfile()).toImmutableSet()
}
val iconSizePx = with(LocalDensity.current) {
@@ -1312,7 +1317,7 @@ private fun ActionableReactionButton(
accountViewModel: AccountViewModel,
onDismiss: () -> Unit,
onChangeAmount: () -> Unit,
toRemove: Set<String>
toRemove: ImmutableSet<String>
) {
Button(
modifier = Modifier.padding(horizontal = 3.dp),
@@ -1,7 +1,6 @@
package com.vitorpamplona.amethyst.ui.note
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
@@ -27,6 +26,8 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.RelayInfo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import java.time.Instant
import java.time.ZoneId
@@ -86,7 +87,7 @@ fun RelayCompose(
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -120,7 +121,7 @@ fun AddRelayButton(onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(id = R.string.add), color = Color.White)
}
@@ -135,7 +136,7 @@ fun RemoveRelayButton(onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.remove), color = Color.White)
}
@@ -32,21 +32,22 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@Composable
public fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
var expanded by remember { mutableStateOf(false) }
val relayList by baseNote.live().relayInfo.observeAsState(persistentListOf())
val shortRelayList by remember {
derivedStateOf {
relayList.take(3).toImmutableList()
}
}
Spacer(DoubleVertSpacer)
if (expanded) {
VerticalRelayPanelWithFlow(relayList, accountViewModel, nav)
} else {
val shortRelayList by remember {
derivedStateOf {
relayList.take(3).toImmutableList()
}
}
VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
}
@@ -21,7 +21,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -120,7 +119,6 @@ fun RenderRelay(relay: RelayBriefInfo, accountViewModel: AccountViewModel, nav:
}
val context = LocalContext.current
val scope = rememberCoroutineScope()
val interactionSource = remember { MutableInteractionSource() }
val ripple = rememberRipple(bounded = false, radius = Size15dp)
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.events.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.events.EmojiUrl
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
@@ -321,8 +322,10 @@ private fun EmojiSelector(accountViewModel: AccountViewModel, nav: (String) -> U
) { emptyNote ->
emptyNote?.let { usersEmojiList ->
val collections by usersEmojiList.live().metadata.map {
(it.note.event as? EmojiPackSelectionEvent)?.taggedAddresses()
}.distinctUntilChanged().observeAsState((usersEmojiList.event as? EmojiPackSelectionEvent)?.taggedAddresses())
(it.note.event as? EmojiPackSelectionEvent)?.taggedAddresses()?.toImmutableList()
}.distinctUntilChanged().observeAsState(
(usersEmojiList.event as? EmojiPackSelectionEvent)?.taggedAddresses()?.toImmutableList()
)
collections?.let {
EmojiCollectionGallery(it, accountViewModel, nav, onClick)
@@ -332,7 +335,7 @@ private fun EmojiSelector(accountViewModel: AccountViewModel, nav: (String) -> U
}
@Composable
fun EmojiCollectionGallery(emojiCollections: List<ATag>, accountViewModel: AccountViewModel, nav: (String) -> Unit, onClick: ((EmojiUrl) -> Unit)? = null) {
fun EmojiCollectionGallery(emojiCollections: ImmutableList<ATag>, accountViewModel: AccountViewModel, nav: (String) -> Unit, onClick: ((EmojiUrl) -> Unit)? = null) {
val color = MaterialTheme.colorScheme.background
val bgColor = remember { mutableStateOf(color) }
@@ -76,6 +76,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner
import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer
import com.vitorpamplona.amethyst.ui.screen.loggedIn.getFragmentActivity
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.encoders.decodePublicKey
@@ -376,7 +377,7 @@ fun UpdateZapAmountDialog(
Divider(
modifier = Modifier.padding(vertical = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
var qrScanning by remember { mutableStateOf(false) }
@@ -12,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.StdPadding
@@ -50,7 +51,7 @@ fun UserCompose(
if (showDiviser) {
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -80,7 +80,7 @@ fun NoteAuthorPicture(
) {
val author by baseNote.live().authorChanges.observeAsState(baseNote.author)
Crossfade(targetState = author) {
Crossfade(targetState = author, label = "NoteAuthorPicture") {
if (it == null) {
DisplayBlankAuthor(size, modifier)
} else {
@@ -351,8 +351,8 @@ fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewMod
WatchUserFollows(userHex, accountViewModel) { newFollowingState ->
AnimatedVisibility(
visible = newFollowingState,
enter = fadeIn(),
exit = fadeOut()
enter = remember { fadeIn() },
exit = remember { fadeOut() }
) {
Box(contentAlignment = Alignment.TopEnd) {
FollowingIcon(iconSize)
@@ -63,6 +63,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size16dp
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.LnZapEvent
import kotlinx.collections.immutable.ImmutableList
@@ -417,14 +418,12 @@ fun payViaIntent(invoice: String, context: Context, onError: (String) -> Unit) {
fun PayButton(isActive: Boolean, modifier: Modifier = Modifier, onPost: () -> Unit = {}) {
Button(
modifier = modifier,
onClick = {
onPost()
},
onClick = onPost,
shape = ButtonBorder,
colors = ButtonDefaults.buttonColors(
containerColor = if (isActive) MaterialTheme.colorScheme.primary else Color.Gray
),
contentPadding = PaddingValues(0.dp)
contentPadding = ZeroPadding
) {
if (isActive) {
Text(text = stringResource(R.string.pay), color = Color.White)
@@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
import com.vitorpamplona.amethyst.ui.screen.loggedIn.WatchIsHiddenUser
import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.LnZapEvent
@@ -76,7 +77,7 @@ fun ZapNoteCompose(baseReqResponse: ZapReqResponse, accountViewModel: AccountVie
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -33,7 +33,7 @@ class AccountStateViewModel() : ViewModel() {
fun tryLoginExistingAccountAsync() {
// pulls account from storage.
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
tryLoginExistingAccount()
}
}
@@ -124,7 +124,7 @@ class AccountStateViewModel() : ViewModel() {
loginWithExternalSigner: Boolean = false,
onError: () -> Unit
) {
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
try {
loginAndStartUI(key, useProxy, proxyPort, loginWithExternalSigner)
} catch (e: Exception) {
@@ -135,9 +135,12 @@ class AccountStateViewModel() : ViewModel() {
}
fun newKey(useProxy: Boolean, proxyPort: Int) {
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
val proxy = HttpClient.initProxy(useProxy, "127.0.0.1", proxyPort)
val account = Account(KeyPair(), proxy = proxy, proxyPort = proxyPort)
account.follow(account.userProfile())
// saves to local preferences
LocalPreferences.updatePrefsForLogin(account)
startUI(account)
@@ -145,7 +148,7 @@ class AccountStateViewModel() : ViewModel() {
}
fun switchUser(accountInfo: AccountInfo) {
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
prepareLogoutOrSwitch()
LocalPreferences.switchToAccount(accountInfo)
tryLoginExistingAccount()
@@ -153,7 +156,7 @@ class AccountStateViewModel() : ViewModel() {
}
fun logOff(accountInfo: AccountInfo) {
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
prepareLogoutOrSwitch()
LocalPreferences.updatePrefsForLogout(accountInfo)
tryLoginExistingAccount()
@@ -4,8 +4,6 @@ import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -26,7 +24,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
import com.vitorpamplona.amethyst.ui.note.MessageSetCompose
@@ -34,6 +31,7 @@ import com.vitorpamplona.amethyst.ui.note.MultiSetCompose
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.ZapUserSetCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun RefresheableCardView(
@@ -56,15 +54,13 @@ fun RefresheableCardView(
)
val modifier = if (enablePullRefresh) {
Modifier.pullRefresh(pullRefreshState)
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
} else {
Modifier
Modifier.fillMaxSize()
}
Box(modifier) {
Column {
SaveableCardFeedState(viewModel, accountViewModel, nav, routeForLastRead, scrollStateKey)
}
SaveableCardFeedState(viewModel, accountViewModel, nav, routeForLastRead, scrollStateKey)
if (enablePullRefresh) {
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
@@ -117,7 +113,7 @@ fun RenderCardFeed(
val feedState by viewModel.feedContent.collectAsStateWithLifecycle()
Crossfade(
modifier = remember { Modifier.fillMaxSize() },
modifier = Modifier.fillMaxSize(),
targetState = feedState,
animationSpec = tween(durationMillis = 100)
) { state ->
@@ -158,13 +154,8 @@ private fun FeedLoaded(
nav: (String) -> Unit
) {
LazyColumn(
modifier = remember { Modifier.fillMaxSize() },
contentPadding = remember {
PaddingValues(
top = 10.dp,
bottom = 10.dp
)
},
modifier = Modifier.fillMaxSize(),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { _, item ->
@@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@@ -17,11 +16,11 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.HalfPadding
@@ -91,10 +90,7 @@ fun ChatroomFeedLoaded(
}
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
modifier = Modifier.fillMaxSize(),
reverseLayout = true,
state = listState
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.screen
import android.util.Log
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
@@ -14,10 +13,10 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.note.ChatroomHeaderCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
@@ -89,10 +88,7 @@ private fun FeedLoaded(
}
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(
@@ -7,7 +7,6 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
@@ -31,11 +30,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
@@ -65,16 +64,14 @@ fun RefresheableView(
val modifier = remember {
if (enablePullRefresh) {
Modifier.pullRefresh(pullRefreshState)
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
} else {
Modifier
Modifier.fillMaxSize()
}
}
Box(modifier) {
Column(Modifier.fillMaxSize()) {
content()
}
content()
if (enablePullRefresh) {
PullRefreshIndicator(
@@ -191,10 +188,7 @@ private fun FeedLoaded(
}
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
@@ -2,16 +2,15 @@ package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun LnZapFeedView(
@@ -52,10 +51,7 @@ private fun LnZapFeedLoaded(
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.zapEvent.idHex }) { _, item ->
@@ -2,8 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen
import android.util.Log
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -11,13 +10,13 @@ import androidx.compose.material3.pullrefresh.PullRefreshIndicator
import androidx.compose.material3.pullrefresh.pullRefresh
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewModelScope
@@ -28,12 +27,14 @@ import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.note.RelayCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@Stable
class RelayFeedViewModel : ViewModel() {
val order = compareByDescending<RelayInfo> { it.lastEvent }.thenByDescending { it.counter }.thenBy { it.url }
@@ -125,30 +126,25 @@ fun RelayFeedView(
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
val modifier = if (enablePullRefresh) {
Modifier.pullRefresh(pullRefreshState)
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
} else {
Modifier
Modifier.fillMaxSize()
}
Box(modifier) {
Column() {
val listState = rememberLazyListState()
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(feedState, key = { _, item -> item.url }) { _, item ->
RelayCompose(
item,
accountViewModel = accountViewModel,
onAddRelay = { wantsToAddRelay = item.url },
onRemoveRelay = { wantsToAddRelay = item.url }
)
}
LazyColumn(
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(feedState, key = { _, item -> item.url }) { _, item ->
RelayCompose(
item,
accountViewModel = accountViewModel,
onAddRelay = { wantsToAddRelay = item.url },
onRemoveRelay = { wantsToAddRelay = item.url }
)
}
}
@@ -4,7 +4,6 @@ import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
@@ -16,9 +15,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun RefreshingFeedStringFeedView(
@@ -99,10 +98,7 @@ private fun FeedLoaded(
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
item {
@@ -8,7 +8,6 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@@ -91,7 +90,9 @@ import com.vitorpamplona.amethyst.ui.note.Reward
import com.vitorpamplona.amethyst.ui.note.timeAgo
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
import com.vitorpamplona.amethyst.ui.theme.lessImportantLink
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@@ -172,10 +173,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
}
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
@@ -507,7 +505,7 @@ fun NoteMaster(
ReactionsRow(note, true, accountViewModel, nav)
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
@@ -2,16 +2,15 @@ package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
@Composable
fun RefreshingFeedUserFeedView(
@@ -67,10 +66,7 @@ private fun FeedLoaded(
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { _, item ->
@@ -10,7 +10,6 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
@@ -47,6 +46,7 @@ import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.ui.actions.CloseButton
import com.vitorpamplona.amethyst.ui.note.authenticate
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.quartz.encoders.toNsec
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@@ -131,7 +131,7 @@ private fun NSecCopyButton(
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Icon(
tint = MaterialTheme.colorScheme.onPrimary,
@@ -14,6 +14,7 @@ import androidx.lifecycle.map
import androidx.lifecycle.viewModelScope
import coil.imageLoader
import coil.request.ImageRequest
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.AccountState
import com.vitorpamplona.amethyst.model.AddressableNote
@@ -31,6 +32,7 @@ import com.vitorpamplona.amethyst.service.OnlineChecker
import com.vitorpamplona.amethyst.service.ZapPaymentHandler
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.ui.actions.Dao
import com.vitorpamplona.amethyst.ui.components.BundledInsert
import com.vitorpamplona.amethyst.ui.components.MarkdownParser
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
import com.vitorpamplona.amethyst.ui.navigation.Route
@@ -861,6 +863,13 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
private var collectorJob: Job? = null
val notificationDots = HasNotificationDot(bottomNavigationItems, account)
private val bundlerInsert = BundledInsert<Set<Note>>(3000, Dispatchers.IO)
fun invalidateInsertData(newItems: Set<Note>) {
bundlerInsert.invalidateList(newItems) {
updateNotificationDots(it.flatten().toSet())
}
}
suspend fun updateNotificationDots(newNotes: Set<Note> = emptySet()) {
val (value, elapsed) = measureTimedValue {
@@ -874,8 +883,7 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
collectorJob = viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { newNotes ->
Log.d("Rendering Metrics", "Notification Dots Calculation refresh ${this@AccountViewModel}")
updateNotificationDots(newNotes)
invalidateInsertData(newNotes)
}
}
}
@@ -915,6 +923,29 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
onReady(newSortedMentions)
}
}
fun tryBoost(baseNote: Note, onMore: () -> Unit) {
if (isWriteable()) {
if (hasBoosted(baseNote)) {
deleteBoostsTo(baseNote)
} else {
onMore()
}
} else {
if (loggedInWithExternalSigner()) {
if (hasBoosted(baseNote)) {
deleteBoostsTo(baseNote)
} else {
onMore()
}
} else {
toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_boost_posts
)
}
}
}
}
class HasNotificationDot(bottomNavigationItems: ImmutableList<Route>, val account: Account) {
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.MaterialTheme
@@ -17,7 +16,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter
@@ -31,65 +29,58 @@ import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun BookmarkListScreen(accountViewModel: AccountViewModel, nav: (String) -> Unit) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account
BookmarkPublicFeedFilter.account = accountViewModel.account
BookmarkPrivateFeedFilter.account = accountViewModel.account
if (account != null) {
BookmarkPublicFeedFilter.account = account
BookmarkPrivateFeedFilter.account = account
val publicFeedViewModel: NostrBookmarkPublicFeedViewModel = viewModel()
val privateFeedViewModel: NostrBookmarkPrivateFeedViewModel = viewModel()
val publicFeedViewModel: NostrBookmarkPublicFeedViewModel = viewModel()
val privateFeedViewModel: NostrBookmarkPrivateFeedViewModel = viewModel()
val userState by accountViewModel.account.userProfile().live().bookmarks.observeAsState()
val userState by account.userProfile().live().bookmarks.observeAsState()
LaunchedEffect(userState) {
publicFeedViewModel.invalidateData()
privateFeedViewModel.invalidateData()
}
LaunchedEffect(userState) {
publicFeedViewModel.invalidateData()
privateFeedViewModel.invalidateData()
Column(Modifier.fillMaxHeight()) {
val pagerState = rememberPagerState() { 2 }
val coroutineScope = rememberCoroutineScope()
TabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = stringResource(R.string.private_bookmarks))
}
)
Tab(
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = stringResource(R.string.public_bookmarks))
}
)
}
Column(Modifier.fillMaxHeight()) {
Column(modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
val pagerState = rememberPagerState() { 2 }
val coroutineScope = rememberCoroutineScope()
TabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = stringResource(R.string.private_bookmarks))
}
)
Tab(
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = stringResource(R.string.public_bookmarks))
}
)
}
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> RefresheableFeedView(
privateFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav
)
1 -> RefresheableFeedView(
publicFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav
)
}
}
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> RefresheableFeedView(
privateFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav
)
1 -> RefresheableFeedView(
publicFeedViewModel,
null,
accountViewModel = accountViewModel,
nav = nav
)
}
}
}
@@ -8,7 +8,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.defaultMinSize
@@ -117,6 +116,8 @@ import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
@@ -130,6 +131,7 @@ import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.SmallBorder
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.EmptyTagList
import com.vitorpamplona.quartz.events.LiveActivitiesEvent.Companion.STATUS_LIVE
@@ -615,7 +617,7 @@ fun ChannelHeader(
if (showBottomDiviser) {
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -1115,14 +1117,9 @@ private fun EditButton(accountViewModel: AccountViewModel, channel: PublicChatCh
}
Button(
modifier = Modifier
.padding(horizontal = 3.dp)
.width(50.dp),
modifier = Modifier.padding(horizontal = 3.dp).width(50.dp),
onClick = { wantsToPost = true },
shape = ButtonBorder,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
contentPadding = ZeroPadding
) {
Icon(
tint = Color.White,
@@ -1143,11 +1140,7 @@ fun JoinChatButton(accountViewModel: AccountViewModel, channel: Channel, nav: (S
accountViewModel.account.follow(channel)
}
},
shape = ButtonBorder,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.join), color = Color.White)
}
@@ -1164,11 +1157,7 @@ fun LeaveChatButton(accountViewModel: AccountViewModel, channel: Channel, nav: (
accountViewModel.account.unfollow(channel)
}
},
shape = ButtonBorder,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.leave), color = Color.White)
}
@@ -1189,7 +1178,7 @@ fun JoinCommunityButton(accountViewModel: AccountViewModel, note: AddressableNot
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.join), color = Color.White)
}
@@ -1210,7 +1199,7 @@ fun LeaveCommunityButton(accountViewModel: AccountViewModel, note: AddressableNo
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.leave), color = Color.White)
}
@@ -221,64 +221,60 @@ fun ChatroomListScreenOnlyList(
}
}
Box(Modifier.fillMaxSize()) {
Column(Modifier.fillMaxHeight()) {
Column(
modifier = Modifier.padding(vertical = 0.dp)
Column(
modifier = Modifier.fillMaxHeight()
) {
Box(Modifier.fillMaxWidth()) {
TabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight
) {
Box(Modifier.fillMaxWidth()) {
TabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight
) {
tabs.forEachIndexed { index, tab ->
Tab(
selected = pagerState.currentPage == index,
text = {
Text(text = stringResource(tab.resource))
},
onClick = {
coroutineScope.launch { pagerState.animateScrollToPage(index) }
}
)
tabs.forEachIndexed { index, tab ->
Tab(
selected = pagerState.currentPage == index,
text = {
Text(text = stringResource(tab.resource))
},
onClick = {
coroutineScope.launch { pagerState.animateScrollToPage(index) }
}
}
IconButton(
modifier = Modifier
.size(40.dp)
.align(Alignment.CenterEnd),
onClick = { moreActionsExpanded = true }
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null,
tint = MaterialTheme.colorScheme.placeholderText
)
ChatroomTabMenu(
moreActionsExpanded,
{ moreActionsExpanded = false },
{ markKnownAsRead.value = true },
{ markNewAsRead.value = true }
)
}
}
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { page ->
ChatroomListFeedView(
viewModel = tabs[page].viewModel,
accountViewModel = accountViewModel,
nav = nav,
markAsRead = tabs[page].markAsRead
)
}
}
IconButton(
modifier = Modifier
.size(40.dp)
.align(Alignment.CenterEnd),
onClick = { moreActionsExpanded = true }
) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = null,
tint = MaterialTheme.colorScheme.placeholderText
)
ChatroomTabMenu(
moreActionsExpanded,
{ moreActionsExpanded = false },
{ markKnownAsRead.value = true },
{ markNewAsRead.value = true }
)
}
}
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize()
) { page ->
ChatroomListFeedView(
viewModel = tabs[page].viewModel,
accountViewModel = accountViewModel,
nav = nav,
markAsRead = tabs[page].markAsRead
)
}
}
}
@@ -84,6 +84,7 @@ import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
@@ -548,7 +549,7 @@ fun ChatroomHeader(
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -592,7 +593,7 @@ fun GroupChatroomHeader(
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -4,7 +4,6 @@ import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@@ -54,6 +53,7 @@ import com.vitorpamplona.amethyst.ui.screen.RefresheableView
import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
import com.vitorpamplona.quartz.events.ChannelCreateEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
@@ -234,10 +234,7 @@ private fun DiscoverFeedLoaded(
nav: (String) -> Unit
) {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
@@ -273,10 +270,7 @@ private fun DiscoverFeedTwoColumnsLoaded(
) {
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item ->
@@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.service.NostrGeohashDataSource
import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil
import com.vitorpamplona.amethyst.ui.screen.NostrGeoHashFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -124,7 +125,7 @@ fun GeoHashHeader(tag: String, modifier: Modifier = StdPadding, account: Account
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.service.NostrHashtagDataSource
import com.vitorpamplona.amethyst.ui.screen.NostrHashtagFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.StdPadding
@Composable
@@ -121,7 +122,7 @@ fun HashtagHeader(tag: String, modifier: Modifier = StdPadding, account: Account
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -3,7 +3,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
@@ -56,9 +55,9 @@ import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
import com.vitorpamplona.amethyst.ui.screen.StringFeedView
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.HorzPadding
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.StdPadding
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
import com.vitorpamplona.amethyst.ui.theme.placeholderText
@@ -109,78 +108,76 @@ fun HiddenUsersScreen(
}
Column(Modifier.fillMaxHeight()) {
Column(modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
val pagerState = rememberPagerState() { 3 }
val coroutineScope = rememberCoroutineScope()
var warnAboutReports by remember { mutableStateOf(accountViewModel.account.warnAboutPostsWithReports) }
var filterSpam by remember { mutableStateOf(accountViewModel.account.filterSpamFromStrangers) }
val pagerState = rememberPagerState() { 3 }
val coroutineScope = rememberCoroutineScope()
var warnAboutReports by remember { mutableStateOf(accountViewModel.account.warnAboutPostsWithReports) }
var filterSpam by remember { mutableStateOf(accountViewModel.account.filterSpamFromStrangers) }
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = warnAboutReports,
onCheckedChange = {
warnAboutReports = it
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
}
)
Text(stringResource(R.string.warn_when_posts_have_reports_from_your_follows))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = filterSpam,
onCheckedChange = {
filterSpam = it
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
}
)
Text(stringResource(R.string.filter_spam_from_strangers))
}
ScrollableTabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
edgePadding = 8.dp,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight,
divider = {
Divider(thickness = DividerThickness)
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = warnAboutReports,
onCheckedChange = {
warnAboutReports = it
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
}
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = stringResource(R.string.blocked_users))
}
)
Tab(
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = stringResource(R.string.spamming_users))
}
)
Tab(
selected = pagerState.currentPage == 2,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
text = {
Text(text = stringResource(R.string.hidden_words))
}
)
}
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> RefreshingUserFeedView(hiddenFeedViewModel, accountViewModel) {
RefreshingFeedUserFeedView(hiddenFeedViewModel, accountViewModel, nav)
}
1 -> RefreshingUserFeedView(spammerFeedViewModel, accountViewModel) {
RefreshingFeedUserFeedView(spammerFeedViewModel, accountViewModel, nav)
}
2 -> HiddenWordsFeed(hiddenWordsViewModel, accountViewModel)
)
Text(stringResource(R.string.warn_when_posts_have_reports_from_your_follows))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = filterSpam,
onCheckedChange = {
filterSpam = it
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
}
)
Text(stringResource(R.string.filter_spam_from_strangers))
}
ScrollableTabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
edgePadding = 8.dp,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight,
divider = {
Divider(thickness = DividerThickness)
}
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = {
Text(text = stringResource(R.string.blocked_users))
}
)
Tab(
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = {
Text(text = stringResource(R.string.spamming_users))
}
)
Tab(
selected = pagerState.currentPage == 2,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
text = {
Text(text = stringResource(R.string.hidden_words))
}
)
}
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> RefreshingUserFeedView(hiddenFeedViewModel, accountViewModel) {
RefreshingFeedUserFeedView(hiddenFeedViewModel, accountViewModel, nav)
}
1 -> RefreshingUserFeedView(spammerFeedViewModel, accountViewModel) {
RefreshingFeedUserFeedView(spammerFeedViewModel, accountViewModel, nav)
}
2 -> HiddenWordsFeed(hiddenWordsViewModel, accountViewModel)
}
}
}
@@ -203,7 +200,7 @@ private fun HiddenWordsFeed(
@Composable
private fun AddMuteWordTextField(accountViewModel: AccountViewModel) {
Row(modifier = Modifier.padding(vertical = Size10dp)) {
Row() {
val currentWordToAdd = remember {
mutableStateOf("")
}
@@ -217,7 +214,7 @@ private fun AddMuteWordTextField(accountViewModel: AccountViewModel) {
value = currentWordToAdd.value,
onValueChange = { currentWordToAdd.value = it },
label = { Text(text = stringResource(R.string.hide_new_word_label)) },
modifier = Modifier.fillMaxWidth(),
modifier = Modifier.fillMaxWidth().padding(10.dp),
placeholder = {
Text(
text = stringResource(R.string.hide_new_word_label),
@@ -291,7 +288,7 @@ fun MutedWordHeader(tag: String, modifier: Modifier = StdPadding, account: Accou
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -347,7 +344,7 @@ fun HideWordButton(onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.block_only), color = Color.White)
}
@@ -362,7 +359,7 @@ fun ShowWordButton(text: Int = R.string.unblock, onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center)
}
@@ -4,7 +4,6 @@ import androidx.compose.animation.Crossfade
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.material3.MaterialTheme
@@ -24,7 +23,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import com.vitorpamplona.amethyst.R
@@ -52,16 +50,65 @@ fun HomeScreen(
nav: (String) -> Unit,
nip47: String? = null
) {
var wantsToAddNip47 by remember(nip47) { mutableStateOf(nip47) }
val pagerState = rememberForeverPagerState(key = PagerStateKeys.HOME_SCREEN) { 2 }
ResolveNIP47(nip47, accountViewModel)
WatchAccountForHomeScreen(homeFeedViewModel, repliesFeedViewModel, accountViewModel)
WatchLifeCycleChanges()
AssembleHomeTabs(homeFeedViewModel, repliesFeedViewModel) { pagerState, tabItems ->
AssembleHomePage(pagerState, tabItems, accountViewModel, nav)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun AssembleHomeTabs(
homeFeedViewModel: NostrHomeFeedViewModel,
repliesFeedViewModel: NostrHomeRepliesFeedViewModel,
inner: @Composable (PagerState, ImmutableList<TabItem>) -> Unit
) {
val pagerState = rememberForeverPagerState(key = PagerStateKeys.HOME_SCREEN) { 2 }
val tabs by remember(homeFeedViewModel, repliesFeedViewModel) {
mutableStateOf(
listOf(
TabItem(R.string.new_threads, homeFeedViewModel, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS),
TabItem(R.string.conversations, repliesFeedViewModel, Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES)
).toImmutableList()
)
}
inner(pagerState, tabs)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun AssembleHomePage(
pagerState: PagerState,
tabs: ImmutableList<TabItem>,
accountViewModel: AccountViewModel,
nav: (String) -> Unit
) {
Column(Modifier.fillMaxHeight()) {
HomePages(pagerState, tabs, accountViewModel, nav)
}
}
@Composable
fun ResolveNIP47(
nip47: String?,
accountViewModel: AccountViewModel
) {
var wantsToAddNip47 by remember(nip47) { mutableStateOf(nip47) }
if (wantsToAddNip47 != null) {
UpdateZapAmountDialog({ wantsToAddNip47 = null }, wantsToAddNip47, accountViewModel)
}
}
@Composable
private fun WatchLifeCycleChanges() {
val lifeCycleOwner = LocalLifecycleOwner.current
DisposableEffect(lifeCycleOwner) {
val observer = LifecycleEventObserver { _, event ->
@@ -75,23 +122,6 @@ fun HomeScreen(
lifeCycleOwner.lifecycle.removeObserver(observer)
}
}
val tabs by remember(homeFeedViewModel, repliesFeedViewModel) {
mutableStateOf(
listOf(
TabItem(R.string.new_threads, homeFeedViewModel, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS),
TabItem(R.string.conversations, repliesFeedViewModel, Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES)
).toImmutableList()
)
}
Column(Modifier.fillMaxHeight()) {
Column(
modifier = Modifier.padding(vertical = 0.dp)
) {
HomePages(pagerState, tabs, accountViewModel, nav)
}
}
}
@Composable
@@ -3,10 +3,14 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
@@ -76,6 +80,7 @@ import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
import kotlinx.coroutines.launch
import kotlin.math.abs
@@ -324,13 +329,23 @@ fun MainScreen(
}
},
floatingActionButton = {
FloatingButtons(
navState,
accountViewModel,
accountStateViewModel,
nav,
navBottomRow
)
AnimatedVisibility(
visible = shouldShow.value,
enter = remember { scaleIn() },
exit = remember { scaleOut() }
) {
Box(
Size55Modifier
) {
FloatingButtons(
navState,
accountViewModel,
accountStateViewModel,
nav,
navBottomRow
)
}
}
}
) {
Column(
@@ -62,6 +62,7 @@ import com.vitorpamplona.amethyst.ui.screen.RefresheableCardView
import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.RoyalBlue
import com.vitorpamplona.amethyst.ui.theme.chartStyle
import java.math.BigDecimal
@@ -96,21 +97,17 @@ fun NotificationScreen(
}
Column(Modifier.fillMaxHeight()) {
Column(
modifier = Modifier.padding(vertical = 0.dp)
) {
SummaryBar(
model = userReactionsStatsModel
)
SummaryBar(
model = userReactionsStatsModel
)
RefresheableCardView(
viewModel = notifFeedViewModel,
accountViewModel = accountViewModel,
nav = nav,
routeForLastRead = Route.Notification.base,
scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN
)
}
RefresheableCardView(
viewModel = notifFeedViewModel,
accountViewModel = accountViewModel,
nav = nav,
routeForLastRead = Route.Notification.base,
scrollStateKey = ScrollStateKeys.NOTIFICATION_SCREEN
)
}
}
@@ -213,7 +210,7 @@ fun SummaryBar(model: UserReactionsViewModel) {
}
Divider(
thickness = 0.25.dp
thickness = DividerThickness
)
}
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
@@ -134,9 +133,11 @@ import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Size16Modifier
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.amethyst.ui.theme.ZeroPadding
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.events.AppDefinitionEvent
@@ -691,7 +692,7 @@ private fun ProfileHeader(
.buttonColors(
containerColor = MaterialTheme.colorScheme.background
),
contentPadding = PaddingValues(0.dp)
contentPadding = ZeroPadding
) {
Icon(
tint = MaterialTheme.colorScheme.placeholderText,
@@ -1659,11 +1660,7 @@ private fun MessageButton(user: User, accountViewModel: AccountViewModel, nav: (
}
}
},
shape = ButtonBorder,
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.placeholderText
)
contentPadding = ZeroPadding
) {
Icon(
painter = painterResource(R.drawable.ic_dm),
@@ -1700,11 +1697,7 @@ private fun InnerEditButton(onClick: () -> Unit) {
.padding(horizontal = 3.dp)
.width(50.dp),
onClick = onClick,
shape = ButtonBorder,
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
contentPadding = ZeroPadding
) {
Icon(
tint = Color.White,
@@ -1723,7 +1716,7 @@ fun UnfollowButton(onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.unfollow), color = Color.White)
}
@@ -1738,7 +1731,7 @@ fun FollowButton(text: Int = R.string.follow, onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center)
}
@@ -1753,7 +1746,7 @@ fun ShowUserButton(onClick: () -> Unit) {
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
),
contentPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
contentPadding = ButtonPadding
) {
Text(text = stringResource(R.string.unblock), color = Color.White)
}
@@ -4,7 +4,6 @@ import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
@@ -65,6 +64,8 @@ import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.SearchIcon
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.FeedPadding
import com.vitorpamplona.amethyst.ui.theme.Size20Modifier
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import com.vitorpamplona.quartz.events.findHashtags
@@ -353,10 +354,7 @@ private fun DisplaySearchResults(
LazyColumn(
modifier = Modifier.fillMaxHeight(),
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
contentPadding = FeedPadding,
state = listState
) {
itemsIndexed(
@@ -438,7 +436,7 @@ fun HashtagLine(tag: String, onClick: () -> Unit) {
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -479,7 +477,7 @@ fun UserLine(
Divider(
modifier = Modifier.padding(top = 10.dp),
thickness = 0.25.dp
thickness = DividerThickness
)
}
}
@@ -6,22 +6,16 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@@ -29,11 +23,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.os.LocaleListCompat
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.ConnectivityType
@@ -43,7 +35,6 @@ import com.vitorpamplona.amethyst.model.parseConnectivityType
import com.vitorpamplona.amethyst.model.parseThemeType
import com.vitorpamplona.amethyst.ui.components.PushNotificationSettingsRow
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size20dp
@@ -142,6 +133,7 @@ fun SettingsScreen(
Column(
Modifier
.fillMaxSize()
.padding(top = Size10dp, start = Size20dp, end = Size20dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally
@@ -266,64 +258,3 @@ fun SettingsRow(
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DropDownSettings(
selectedItem: MutableState<String>,
listItems: Array<String>,
title: String
) {
var expanded by remember {
mutableStateOf(false)
}
ExposedDropdownMenuBox(
modifier = Modifier.padding(8.dp),
expanded = expanded,
onExpandedChange = {
expanded = !expanded
}
) {
TextField(
modifier = Modifier.fillMaxWidth(),
value = selectedItem.value,
onValueChange = {},
readOnly = true,
label = { Text(text = title) },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
listItems.forEach { selectedOption ->
DropdownMenuItem(
text = {
Text(text = selectedOption)
},
onClick = {
selectedItem.value = selectedOption
expanded = false
}
)
}
}
}
}
@Composable
fun Section(text: String) {
Spacer(modifier = DoubleVertSpacer)
Text(
text = text,
fontWeight = FontWeight.Bold,
fontSize = 20.sp
)
Spacer(modifier = DoubleVertSpacer)
}
@@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.theme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -57,6 +58,7 @@ val Size18dp = 18.dp
val Size19dp = 19.dp
val Size20dp = 20.dp
val Size22dp = 22.dp
val Size23dp = 23.dp
val Size24dp = 24.dp
val Size25dp = 25.dp
val Size30dp = 30.dp
@@ -142,3 +144,7 @@ val EditFieldTrailingIconModifier = Modifier
val EditFieldLeadingIconModifier = Modifier
.height(32.dp)
.padding(start = 2.dp)
val ZeroPadding = PaddingValues(0.dp)
val FeedPadding = PaddingValues(top = 10.dp, bottom = 10.dp)
val ButtonPadding = PaddingValues(vertical = 6.dp, horizontal = 16.dp)
+13
View File
@@ -146,6 +146,7 @@
<string name="login">Connexion</string>
<string name="generate_a_new_key">Générer une nouvelle clé</string>
<string name="loading_feed">Chargement du flux</string>
<string name="loading_account">Chargement du compte</string>
<string name="error_loading_replies">"Erreur lors du chargement des réponses : "</string>
<string name="try_again">Essayer à nouveau</string>
<string name="feed_is_empty">Le flux est vide.</string>
@@ -519,7 +520,9 @@
<string name="error_dialog_pay_invoice_error">Impossible de payer la facture</string>
<string name="error_dialog_pay_withdraw_error">Impossible de retirer</string>
<string name="error_parsing_nip47_title">Impossible de configurer Wallet Connect</string>
<string name="cashu_sucessful_redemption">Cashu reçu</string>
<string name="cashu_sucessful_redemption_explainer">%1$s sats ont été envoyés à votre portefeuille. (Frais : %2$s sats)</string>
<string name="error_unable_to_fetch_invoice">Impossible de récupérer la facture depuis les serveurs du destinataire</string>
<string name="could_not_connect_to_tor">Impossible de se connecter à Tor</string>
<string name="unable_to_download_relay_document">Téléchargement du document relais indisponible</string>
<string name="could_not_assemble_lnurl_from_lightning_address_check_the_user_s_setup">Impossible d\'assembler LNUrl à partir de l\'adresse Lightning \"%1$s\". Vérifiez la configuration de l\'utilisateur</string>
@@ -534,4 +537,14 @@
<string name="unable_to_create_a_lightning_invoice_before_sending_the_zap_element_pr_not_found_in_the_resulting_json">Impossible de créer une facture lightning avant d\'envoyer le zap. Élément pr introuvable dans le JSON résultant.</string>
<string name="read_only_user">Utilisateur en lecture seule</string>
<string name="no_reactions_setup">Aucune réaction configurée</string>
<string name="select_push_server">Sélectionnez une application UnifiedPush</string>
<string name="push_server_title">Notification Push</string>
<string name="push_server_explainer">Depuis les applications UnifiedPush installées</string>
<string name="push_server_none">Aucun</string>
<string name="push_server_none_explainer">Désactive les Notifications Push</string>
<string name="push_server_uses_app_explainer">Utilise l\'application %1$s</string>
<string name="push_server_install_app">Configuration des Notifications Push</string>
<string name="push_server_install_app_description"> Pour recevoir des notifications push, installez n\'importe quelle application qui supporte les [Unified Push](https://unifiedpush.org/), tel que [Nfty](https://ntfy.sh/).
Après installation, sélectionnez l\'application que vous voulez utiliser dans les Paramètres.
</string>
</resources>
+15 -5
View File
@@ -59,7 +59,7 @@
<string name="show_more">Meer</string>
<string name="lightning_invoice">Lightning invoice</string>
<string name="pay">Betalen</string>
<string name="lightning_tips">Lighnting tips</string>
<string name="lightning_tips">Lightning tips</string>
<string name="note_to_receiver">Note aan ontvanger</string>
<string name="thank_you_so_much">Hartelijk bedankt!</string>
<string name="amount_in_sats">Bedrag in sats</string>
@@ -110,7 +110,7 @@
<string name="join">Lid worden</string>
<string name="known">Bekend</string>
<string name="new_requests">Nieuw verzoek</string>
<string name="blocked_users">Geblokeerde gebruikers</string>
<string name="blocked_users">Geblokkeerde gebruikers</string>
<string name="new_threads">Nieuwe notes</string>
<string name="conversations">Gesprekken</string>
<string name="notes">Notes</string>
@@ -234,7 +234,7 @@
<string name="report_dialog_spam">Spam of oplichting</string>
<string name="report_dialog_profanity">Godslastering of haatdragend gedrag</string>
<string name="report_dialog_impersonation">Kwaadwillige imitatie</string>
<string name="report_dialog_nudity">Naaktheid of grafische inhoud</string>
<string name="report_dialog_nudity">Naaktheid of gevoelige inhoud</string>
<string name="report_dialog_illegal">Illegaal gedrag</string>
<string name="report_dialog_blocking_a_user">Als u een gebruiker blokkeert, wordt zijn inhoud in uw app verborgen. Uw notes zijn nog steeds publiekelijk zichtbaar, ook voor mensen die u blokkeert. Geblokkeerde gebruikers worden vermeld in het scherm Beveiligingsfilters.</string>
<string name="report_dialog_block_hide_user_btn"><![CDATA[Blokkeer & verberg gebruiker]]></string>
@@ -312,7 +312,7 @@
<string name="upload_server_nostrimg">nostrimg.com - vertrouwd</string>
<string name="upload_server_nostrimg_explainer">NostrImg kan het bestand aanpassen</string>
<string name="upload_server_nostrbuild">nostr.build - vertrouwd</string>
<string name="upload_server_nostrbuild_explainer">Nostr.build kan het bestand aanpassen</string>
<string name="upload_server_nostrbuild_explainer">Nostr.build kan mogelijk het bestand aanpassen</string>
<string name="upload_server_nostrfilesdev">nostrfiles.dev - vertrouwd</string>
<string name="upload_server_nostrfilesdev_explainer">Nostrfiles.dev kan het bestand aanpassen</string>
<string name="upload_server_nostrcheckme">nostrcheck.me - vertrouwd</string>
@@ -365,7 +365,7 @@
<string name="channel_list_join_channel">Deelnemen</string>
<string name="today">Vandaag</string>
<string name="content_warning">Content waarschuwing</string>
<string name="content_warning_explanation">Dit bericht bevat gevoelige inhoud die sommige mensen beledigend of verontrustend kunnen vinden</string>
<string name="content_warning_explanation">Dit bericht bevat mogelijk beledigend of verontrustend, gevoelige inhoud</string>
<string name="content_warning_hide_all_sensitive_content">Verberg altijd gevoelige inhoud</string>
<string name="content_warning_show_all_sensitive_content">Toon altijd gevoelige inhoud</string>
<string name="content_warning_see_warnings">Toon altijd content waarshuwingen</string>
@@ -543,4 +543,14 @@
<string name="unable_to_create_a_lightning_invoice_before_sending_the_zap_element_pr_not_found_in_the_resulting_json">Kan geen Linghtning invoice aanmaken voor het verzenden van de zap. Element pr niet gevonden in de resulterende JSON.</string>
<string name="read_only_user">Read-only gebruiker</string>
<string name="no_reactions_setup">Geen reacties setup</string>
<string name="select_push_server">Selecteer een UnifiedPush App</string>
<string name="push_server_title">Push-notificatie</string>
<string name="push_server_explainer">Van geïnstalleerde UnifiedPush apps</string>
<string name="push_server_none">Geen</string>
<string name="push_server_none_explainer">Push-notificaties uitschakelen</string>
<string name="push_server_uses_app_explainer">Gebruikt app %1$s</string>
<string name="push_server_install_app">Instellen Push-notificaties</string>
<string name="push_server_install_app_description"> Om push-notificaties te ontvangen, installeer een app die [Unified Push] ondersteunt](https://unifiedpush.org/), zoals [Nfty](https://ntfy.sh/).
Na installatie selecteer de app die je wilt gebruiken in de instellingen.
</string>
</resources>
@@ -59,4 +59,5 @@ class AudioTrackEvent(
}
}
@Immutable
data class Participant(val key: String, val role: String?)
@@ -43,10 +43,10 @@ class TextNoteEvent(
): TextNoteEvent {
val tags = mutableListOf<List<String>>()
replyTos?.forEach {
if (it == replyingTo) {
tags.add(listOf("e", it, "", "reply"))
} else if (it == root) {
if (it == root) {
tags.add(listOf("e", it, "", "root"))
} else if (it == replyingTo) {
tags.add(listOf("e", it, "", "reply"))
} else if (it in directMentions) {
tags.add(listOf("e", it, "", "mention"))
} else {
@@ -62,10 +62,10 @@ class TextNoteEvent(
}
addresses?.forEach {
val aTag = it.toTag()
if (aTag == replyingTo) {
tags.add(listOf("a", aTag, "", "reply"))
} else if (aTag == root) {
if (aTag == root) {
tags.add(listOf("a", aTag, "", "root"))
} else if (aTag == replyingTo) {
tags.add(listOf("a", aTag, "", "reply"))
} else if (aTag in directMentions) {
tags.add(listOf("a", aTag, "", "mention"))
} else {