mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-29 08:20:51 +00:00
Trying to improve rendering by allowing recompositions to perform layout faster.
This commit is contained in:
parent
8fcd05a6bb
commit
b1de36f423
@ -1,6 +1,7 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.components
|
package com.vitorpamplona.amethyst.ui.components
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
@ -30,24 +31,31 @@ fun RobohashAsyncImage(
|
|||||||
colorFilter: ColorFilter? = null,
|
colorFilter: ColorFilter? = null,
|
||||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
|
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
|
||||||
) {
|
) {
|
||||||
with(LocalDensity.current) {
|
val context = LocalContext.current
|
||||||
AsyncImage(
|
val size = with(LocalDensity.current) {
|
||||||
model = Robohash.imageRequest(
|
robotSize.roundToPx()
|
||||||
LocalContext.current,
|
}
|
||||||
robot,
|
|
||||||
Size(robotSize.roundToPx(), robotSize.roundToPx())
|
val imageRequest = remember(size, robot) {
|
||||||
),
|
Robohash.imageRequest(
|
||||||
contentDescription = contentDescription,
|
context,
|
||||||
modifier = modifier,
|
robot,
|
||||||
transform = transform,
|
Size(size, size)
|
||||||
onState = onState,
|
|
||||||
alignment = alignment,
|
|
||||||
contentScale = contentScale,
|
|
||||||
alpha = alpha,
|
|
||||||
colorFilter = colorFilter,
|
|
||||||
filterQuality = filterQuality
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
model = imageRequest,
|
||||||
|
contentDescription = contentDescription,
|
||||||
|
modifier = modifier,
|
||||||
|
transform = transform,
|
||||||
|
onState = onState,
|
||||||
|
alignment = alignment,
|
||||||
|
contentScale = contentScale,
|
||||||
|
alpha = alpha,
|
||||||
|
colorFilter = colorFilter,
|
||||||
|
filterQuality = filterQuality
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var imageErrors = setOf<String>()
|
var imageErrors = setOf<String>()
|
||||||
@ -120,7 +128,7 @@ fun RobohashAsyncImageProxy(
|
|||||||
colorFilter: ColorFilter? = null,
|
colorFilter: ColorFilter? = null,
|
||||||
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
|
filterQuality: FilterQuality = DrawScope.DefaultFilterQuality
|
||||||
) {
|
) {
|
||||||
val proxy = model.proxyUrl()
|
val proxy = remember(model) { model.proxyUrl() }
|
||||||
if (proxy == null) {
|
if (proxy == null) {
|
||||||
RobohashAsyncImage(
|
RobohashAsyncImage(
|
||||||
robot = robot,
|
robot = robot,
|
||||||
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.combinedClickable
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
@ -34,18 +35,18 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by messageSetCard.note.live().metadata.observeAsState()
|
val noteState by messageSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = remember(noteState) { noteState?.note }
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
BlankNote(Modifier, isInnerNote)
|
BlankNote(Modifier)
|
||||||
} else {
|
} else {
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
LaunchedEffect(key1 = messageSetCard.createdAt()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@ -66,44 +67,40 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = fal
|
|||||||
MaterialTheme.colors.background
|
MaterialTheme.colors.background
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
val columnModifier = remember {
|
||||||
modifier = Modifier.background(backgroundColor).combinedClickable(
|
Modifier
|
||||||
onClick = {
|
.background(backgroundColor)
|
||||||
scope.launch {
|
.padding(
|
||||||
routeFor(
|
start = 12.dp,
|
||||||
note,
|
end = 12.dp,
|
||||||
accountViewModel.userProfile()
|
top = 10.dp
|
||||||
)?.let { navController.navigate(it) }
|
)
|
||||||
}
|
.combinedClickable(
|
||||||
},
|
onClick = {
|
||||||
onLongClick = { popupExpanded = true }
|
scope.launch {
|
||||||
)
|
routeFor(
|
||||||
) {
|
note,
|
||||||
Row(
|
accountViewModel.userProfile()
|
||||||
modifier = Modifier
|
)?.let { navController.navigate(it) }
|
||||||
.padding(
|
}
|
||||||
start = if (!isInnerNote) 12.dp else 0.dp,
|
},
|
||||||
end = if (!isInnerNote) 12.dp else 0.dp,
|
onLongClick = { popupExpanded = true }
|
||||||
top = 10.dp
|
)
|
||||||
|
.fillMaxWidth()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(columnModifier) {
|
||||||
|
Row(Modifier.fillMaxWidth()) {
|
||||||
|
Box(modifier = remember { Modifier.width(55.dp).padding(top = 5.dp, end = 5.dp) }) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_dm),
|
||||||
|
null,
|
||||||
|
modifier = remember { Modifier.size(16.dp).align(Alignment.TopEnd) },
|
||||||
|
tint = MaterialTheme.colors.primary
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
// Draws the like picture outside the boosted card.
|
|
||||||
if (!isInnerNote) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(55.dp)
|
|
||||||
.padding(top = 5.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_dm),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(16.dp).align(Alignment.TopEnd),
|
|
||||||
tint = MaterialTheme.colors.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
|
Column(modifier = remember { Modifier.padding(start = 10.dp) }) {
|
||||||
NoteCompose(
|
NoteCompose(
|
||||||
baseNote = messageSetCard.note,
|
baseNote = messageSetCard.note,
|
||||||
routeForLastRead = null,
|
routeForLastRead = null,
|
||||||
|
@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons
|
|||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -40,6 +41,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapRequestEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
|
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
|
||||||
@ -52,19 +54,19 @@ import kotlinx.coroutines.launch
|
|||||||
@Composable
|
@Composable
|
||||||
fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val noteState by multiSetCard.note.live().metadata.observeAsState()
|
val noteState by multiSetCard.note.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = remember(noteState) { noteState?.note }
|
||||||
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = remember(accountState) { accountState?.account }
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null || account == null) {
|
||||||
BlankNote(Modifier, false)
|
BlankNote(Modifier, false)
|
||||||
} else {
|
} else {
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
var isNew by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(key1 = multiSetCard.createdAt()) {
|
LaunchedEffect(key1 = multiSetCard.createdAt()) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
@ -84,112 +86,157 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
|||||||
MaterialTheme.colors.background
|
MaterialTheme.colors.background
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
val columnModifier = remember {
|
||||||
modifier = Modifier
|
Modifier
|
||||||
.background(backgroundColor)
|
.background(backgroundColor)
|
||||||
|
.padding(
|
||||||
|
start = 12.dp,
|
||||||
|
end = 12.dp,
|
||||||
|
top = 10.dp
|
||||||
|
)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
routeFor(note, account.userProfile())?.let { navController.navigate(it) }
|
routeFor(
|
||||||
|
note,
|
||||||
|
account.userProfile()
|
||||||
|
)?.let { navController.navigate(it) }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongClick = { popupExpanded = true }
|
onLongClick = { popupExpanded = true }
|
||||||
)
|
)
|
||||||
) {
|
.fillMaxWidth()
|
||||||
Row(
|
}
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
|
||||||
start = 12.dp,
|
|
||||||
end = 12.dp,
|
|
||||||
top = 10.dp
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Column(Modifier.fillMaxWidth()) {
|
|
||||||
if (multiSetCard.zapEvents.isNotEmpty()) {
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
|
||||||
// Draws the like picture outside the boosted card.
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(55.dp)
|
|
||||||
.padding(0.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Bolt,
|
|
||||||
contentDescription = "Zaps",
|
|
||||||
tint = BitcoinOrange,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(25.dp)
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorGalleryZaps(multiSetCard.zapEvents, navController, account, accountViewModel)
|
val zapEvents = remember { multiSetCard.zapEvents }
|
||||||
}
|
val boostEvents = remember { multiSetCard.boostEvents }
|
||||||
}
|
val likeEvents = remember { multiSetCard.likeEvents }
|
||||||
|
|
||||||
if (multiSetCard.boostEvents.isNotEmpty()) {
|
Column(modifier = columnModifier) {
|
||||||
Row(Modifier.fillMaxWidth()) {
|
if (zapEvents.isNotEmpty()) {
|
||||||
Box(
|
RenderZapGallery(zapEvents, navController, account, accountViewModel)
|
||||||
modifier = Modifier
|
}
|
||||||
.width(55.dp)
|
|
||||||
.padding(end = 4.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_retweeted),
|
|
||||||
null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(18.dp)
|
|
||||||
.align(Alignment.TopEnd),
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorGallery(multiSetCard.boostEvents, navController, account, accountViewModel)
|
if (boostEvents.isNotEmpty()) {
|
||||||
}
|
RenderBoostGallery(boostEvents, navController, account, accountViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiSetCard.likeEvents.isNotEmpty()) {
|
if (likeEvents.isNotEmpty()) {
|
||||||
Row(Modifier.fillMaxWidth()) {
|
RenderLikeGallery(likeEvents, navController, account, accountViewModel)
|
||||||
Box(
|
}
|
||||||
modifier = Modifier
|
|
||||||
.width(55.dp)
|
|
||||||
.padding(end = 5.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_liked),
|
|
||||||
null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(16.dp)
|
|
||||||
.align(Alignment.TopEnd),
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorGallery(multiSetCard.likeEvents, navController, account, accountViewModel)
|
Row(Modifier.fillMaxWidth()) {
|
||||||
}
|
Spacer(modifier = Modifier.width(65.dp))
|
||||||
}
|
|
||||||
|
|
||||||
Row(Modifier.fillMaxWidth()) {
|
NoteCompose(
|
||||||
Spacer(modifier = Modifier.width(65.dp))
|
baseNote = multiSetCard.note,
|
||||||
|
routeForLastRead = null,
|
||||||
|
modifier = Modifier.padding(top = 5.dp),
|
||||||
|
isBoostedNote = true,
|
||||||
|
parentBackgroundColor = backgroundColor,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
|
||||||
NoteCompose(
|
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||||
baseNote = multiSetCard.note,
|
|
||||||
routeForLastRead = null,
|
|
||||||
modifier = Modifier.padding(top = 5.dp),
|
|
||||||
isBoostedNote = true,
|
|
||||||
parentBackgroundColor = backgroundColor,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
|
|
||||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderLikeGallery(
|
||||||
|
likeEvents: List<Note>,
|
||||||
|
navController: NavController,
|
||||||
|
account: Account,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
Row(Modifier.fillMaxWidth()) {
|
||||||
|
Box(
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.width(55.dp)
|
||||||
|
.padding(end = 5.dp)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
|
null,
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
},
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorGallery(likeEvents, navController, account, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderZapGallery(
|
||||||
|
zapEvents: Map<Note, Note>,
|
||||||
|
navController: NavController,
|
||||||
|
account: Account,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
Row(Modifier.fillMaxWidth()) {
|
||||||
|
Box(
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.width(55.dp)
|
||||||
|
.padding(0.dp)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Bolt,
|
||||||
|
contentDescription = "Zaps",
|
||||||
|
tint = BitcoinOrange,
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.size(25.dp)
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorGalleryZaps(zapEvents, navController, account, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderBoostGallery(
|
||||||
|
boostEvents: List<Note>,
|
||||||
|
navController: NavController,
|
||||||
|
account: Account,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
Row(Modifier.fillMaxWidth()) {
|
||||||
|
Box(
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.width(55.dp)
|
||||||
|
.padding(end = 4.dp)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_retweeted),
|
||||||
|
null,
|
||||||
|
modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.size(18.dp)
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
},
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthorGallery(boostEvents, navController, account, accountViewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AuthorGalleryZaps(
|
fun AuthorGalleryZaps(
|
||||||
authorNotes: Map<Note, Note>,
|
authorNotes: Map<Note, Note>,
|
||||||
@ -197,13 +244,16 @@ fun AuthorGalleryZaps(
|
|||||||
account: Account,
|
account: Account,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val accountState by account.userProfile().live().follows.observeAsState()
|
val accountState = account.userProfile().live().follows.observeAsState()
|
||||||
val accountUser = accountState?.user ?: return
|
|
||||||
|
val listToRender = remember {
|
||||||
|
authorNotes.keys.take(50)
|
||||||
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
FlowRow() {
|
FlowRow() {
|
||||||
authorNotes.forEach {
|
listToRender.forEach {
|
||||||
AuthorPictureAndComment(it.key, navController, accountUser, accountViewModel)
|
AuthorPictureAndComment(it, navController, accountState, accountViewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +263,7 @@ fun AuthorGalleryZaps(
|
|||||||
private fun AuthorPictureAndComment(
|
private fun AuthorPictureAndComment(
|
||||||
zapRequest: Note,
|
zapRequest: Note,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
accountUser: User,
|
accountUser: State<UserState?>,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val author = zapRequest.author ?: return
|
val author = zapRequest.author ?: return
|
||||||
@ -245,19 +295,25 @@ private fun AuthorPictureAndComment(
|
|||||||
author: User,
|
author: User,
|
||||||
comment: String?,
|
comment: String?,
|
||||||
navController: NavController,
|
navController: NavController,
|
||||||
accountUser: User,
|
accountUser: State<UserState?>,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val modifier = if (!comment.isNullOrBlank()) {
|
val modifier = remember(comment) {
|
||||||
Modifier.fillMaxWidth()
|
if (!comment.isNullOrBlank()) {
|
||||||
} else {
|
Modifier
|
||||||
Modifier
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
navController.navigate("User/${author.pubkeyHex}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Modifier.clickable {
|
||||||
|
navController.navigate("User/${author.pubkeyHex}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier.clickable {
|
modifier = modifier,
|
||||||
navController.navigate("User/${author.pubkeyHex}")
|
|
||||||
},
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
FastNoteAuthorPicture(
|
FastNoteAuthorPicture(
|
||||||
@ -288,20 +344,22 @@ fun AuthorGallery(
|
|||||||
account: Account,
|
account: Account,
|
||||||
accountViewModel: AccountViewModel
|
accountViewModel: AccountViewModel
|
||||||
) {
|
) {
|
||||||
val accountState by account.userProfile().live().follows.observeAsState()
|
val accountState = account.userProfile().live().follows.observeAsState()
|
||||||
val accountUser = accountState?.user ?: return
|
val listToRender = remember {
|
||||||
|
Pair(
|
||||||
|
authorNotes.take(50).mapNotNull { it.author },
|
||||||
|
authorNotes.size
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||||
FlowRow() {
|
FlowRow() {
|
||||||
authorNotes.take(50).forEach {
|
listToRender.first.forEach { author ->
|
||||||
val author = it.author
|
AuthorPictureAndComment(author, null, navController, accountState, accountViewModel)
|
||||||
if (author != null) {
|
|
||||||
AuthorPictureAndComment(author, null, navController, accountUser, accountViewModel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authorNotes.size > 50) {
|
if (listToRender.second > 50) {
|
||||||
Text(" and ${authorNotes.size - 50} others")
|
Text(" and ${listToRender.second - 50} others")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -310,18 +368,26 @@ fun AuthorGallery(
|
|||||||
@Composable
|
@Composable
|
||||||
fun FastNoteAuthorPicture(
|
fun FastNoteAuthorPicture(
|
||||||
author: User,
|
author: User,
|
||||||
userAccount: User,
|
userAccount: State<UserState?>,
|
||||||
size: Dp,
|
size: Dp,
|
||||||
pictureModifier: Modifier = Modifier
|
pictureModifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val userState by author.live().metadata.observeAsState()
|
val userState by author.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
val profilePicture = remember(userState) {
|
||||||
|
userState?.user?.profilePicture()
|
||||||
|
}
|
||||||
|
|
||||||
val showFollowingMark = userAccount.isFollowingCached(user) || user === userAccount
|
val authorPubKey = remember {
|
||||||
|
author.pubkeyHex
|
||||||
|
}
|
||||||
|
|
||||||
|
val showFollowingMark = remember(userAccount.value) {
|
||||||
|
userAccount.value?.user?.isFollowingCached(author) == true || (author === userAccount.value?.user)
|
||||||
|
}
|
||||||
|
|
||||||
UserPicture(
|
UserPicture(
|
||||||
userHex = user.pubkeyHex,
|
userHex = authorPubKey,
|
||||||
userPicture = user.profilePicture(),
|
userPicture = profilePicture,
|
||||||
showFollowingMark = showFollowingMark,
|
showFollowingMark = showFollowingMark,
|
||||||
size = size,
|
size = size,
|
||||||
modifier = pictureModifier
|
modifier = pictureModifier
|
||||||
|
@ -96,6 +96,7 @@ import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
|
import com.vitorpamplona.amethyst.service.model.ReportedKey
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
import com.vitorpamplona.amethyst.ui.components.ClickableUrl
|
||||||
@ -178,208 +179,226 @@ fun NoteComposeInner(
|
|||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
val loggedIn = account.userProfile()
|
val loggedIn = remember(accountState) { accountState?.account?.userProfile() } ?: return
|
||||||
|
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note
|
val note = remember(noteState) { noteState?.note }
|
||||||
|
|
||||||
val noteReportsState by baseNote.live().reports.observeAsState()
|
val noteReportsState by baseNote.live().reports.observeAsState()
|
||||||
val noteForReports = noteReportsState?.note ?: return
|
val noteForReports = remember(noteReportsState) { noteReportsState?.note } ?: return
|
||||||
|
|
||||||
var popupExpanded by remember { mutableStateOf(false) }
|
|
||||||
var showHiddenNote by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteReportsState) {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val newCanPreview = note?.author === loggedIn ||
|
|
||||||
(note?.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
|
||||||
!noteForReports.hasAnyReports()
|
|
||||||
|
|
||||||
val newIsAcceptable = account.isAcceptable(noteForReports)
|
|
||||||
|
|
||||||
if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) {
|
|
||||||
isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val noteEvent = note?.event
|
val noteEvent = note?.event
|
||||||
val baseChannel = note?.channel()
|
val baseChannel = note?.channel()
|
||||||
|
|
||||||
|
var popupExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
if (noteEvent == null) {
|
if (noteEvent == null) {
|
||||||
BlankNote(
|
BlankNote(
|
||||||
modifier.combinedClickable(
|
remember {
|
||||||
onClick = { },
|
modifier.combinedClickable(
|
||||||
onLongClick = { popupExpanded = true }
|
onClick = { },
|
||||||
),
|
|
||||||
isBoostedNote
|
|
||||||
)
|
|
||||||
} else if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
|
|
||||||
if (!account.isHidden(noteForReports.author!!)) {
|
|
||||||
HiddenNote(
|
|
||||||
account.getRelevantReports(noteForReports),
|
|
||||||
loggedIn,
|
|
||||||
modifier,
|
|
||||||
isBoostedNote,
|
|
||||||
navController,
|
|
||||||
onClick = { showHiddenNote = true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) {
|
|
||||||
ChannelHeader(baseChannel = baseChannel, account = account, navController = navController)
|
|
||||||
} else if (noteEvent is BadgeDefinitionEvent) {
|
|
||||||
BadgeDisplay(baseNote = note)
|
|
||||||
} else if (noteEvent is FileHeaderEvent) {
|
|
||||||
FileHeaderDisplay(note)
|
|
||||||
} else if (noteEvent is FileStorageHeaderEvent) {
|
|
||||||
FileStorageHeaderDisplay(note)
|
|
||||||
} else {
|
|
||||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = routeForLastRead) {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
routeForLastRead?.let {
|
|
||||||
val lastTime = NotificationCache.load(it)
|
|
||||||
|
|
||||||
val createdAt = note.createdAt()
|
|
||||||
if (createdAt != null) {
|
|
||||||
NotificationCache.markAsRead(it, createdAt)
|
|
||||||
|
|
||||||
val newIsNew = createdAt > lastTime
|
|
||||||
if (newIsNew != isNew) {
|
|
||||||
isNew = newIsNew
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val backgroundColor = if (isNew) {
|
|
||||||
val newColor = MaterialTheme.colors.primary.copy(0.12f)
|
|
||||||
if (parentBackgroundColor != null) {
|
|
||||||
newColor.compositeOver(parentBackgroundColor)
|
|
||||||
} else {
|
|
||||||
newColor.compositeOver(MaterialTheme.colors.background)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
parentBackgroundColor ?: MaterialTheme.colors.background
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
routeFor(note, loggedIn)?.let { navController.navigate(it) }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongClick = { popupExpanded = true }
|
onLongClick = { popupExpanded = true }
|
||||||
)
|
)
|
||||||
.background(backgroundColor)
|
},
|
||||||
) {
|
isBoostedNote
|
||||||
Row(
|
)
|
||||||
modifier = Modifier
|
|
||||||
.padding(
|
note?.let {
|
||||||
start = if (!isBoostedNote) 12.dp else 0.dp,
|
NoteQuickActionMenu(it, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||||
end = if (!isBoostedNote) 12.dp else 0.dp,
|
}
|
||||||
top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp
|
} else {
|
||||||
)
|
var showHiddenNote by remember { mutableStateOf(false) }
|
||||||
) {
|
var isAcceptableAndCanPreview by remember { mutableStateOf(Pair(true, true)) }
|
||||||
if (!isBoostedNote && !isQuotedNote) {
|
|
||||||
DrawAuthorImages(baseNote, loggedIn, navController)
|
LaunchedEffect(key1 = noteReportsState, key2 = accountState) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
account.userProfile().let { loggedIn ->
|
||||||
|
val newCanPreview = note.author === loggedIn ||
|
||||||
|
(note.author?.let { loggedIn.isFollowingCached(it) } ?: true) ||
|
||||||
|
!(noteForReports.hasAnyReports())
|
||||||
|
|
||||||
|
val newIsAcceptable = account.isAcceptable(noteForReports)
|
||||||
|
|
||||||
|
if (newIsAcceptable != isAcceptableAndCanPreview.first && newCanPreview != isAcceptableAndCanPreview.second) {
|
||||||
|
isAcceptableAndCanPreview = Pair(newIsAcceptable, newCanPreview)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
if (!isAcceptableAndCanPreview.first && !showHiddenNote) {
|
||||||
modifier = Modifier
|
if (!account.isHidden(noteForReports.author!!)) {
|
||||||
.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
|
HiddenNote(
|
||||||
) {
|
account.getRelevantReports(noteForReports),
|
||||||
FirstUserInfoRow(
|
account.userProfile(),
|
||||||
baseNote = baseNote,
|
modifier,
|
||||||
showAuthorPicture = isQuotedNote,
|
isBoostedNote,
|
||||||
account = account,
|
navController,
|
||||||
accountViewModel = accountViewModel,
|
onClick = { showHiddenNote = true }
|
||||||
navController = navController
|
)
|
||||||
|
}
|
||||||
|
} else if ((noteEvent is ChannelCreateEvent || noteEvent is ChannelMetadataEvent) && baseChannel != null) {
|
||||||
|
ChannelHeader(baseChannel = baseChannel, account = account, navController = navController)
|
||||||
|
} else if (noteEvent is BadgeDefinitionEvent) {
|
||||||
|
BadgeDisplay(baseNote = note)
|
||||||
|
} else if (noteEvent is FileHeaderEvent) {
|
||||||
|
FileHeaderDisplay(note)
|
||||||
|
} else if (noteEvent is FileStorageHeaderEvent) {
|
||||||
|
FileStorageHeaderDisplay(note)
|
||||||
|
} else {
|
||||||
|
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = routeForLastRead) {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
routeForLastRead?.let {
|
||||||
|
val lastTime = NotificationCache.load(it)
|
||||||
|
|
||||||
|
val createdAt = note.createdAt()
|
||||||
|
if (createdAt != null) {
|
||||||
|
NotificationCache.markAsRead(it, createdAt)
|
||||||
|
|
||||||
|
val newIsNew = createdAt > lastTime
|
||||||
|
if (newIsNew != isNew) {
|
||||||
|
isNew = newIsNew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val backgroundColor = if (isNew) {
|
||||||
|
val newColor = MaterialTheme.colors.primary.copy(0.12f)
|
||||||
|
if (parentBackgroundColor != null) {
|
||||||
|
newColor.compositeOver(parentBackgroundColor)
|
||||||
|
} else {
|
||||||
|
newColor.compositeOver(MaterialTheme.colors.background)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parentBackgroundColor ?: MaterialTheme.colors.background
|
||||||
|
}
|
||||||
|
|
||||||
|
val columnModifier = remember(backgroundColor) {
|
||||||
|
modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
routeFor(note, loggedIn)?.let {
|
||||||
|
navController.navigate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongClick = { popupExpanded = true }
|
||||||
)
|
)
|
||||||
|
.background(backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
|
Column(modifier = columnModifier) {
|
||||||
SecondUserInfoRow(
|
Row(
|
||||||
note,
|
modifier = remember {
|
||||||
account,
|
Modifier
|
||||||
navController
|
.padding(
|
||||||
)
|
start = if (!isBoostedNote) 12.dp else 0.dp,
|
||||||
|
end = if (!isBoostedNote) 12.dp else 0.dp,
|
||||||
|
top = if (addMarginTop && !isBoostedNote) 10.dp else 0.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (!isBoostedNote && !isQuotedNote) {
|
||||||
|
DrawAuthorImages(baseNote, loggedIn, navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Column(
|
||||||
|
modifier = remember {
|
||||||
if (!makeItShort) {
|
Modifier
|
||||||
ReplyRow(
|
.padding(start = if (!isBoostedNote && !isQuotedNote) 10.dp else 0.dp)
|
||||||
note,
|
}
|
||||||
unPackReply,
|
) {
|
||||||
backgroundColor,
|
FirstUserInfoRow(
|
||||||
account,
|
baseNote = baseNote,
|
||||||
accountViewModel,
|
showAuthorPicture = isQuotedNote,
|
||||||
navController
|
account = account,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
navController = navController
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
when (noteEvent) {
|
if (noteEvent !is RepostEvent && !makeItShort && !isQuotedNote) {
|
||||||
is ReactionEvent -> {
|
SecondUserInfoRow(
|
||||||
RenderReaction(note, backgroundColor, accountViewModel, navController)
|
|
||||||
}
|
|
||||||
|
|
||||||
is RepostEvent -> {
|
|
||||||
RenderRepost(note, backgroundColor, accountViewModel, navController)
|
|
||||||
}
|
|
||||||
|
|
||||||
is ReportEvent -> {
|
|
||||||
RenderReport(note)
|
|
||||||
}
|
|
||||||
|
|
||||||
is LongTextNoteEvent -> {
|
|
||||||
RenderLongFormContent(note, loggedIn, accountViewModel, navController)
|
|
||||||
}
|
|
||||||
|
|
||||||
is BadgeAwardEvent -> {
|
|
||||||
RenderBadgeAward(note, backgroundColor, accountViewModel, navController)
|
|
||||||
}
|
|
||||||
|
|
||||||
is PrivateDmEvent -> {
|
|
||||||
RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController)
|
|
||||||
}
|
|
||||||
|
|
||||||
is HighlightEvent -> {
|
|
||||||
RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController)
|
|
||||||
}
|
|
||||||
|
|
||||||
is PollNoteEvent -> {
|
|
||||||
RenderPoll(
|
|
||||||
note,
|
note,
|
||||||
makeItShort,
|
account,
|
||||||
isAcceptableAndCanPreview.second,
|
navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(2.dp))
|
||||||
|
|
||||||
|
if (!makeItShort) {
|
||||||
|
ReplyRow(
|
||||||
|
note,
|
||||||
|
unPackReply,
|
||||||
backgroundColor,
|
backgroundColor,
|
||||||
|
account,
|
||||||
accountViewModel,
|
accountViewModel,
|
||||||
navController
|
navController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
when (noteEvent) {
|
||||||
RenderTextEvent(
|
is ReactionEvent -> {
|
||||||
note,
|
RenderReaction(note, backgroundColor, accountViewModel, navController)
|
||||||
makeItShort,
|
}
|
||||||
isAcceptableAndCanPreview.second,
|
|
||||||
backgroundColor,
|
|
||||||
accountViewModel,
|
|
||||||
navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
is RepostEvent -> {
|
||||||
|
RenderRepost(note, backgroundColor, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
is ReportEvent -> {
|
||||||
|
RenderReport(note)
|
||||||
|
}
|
||||||
|
|
||||||
|
is LongTextNoteEvent -> {
|
||||||
|
RenderLongFormContent(note, loggedIn, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
is BadgeAwardEvent -> {
|
||||||
|
RenderBadgeAward(note, backgroundColor, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
is PrivateDmEvent -> {
|
||||||
|
RenderPrivateMessage(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
is HighlightEvent -> {
|
||||||
|
RenderHighlight(note, makeItShort, isAcceptableAndCanPreview.second, backgroundColor, accountViewModel, navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
is PollNoteEvent -> {
|
||||||
|
RenderPoll(
|
||||||
|
note,
|
||||||
|
makeItShort,
|
||||||
|
isAcceptableAndCanPreview.second,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
RenderTextEvent(
|
||||||
|
note,
|
||||||
|
makeItShort,
|
||||||
|
isAcceptableAndCanPreview.second,
|
||||||
|
backgroundColor,
|
||||||
|
accountViewModel,
|
||||||
|
navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,13 +443,14 @@ private fun RenderTextEvent(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event ?: return
|
val tags = remember { note.event?.tags() }
|
||||||
|
val hashtags = remember { note.event?.hashtags() ?: emptyList() }
|
||||||
val eventContent = remember { accountViewModel.decrypt(note) }
|
val eventContent = remember { accountViewModel.decrypt(note) }
|
||||||
val modifier = remember { Modifier.fillMaxWidth() }
|
val modifier = remember { Modifier.fillMaxWidth() }
|
||||||
|
val isAuthorTheLoggedUser = remember { accountViewModel.isLoggedUser(note.author) }
|
||||||
|
|
||||||
if (eventContent != null) {
|
if (eventContent != null) {
|
||||||
if (makeItShort && accountViewModel.isLoggedUser(note.author)) {
|
if (makeItShort && isAuthorTheLoggedUser) {
|
||||||
Text(
|
Text(
|
||||||
text = eventContent,
|
text = eventContent,
|
||||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||||
@ -442,13 +462,13 @@ private fun RenderTextEvent(
|
|||||||
content = eventContent,
|
content = eventContent,
|
||||||
canPreview = canPreview && !makeItShort,
|
canPreview = canPreview && !makeItShort,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
tags = noteEvent.tags(),
|
tags = tags,
|
||||||
backgroundColor = backgroundColor,
|
backgroundColor = backgroundColor,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
navController = navController
|
navController = navController
|
||||||
)
|
)
|
||||||
|
|
||||||
DisplayUncitedHashtags(noteEvent.hashtags(), eventContent, navController)
|
DisplayUncitedHashtags(hashtags, eventContent, navController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,9 +657,11 @@ private fun RenderBadgeAward(
|
|||||||
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
FlowRow(modifier = Modifier.padding(top = 5.dp)) {
|
||||||
awardees.take(100).forEach { user ->
|
awardees.take(100).forEach { user ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.size(size = 35.dp).clickable {
|
modifier = Modifier
|
||||||
navController.navigate("User/${user.pubkeyHex}")
|
.size(size = 35.dp)
|
||||||
},
|
.clickable {
|
||||||
|
navController.navigate("User/${user.pubkeyHex}")
|
||||||
|
},
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
@ -711,7 +733,11 @@ private fun RenderRepost(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
note.replyTo?.lastOrNull()?.let {
|
val boostedNote = remember {
|
||||||
|
note.replyTo?.lastOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
boostedNote?.let {
|
||||||
NoteCompose(
|
NoteCompose(
|
||||||
it,
|
it,
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
@ -745,9 +771,16 @@ private fun RenderLongFormContent(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RenderReport(note: Note) {
|
private fun RenderReport(note: Note) {
|
||||||
val noteEvent = note.event as? ReportEvent ?: return
|
val base = remember {
|
||||||
|
val noteEvent = note.event as? ReportEvent
|
||||||
|
if (noteEvent == null) {
|
||||||
|
emptyList<ReportedKey>()
|
||||||
|
} else {
|
||||||
|
(noteEvent.reportedPost() + noteEvent.reportedAuthor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val reportType = (noteEvent.reportedPost() + noteEvent.reportedAuthor()).map {
|
val reportType = base.map {
|
||||||
when (it.reportType) {
|
when (it.reportType) {
|
||||||
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
|
ReportEvent.ReportType.EXPLICIT -> stringResource(R.string.explicit_content)
|
||||||
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
|
ReportEvent.ReportType.NUDITY -> stringResource(R.string.nudity)
|
||||||
@ -780,7 +813,9 @@ private fun ReplyRow(
|
|||||||
val noteEvent = note.event
|
val noteEvent = note.event
|
||||||
|
|
||||||
if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
if (noteEvent is TextNoteEvent && (note.replyTo != null || noteEvent.hasAnyTaggedUser())) {
|
||||||
val replyingDirectlyTo = note.replyTo?.lastOrNull()
|
val replyingDirectlyTo = remember {
|
||||||
|
note.replyTo?.lastOrNull()
|
||||||
|
}
|
||||||
if (replyingDirectlyTo != null && unPackReply) {
|
if (replyingDirectlyTo != null && unPackReply) {
|
||||||
NoteCompose(
|
NoteCompose(
|
||||||
baseNote = replyingDirectlyTo,
|
baseNote = replyingDirectlyTo,
|
||||||
@ -821,18 +856,18 @@ private fun SecondUserInfoRow(
|
|||||||
account: Account,
|
account: Account,
|
||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
val noteEvent = note.event ?: return
|
val noteEvent = remember { note.event } ?: return
|
||||||
val noteAuthor = note.author ?: return
|
val noteAuthor = remember { note.author } ?: return
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f))
|
ObserveDisplayNip05Status(noteAuthor, Modifier.weight(1f))
|
||||||
|
|
||||||
val baseReward = noteEvent.getReward()
|
val baseReward = remember { noteEvent.getReward() }
|
||||||
if (baseReward != null) {
|
if (baseReward != null) {
|
||||||
DisplayReward(baseReward, note, account, navController)
|
DisplayReward(baseReward, note, account, navController)
|
||||||
}
|
}
|
||||||
|
|
||||||
val pow = noteEvent.getPoWRank()
|
val pow = remember { noteEvent.getPoWRank() }
|
||||||
if (pow > 20) {
|
if (pow > 20) {
|
||||||
DisplayPoW(pow)
|
DisplayPoW(pow)
|
||||||
}
|
}
|
||||||
@ -848,14 +883,17 @@ private fun FirstUserInfoRow(
|
|||||||
navController: NavController
|
navController: NavController
|
||||||
) {
|
) {
|
||||||
var moreActionsExpanded by remember { mutableStateOf(false) }
|
var moreActionsExpanded by remember { mutableStateOf(false) }
|
||||||
val eventNote = baseNote.event ?: return
|
val eventNote = remember { baseNote.event } ?: return
|
||||||
val time = baseNote.createdAt() ?: return
|
val time = remember { baseNote.createdAt() } ?: return
|
||||||
val loggedIn = account.userProfile()
|
val loggedIn = remember { account.userProfile() }
|
||||||
|
val padding = remember {
|
||||||
|
Modifier.padding(horizontal = 5.dp)
|
||||||
|
}
|
||||||
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
if (showAuthorPicture) {
|
if (showAuthorPicture) {
|
||||||
NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp)
|
NoteAuthorPicture(baseNote, navController, loggedIn, 25.dp)
|
||||||
Spacer(Modifier.padding(horizontal = 5.dp))
|
Spacer(padding)
|
||||||
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
||||||
} else {
|
} else {
|
||||||
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
NoteUsernameDisplay(baseNote, Modifier.weight(1f))
|
||||||
@ -916,10 +954,11 @@ fun TimeAgo(time: Long) {
|
|||||||
@Composable
|
@Composable
|
||||||
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
|
private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavController) {
|
||||||
val baseChannel = remember { baseNote.channel() }
|
val baseChannel = remember { baseNote.channel() }
|
||||||
|
val modifier = remember { Modifier.width(55.dp) }
|
||||||
|
|
||||||
Column(Modifier.width(55.dp)) {
|
Column(modifier) {
|
||||||
// Draws the boosted picture outside the boosted card.
|
// Draws the boosted picture outside the boosted card.
|
||||||
Box(modifier = Modifier.width(55.dp), contentAlignment = Alignment.BottomEnd) {
|
Box(modifier = modifier, contentAlignment = Alignment.BottomEnd) {
|
||||||
NoteAuthorPicture(baseNote, navController, loggedIn, 55.dp)
|
NoteAuthorPicture(baseNote, navController, loggedIn, 55.dp)
|
||||||
|
|
||||||
if (baseNote.event is RepostEvent) {
|
if (baseNote.event is RepostEvent) {
|
||||||
@ -932,7 +971,10 @@ private fun DrawAuthorImages(baseNote: Note, loggedIn: User, navController: NavC
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (baseNote.event is RepostEvent) {
|
if (baseNote.event is RepostEvent) {
|
||||||
baseNote.replyTo?.lastOrNull()?.let {
|
val baseReply = remember {
|
||||||
|
baseNote.replyTo?.lastOrNull()
|
||||||
|
}
|
||||||
|
baseReply?.let {
|
||||||
RelayBadges(it)
|
RelayBadges(it)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -946,20 +988,30 @@ private fun ChannelNotePicture(baseChannel: Channel) {
|
|||||||
val channelState by baseChannel.live.observeAsState()
|
val channelState by baseChannel.live.observeAsState()
|
||||||
val channel = channelState?.channel
|
val channel = channelState?.channel
|
||||||
|
|
||||||
|
val modifier = remember {
|
||||||
|
Modifier
|
||||||
|
.width(30.dp)
|
||||||
|
.height(30.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
}
|
||||||
|
|
||||||
|
val boxModifier = remember {
|
||||||
|
Modifier
|
||||||
|
.width(30.dp)
|
||||||
|
.height(30.dp)
|
||||||
|
}
|
||||||
|
|
||||||
if (channel != null) {
|
if (channel != null) {
|
||||||
Box(
|
val model = remember(channelState) {
|
||||||
Modifier
|
ResizeImage(channel.profilePicture(), 30.dp)
|
||||||
.width(30.dp)
|
}
|
||||||
.height(30.dp)
|
|
||||||
) {
|
Box(boxModifier) {
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
robot = channel.idHex,
|
robot = channel.idHex,
|
||||||
model = ResizeImage(channel.profilePicture(), 30.dp),
|
model = model,
|
||||||
contentDescription = stringResource(R.string.group_picture),
|
contentDescription = stringResource(R.string.group_picture),
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.width(30.dp)
|
|
||||||
.height(30.dp)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(MaterialTheme.colors.background)
|
.background(MaterialTheme.colors.background)
|
||||||
.border(
|
.border(
|
||||||
2.dp,
|
2.dp,
|
||||||
@ -977,12 +1029,16 @@ private fun RepostNoteAuthorPicture(
|
|||||||
navController: NavController,
|
navController: NavController,
|
||||||
loggedIn: User
|
loggedIn: User
|
||||||
) {
|
) {
|
||||||
baseNote.replyTo?.lastOrNull()?.let {
|
val baseRepost = remember { baseNote.replyTo?.lastOrNull() }
|
||||||
Box(
|
|
||||||
Modifier
|
val modifier = remember {
|
||||||
.width(30.dp)
|
Modifier
|
||||||
.height(30.dp)
|
.width(30.dp)
|
||||||
) {
|
.height(30.dp)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseRepost?.let {
|
||||||
|
Box(modifier) {
|
||||||
NoteAuthorPicture(
|
NoteAuthorPicture(
|
||||||
it,
|
it,
|
||||||
navController,
|
navController,
|
||||||
@ -1041,13 +1097,14 @@ fun DisplayHighlight(
|
|||||||
authorHex?.let { authorHex ->
|
authorHex?.let { authorHex ->
|
||||||
userBase?.let { userBase ->
|
userBase?.let { userBase ->
|
||||||
val userState by userBase.live().metadata.observeAsState()
|
val userState by userBase.live().metadata.observeAsState()
|
||||||
val user = userState?.user
|
val route = remember { "User/${userBase.pubkeyHex}" }
|
||||||
|
val userDisplayName = remember(userState) { userState?.user?.toBestDisplayName() }
|
||||||
|
|
||||||
if (user != null) {
|
if (userDisplayName != null) {
|
||||||
CreateClickableText(
|
CreateClickableText(
|
||||||
user.toBestDisplayName(),
|
userDisplayName,
|
||||||
"",
|
"",
|
||||||
"User/${user.pubkeyHex}",
|
route,
|
||||||
navController
|
navController
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1055,11 +1112,13 @@ fun DisplayHighlight(
|
|||||||
}
|
}
|
||||||
|
|
||||||
url?.let { url ->
|
url?.let { url ->
|
||||||
val validatedUrl = try {
|
val validatedUrl = remember {
|
||||||
URL(url)
|
try {
|
||||||
} catch (e: Exception) {
|
URL(url)
|
||||||
Log.w("Note Compose", "Invalid URI: $url")
|
} catch (e: Exception) {
|
||||||
null
|
Log.w("Note Compose", "Invalid URI: $url")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validatedUrl?.host?.let { host ->
|
validatedUrl?.host?.let { host ->
|
||||||
@ -1524,44 +1583,59 @@ private fun VerticalRelayPanelWithFlow(
|
|||||||
private fun RelayIconCompose(url: String) {
|
private fun RelayIconCompose(url: String) {
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
|
|
||||||
Box(
|
val model = remember(url) { "https://$url/favicon.ico" }
|
||||||
|
|
||||||
|
val boxModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
.padding(1.dp)
|
.padding(1.dp)
|
||||||
.size(15.dp)
|
.size(15.dp)
|
||||||
) {
|
}
|
||||||
|
val iconModifier = remember(url) {
|
||||||
|
Modifier
|
||||||
|
.width(13.dp)
|
||||||
|
.height(13.dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.clickable(onClick = { uri.openUri("https://$url") })
|
||||||
|
}
|
||||||
|
val colorFilter = remember {
|
||||||
|
ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) })
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(boxModifier) {
|
||||||
RobohashFallbackAsyncImage(
|
RobohashFallbackAsyncImage(
|
||||||
robot = "https://$url/favicon.ico",
|
robot = model,
|
||||||
robotSize = 15.dp,
|
robotSize = 15.dp,
|
||||||
model = "https://$url/favicon.ico",
|
model = model,
|
||||||
contentDescription = stringResource(R.string.relay_icon),
|
contentDescription = stringResource(R.string.relay_icon),
|
||||||
colorFilter = ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }),
|
colorFilter = colorFilter,
|
||||||
modifier = Modifier
|
modifier = iconModifier.background(MaterialTheme.colors.background)
|
||||||
.width(13.dp)
|
|
||||||
.height(13.dp)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
.clickable(onClick = { uri.openUri("https://$url") })
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
||||||
Row(
|
val boxModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(25.dp),
|
.height(25.dp)
|
||||||
|
}
|
||||||
|
val iconButtonModifier = remember { Modifier.size(24.dp) }
|
||||||
|
val iconModifier = remember { Modifier.size(15.dp) }
|
||||||
|
|
||||||
|
Row(
|
||||||
|
boxModifier,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.Top
|
verticalAlignment = Alignment.Top
|
||||||
) {
|
) {
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(24.dp)),
|
modifier = iconButtonModifier,
|
||||||
onClick = onClick
|
onClick = onClick
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.ExpandMore,
|
imageVector = Icons.Default.ExpandMore,
|
||||||
null,
|
null,
|
||||||
modifier = Modifier.size(15.dp),
|
modifier = iconModifier,
|
||||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1590,25 +1664,30 @@ fun NoteAuthorPicture(
|
|||||||
onClick: ((User) -> Unit)? = null
|
onClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val noteState by baseNote.live().metadata.observeAsState()
|
val noteState by baseNote.live().metadata.observeAsState()
|
||||||
val note = noteState?.note ?: return
|
val author = remember(noteState) {
|
||||||
|
noteState?.note?.author
|
||||||
|
}
|
||||||
|
|
||||||
val author = note.author
|
val boxModifier = remember {
|
||||||
|
|
||||||
Box(
|
|
||||||
Modifier
|
Modifier
|
||||||
.width(size)
|
.width(size)
|
||||||
.height(size)
|
.height(size)
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
val nullModifier = remember {
|
||||||
|
modifier
|
||||||
|
.width(size)
|
||||||
|
.height(size)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(boxModifier) {
|
||||||
if (author == null) {
|
if (author == null) {
|
||||||
RobohashAsyncImage(
|
RobohashAsyncImage(
|
||||||
robot = "authornotfound",
|
robot = "authornotfound",
|
||||||
robotSize = size,
|
robotSize = size,
|
||||||
contentDescription = stringResource(R.string.unknown_author),
|
contentDescription = stringResource(R.string.unknown_author),
|
||||||
modifier = modifier
|
modifier = nullModifier.background(MaterialTheme.colors.background)
|
||||||
.width(size)
|
|
||||||
.height(size)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
UserPicture(author, baseUserAccount, size, modifier, onClick)
|
UserPicture(author, baseUserAccount, size, modifier, onClick)
|
||||||
@ -1640,31 +1719,38 @@ fun UserPicture(
|
|||||||
onLongClick: ((User) -> Unit)? = null
|
onLongClick: ((User) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
val userState by baseUser.live().metadata.observeAsState()
|
val userState by baseUser.live().metadata.observeAsState()
|
||||||
val user = userState?.user ?: return
|
|
||||||
|
|
||||||
val accountState by baseUserAccount.live().follows.observeAsState()
|
val accountState by baseUserAccount.live().follows.observeAsState()
|
||||||
val accountUser = accountState?.user ?: return
|
|
||||||
|
|
||||||
val showFollowingMark = accountUser.isFollowingCached(user) || user == accountUser
|
val userPubkey = remember {
|
||||||
|
baseUser.pubkeyHex
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
val userProfile = remember(userState) {
|
||||||
modifier = Modifier
|
userState?.user?.profilePicture()
|
||||||
.run {
|
}
|
||||||
if (onClick != null && onLongClick != null) {
|
|
||||||
this.combinedClickable(
|
val showFollowingMark = remember(accountState) {
|
||||||
onClick = { onClick(user) },
|
accountState?.user?.isFollowingCached(baseUser) == true || baseUser === accountState?.user
|
||||||
onLongClick = { onLongClick(user) }
|
}
|
||||||
)
|
|
||||||
} else if (onClick != null) {
|
// BaseUser is the same reference as accountState.user
|
||||||
this.clickable(onClick = { onClick(user) })
|
val myModifier = remember {
|
||||||
} else {
|
if (onClick != null && onLongClick != null) {
|
||||||
this
|
Modifier.combinedClickable(
|
||||||
}
|
onClick = { onClick(baseUser) },
|
||||||
}
|
onLongClick = { onLongClick(baseUser) }
|
||||||
) {
|
)
|
||||||
|
} else if (onClick != null) {
|
||||||
|
Modifier.clickable(onClick = { onClick(baseUser) })
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(modifier = myModifier) {
|
||||||
UserPicture(
|
UserPicture(
|
||||||
userHex = user.pubkeyHex,
|
userHex = userPubkey,
|
||||||
userPicture = user.profilePicture(),
|
userPicture = userProfile,
|
||||||
showFollowingMark = showFollowingMark,
|
showFollowingMark = showFollowingMark,
|
||||||
size = size,
|
size = size,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
@ -1680,43 +1766,59 @@ fun UserPicture(
|
|||||||
size: Dp,
|
size: Dp,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Box(
|
val myBoxModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
.width(size)
|
.width(size)
|
||||||
.height(size)
|
.height(size)
|
||||||
) {
|
}
|
||||||
|
|
||||||
|
val myImageModifier = remember {
|
||||||
|
modifier
|
||||||
|
.width(size)
|
||||||
|
.height(size)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
}
|
||||||
|
|
||||||
|
val myResizeImage = remember(userPicture) {
|
||||||
|
ResizeImage(userPicture, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(myBoxModifier) {
|
||||||
RobohashAsyncImageProxy(
|
RobohashAsyncImageProxy(
|
||||||
robot = userHex,
|
robot = userHex,
|
||||||
model = ResizeImage(userPicture, size),
|
model = myResizeImage,
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
modifier = modifier
|
modifier = myImageModifier.background(MaterialTheme.colors.background)
|
||||||
.width(size)
|
|
||||||
.height(size)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (showFollowingMark) {
|
if (showFollowingMark) {
|
||||||
Box(
|
val myIconBoxModifier = remember {
|
||||||
Modifier
|
Modifier
|
||||||
.width(size.div(3.5f))
|
.width(size.div(3.5f))
|
||||||
.height(size.div(3.5f))
|
.height(size.div(3.5f))
|
||||||
.align(Alignment.TopEnd),
|
.align(Alignment.TopEnd)
|
||||||
contentAlignment = Alignment.Center
|
}
|
||||||
) {
|
|
||||||
// Background for the transparent checkmark
|
val myIconBackgroundModifier = remember {
|
||||||
|
Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.fillMaxSize(0.6f)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
}
|
||||||
|
|
||||||
|
val myIconModifier = remember {
|
||||||
|
Modifier.fillMaxSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(myIconBoxModifier, contentAlignment = Alignment.Center) {
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
myIconBackgroundModifier.background(MaterialTheme.colors.background)
|
||||||
.clip(CircleShape)
|
|
||||||
.fillMaxSize(0.6f)
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.background(MaterialTheme.colors.background)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_verified),
|
painter = painterResource(R.drawable.ic_verified),
|
||||||
stringResource(id = R.string.following),
|
stringResource(id = R.string.following),
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = myIconModifier,
|
||||||
tint = Following
|
tint = Following
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ import kotlin.math.roundToInt
|
|||||||
@Composable
|
@Composable
|
||||||
fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel, navController: NavController) {
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = accountState?.account ?: return
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
|
|
||||||
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
val grayTint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||||
|
|
||||||
@ -123,13 +123,21 @@ fun ReplyReaction(
|
|||||||
onPress: () -> Unit
|
onPress: () -> Unit
|
||||||
) {
|
) {
|
||||||
val repliesState by baseNote.live().replies.observeAsState()
|
val repliesState by baseNote.live().replies.observeAsState()
|
||||||
val replies = repliesState?.note?.replies ?: emptySet()
|
val replies = remember(repliesState) { repliesState?.note?.replies } ?: emptySet()
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val iconButtonModifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconModifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(iconSize),
|
modifier = iconButtonModifier,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (accountViewModel.isWriteable()) {
|
if (accountViewModel.isWriteable()) {
|
||||||
onPress()
|
onPress()
|
||||||
@ -147,7 +155,7 @@ fun ReplyReaction(
|
|||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_comment),
|
painter = painterResource(R.drawable.ic_comment),
|
||||||
null,
|
null,
|
||||||
modifier = Modifier.size(iconSize),
|
modifier = iconModifier,
|
||||||
tint = grayTint
|
tint = grayTint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -162,7 +170,7 @@ fun ReplyReaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun BoostReaction(
|
fun BoostReaction(
|
||||||
baseNote: Note,
|
baseNote: Note,
|
||||||
grayTint: Color,
|
grayTint: Color,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
@ -170,17 +178,32 @@ public fun BoostReaction(
|
|||||||
onQuotePress: () -> Unit
|
onQuotePress: () -> Unit
|
||||||
) {
|
) {
|
||||||
val boostsState by baseNote.live().boosts.observeAsState()
|
val boostsState by baseNote.live().boosts.observeAsState()
|
||||||
val boostedNote = boostsState?.note
|
val boostedNote = remember(boostsState) { boostsState?.note } ?: return
|
||||||
|
|
||||||
|
val hasBoosted = remember(boostsState) { accountViewModel.hasBoosted(baseNote) }
|
||||||
|
val wasBoostedByLoggedIn = remember(boostsState) { boostedNote.isBoostedBy(accountViewModel.userProfile()) }
|
||||||
|
val isWriteable = remember { accountViewModel.isWriteable() }
|
||||||
|
|
||||||
|
val boostCount = remember(boostsState) { showCount(boostedNote.boosts.size) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
var wantsToBoost by remember { mutableStateOf(false) }
|
var wantsToBoost by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val iconButtonModifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconModifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(iconSize)),
|
modifier = iconButtonModifier,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (accountViewModel.isWriteable()) {
|
if (isWriteable) {
|
||||||
if (accountViewModel.hasBoosted(baseNote)) {
|
if (hasBoosted) {
|
||||||
accountViewModel.deleteBoostsTo(baseNote)
|
accountViewModel.deleteBoostsTo(baseNote)
|
||||||
} else {
|
} else {
|
||||||
wantsToBoost = true
|
wantsToBoost = true
|
||||||
@ -210,25 +233,16 @@ public fun BoostReaction(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (boostedNote?.isBoostedBy(accountViewModel.userProfile()) == true) {
|
Icon(
|
||||||
Icon(
|
painter = painterResource(R.drawable.ic_retweeted),
|
||||||
painter = painterResource(R.drawable.ic_retweeted),
|
null,
|
||||||
null,
|
modifier = iconModifier,
|
||||||
modifier = Modifier.size(iconSize),
|
tint = if (wasBoostedByLoggedIn) Color.Unspecified else grayTint
|
||||||
tint = Color.Unspecified
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.ic_retweet),
|
|
||||||
null,
|
|
||||||
modifier = Modifier.size(iconSize),
|
|
||||||
tint = grayTint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
" ${showCount(boostedNote?.boosts?.size)}",
|
" $boostCount",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = grayTint
|
color = grayTint
|
||||||
)
|
)
|
||||||
@ -243,16 +257,30 @@ fun LikeReaction(
|
|||||||
heartSize: Dp = 16.dp
|
heartSize: Dp = 16.dp
|
||||||
) {
|
) {
|
||||||
val reactionsState by baseNote.live().reactions.observeAsState()
|
val reactionsState by baseNote.live().reactions.observeAsState()
|
||||||
val reactedNote = reactionsState?.note ?: return
|
val reactedNote = remember(reactionsState) { reactionsState?.note } ?: return
|
||||||
|
|
||||||
|
val hasReacted = remember(reactionsState) { accountViewModel.hasReactedTo(baseNote) }
|
||||||
|
val wasReactedByLoggedIn = remember(reactionsState) { reactedNote.isReactedBy(accountViewModel.userProfile()) }
|
||||||
|
val isWriteable = remember { accountViewModel.isWriteable() }
|
||||||
|
|
||||||
|
val reactionCount = remember(reactionsState) { showCount(reactedNote.reactions.size) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val iconButtonModifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconModifier = remember {
|
||||||
|
Modifier.size(heartSize)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.then(Modifier.size(iconSize)),
|
modifier = iconButtonModifier,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (accountViewModel.isWriteable()) {
|
if (isWriteable) {
|
||||||
if (accountViewModel.hasReactedTo(baseNote)) {
|
if (hasReacted) {
|
||||||
accountViewModel.deleteReactionTo(baseNote)
|
accountViewModel.deleteReactionTo(baseNote)
|
||||||
} else {
|
} else {
|
||||||
accountViewModel.reactTo(baseNote)
|
accountViewModel.reactTo(baseNote)
|
||||||
@ -268,25 +296,25 @@ fun LikeReaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
if (reactedNote.isReactedBy(accountViewModel.userProfile())) {
|
if (wasReactedByLoggedIn) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_liked),
|
painter = painterResource(R.drawable.ic_liked),
|
||||||
null,
|
null,
|
||||||
modifier = Modifier.size(heartSize),
|
modifier = iconModifier,
|
||||||
tint = Color.Unspecified
|
tint = Color.Unspecified
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(R.drawable.ic_like),
|
painter = painterResource(R.drawable.ic_like),
|
||||||
null,
|
null,
|
||||||
modifier = Modifier.size(heartSize),
|
modifier = iconModifier,
|
||||||
tint = grayTint
|
tint = grayTint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
" ${showCount(reactedNote.reactions.size)}",
|
" $reactionCount",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = grayTint
|
color = grayTint
|
||||||
)
|
)
|
||||||
@ -472,6 +500,19 @@ public fun ViewCountReaction(
|
|||||||
numberSize: Dp = 24.dp
|
numberSize: Dp = 24.dp
|
||||||
) {
|
) {
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val iconButtonModifier = remember {
|
||||||
|
Modifier.size(barChartSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val iconModifier = remember {
|
||||||
|
Modifier.height(numberSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorFilter = remember {
|
||||||
|
ColorFilter.tint(grayTint)
|
||||||
|
}
|
||||||
|
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.size(iconSize),
|
modifier = Modifier.size(iconSize),
|
||||||
@ -480,21 +521,25 @@ public fun ViewCountReaction(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.BarChart,
|
imageVector = Icons.Outlined.BarChart,
|
||||||
null,
|
null,
|
||||||
modifier = Modifier.size(barChartSize),
|
modifier = iconButtonModifier,
|
||||||
tint = grayTint
|
tint = grayTint
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row() {
|
val request = remember {
|
||||||
|
ImageRequest.Builder(context)
|
||||||
|
.data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000")
|
||||||
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
model = ImageRequest.Builder(LocalContext.current)
|
model = request,
|
||||||
.data("https://counter.amethyst.social/$idHex.svg?label=+&color=00000000")
|
|
||||||
.diskCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
|
||||||
.build(),
|
|
||||||
contentDescription = stringResource(R.string.view_count),
|
contentDescription = stringResource(R.string.view_count),
|
||||||
modifier = Modifier.height(numberSize),
|
modifier = iconModifier,
|
||||||
colorFilter = ColorFilter.tint(grayTint)
|
colorFilter = colorFilter
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user