mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-04 22:04:38 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ec6e6d955 | ||
|
|
6b7ec2caa9 | ||
|
|
17130c81c8 | ||
|
|
0571312dca | ||
|
|
717c3bc45e | ||
|
|
c304f7c69d | ||
|
|
6d2a1ec1b9 | ||
|
|
59f7f5257b | ||
|
|
ba1f2dc17d | ||
|
|
820def620c | ||
|
|
9fef3e9e3e | ||
|
|
e8998a26d7 | ||
|
|
221ab1ca1a | ||
|
|
2f1e67779b | ||
|
|
96663b822c | ||
|
|
c7895d1cdd | ||
|
|
85762c581a | ||
|
|
e189a4d68d | ||
|
|
3306dd88d2 | ||
|
|
18952bc8a7 | ||
|
|
f6cfba6e1f | ||
|
|
fb37ccd14f | ||
|
|
3858c57816 | ||
|
|
572c11e847 | ||
|
|
bd70bd708b | ||
|
|
9a2ecfc85d | ||
|
|
f20fd1aebe | ||
|
|
2f2914077b | ||
|
|
712d498788 |
+2
-2
@@ -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]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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(
|
||||
|
||||
+114
-37
@@ -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 ->
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
+33
-2
@@ -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) {
|
||||
|
||||
+47
-56
@@ -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)
|
||||
}
|
||||
|
||||
+49
-53
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+73
-76
@@ -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(
|
||||
|
||||
+12
-15
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user