mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
Merge remote-tracking branch 'origin/HEAD' into less_memory_test_branch
This commit is contained in:
@@ -2,18 +2,20 @@ package com.vitorpamplona.amethyst
|
||||
|
||||
import android.content.Context
|
||||
import androidx.security.crypto.EncryptedSharedPreferences
|
||||
import androidx.security.crypto.MasterKeys
|
||||
import androidx.security.crypto.MasterKey
|
||||
|
||||
class EncryptedStorage {
|
||||
object EncryptedStorage {
|
||||
private const val PREFERENCES_NAME = "secret_keeper"
|
||||
|
||||
fun preferences(context: Context): EncryptedSharedPreferences {
|
||||
val secretKey: String = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
|
||||
val preferencesName = "secret_keeper"
|
||||
val masterKey: MasterKey = MasterKey.Builder(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS)
|
||||
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
||||
.build()
|
||||
|
||||
return EncryptedSharedPreferences.create(
|
||||
preferencesName,
|
||||
secretKey,
|
||||
context,
|
||||
PREFERENCES_NAME,
|
||||
masterKey,
|
||||
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
||||
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
||||
) as EncryptedSharedPreferences
|
||||
|
||||
@@ -29,7 +29,7 @@ class LocalPreferences(context: Context) {
|
||||
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||
}
|
||||
|
||||
private val encryptedPreferences = EncryptedStorage().preferences(context)
|
||||
private val encryptedPreferences = EncryptedStorage.preferences(context)
|
||||
private val gson = GsonBuilder().create()
|
||||
|
||||
fun clearEncryptedStorage() {
|
||||
|
||||
@@ -300,7 +300,7 @@ object LocalCache {
|
||||
// Saves relay list only if it's a user that is currently been seen
|
||||
user.updateContactList(event)
|
||||
|
||||
Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}")
|
||||
// Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ class NoteLiveData(val note: Note) : LiveData<NoteState>(NoteState(note)) {
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
private fun refresh() {
|
||||
postValue(NoteState(note))
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,17 @@ class LnZapEvent(
|
||||
}
|
||||
|
||||
override fun amount(): BigDecimal? {
|
||||
return lnInvoice()?.let { LnInvoiceUtil.getAmountInSats(it) }
|
||||
return amount
|
||||
}
|
||||
|
||||
// Keeps this as a field because it's a heavier function used everywhere.
|
||||
val amount by lazy {
|
||||
lnInvoice()?.let { LnInvoiceUtil.getAmountInSats(it) }
|
||||
try {
|
||||
lnInvoice()?.let { LnInvoiceUtil.getAmountInSats(it) }
|
||||
} catch (e: Exception) {
|
||||
Log.e("LnZapEvent", "Failed to Parse LnInvoice ${description()}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun containedPost(): Event? = try {
|
||||
|
||||
@@ -21,6 +21,9 @@ import com.vitorpamplona.amethyst.service.relays.Client
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountScreen
|
||||
import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.AmethystTheme
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class MainActivity : FragmentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -66,7 +69,9 @@ class MainActivity : FragmentActivity() {
|
||||
super.onResume()
|
||||
|
||||
// Only starts after login
|
||||
ServiceManager.start()
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
ServiceManager.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
|
||||
@@ -40,7 +40,9 @@ import androidx.navigation.NavHostController
|
||||
import com.vitorpamplona.amethyst.NotificationCache
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
val bottomNavigationItems = listOf(
|
||||
Route.Home,
|
||||
@@ -157,11 +159,15 @@ private fun NotifiableIcon(item: Route, currentRoute: String?, accountViewModel:
|
||||
val context = LocalContext.current.applicationContext
|
||||
|
||||
LaunchedEffect(key1 = notif) {
|
||||
hasNewItems = item.hasNewItems(account, notif.cache, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
hasNewItems = item.hasNewItems(account, notif.cache, context)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = db) {
|
||||
hasNewItems = item.hasNewItems(account, notif.cache, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
hasNewItems = item.hasNewItems(account, notif.cache, context)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasNewItems) {
|
||||
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import coil.compose.AsyncImage
|
||||
import com.vitorpamplona.amethyst.BuildConfig
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.RoboHashCache
|
||||
@@ -114,8 +115,8 @@ fun ProfileContent(baseAccountUser: User, modifier: Modifier = Modifier, scaffol
|
||||
Box {
|
||||
val banner = accountUser.info?.banner
|
||||
if (banner != null && banner.isNotBlank()) {
|
||||
AsyncImageProxy(
|
||||
model = ResizeImage(banner, 150.dp),
|
||||
AsyncImage(
|
||||
model = banner,
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
|
||||
@@ -11,10 +11,12 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.MilitaryTech
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -35,6 +37,8 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -56,9 +60,11 @@ fun BadgeCompose(likeSetCard: BadgeCard, modifier: Modifier = Modifier, isInnerN
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = likeSetCard) {
|
||||
isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead, context)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt(), context)
|
||||
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt(), context)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
@@ -108,11 +114,33 @@ fun BadgeCompose(likeSetCard: BadgeCard, modifier: Modifier = Modifier, isInnerN
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(start = if (!isInnerNote) 10.dp else 0.dp)) {
|
||||
Text(
|
||||
stringResource(R.string.new_badge_award_notif),
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 5.dp)
|
||||
)
|
||||
Row() {
|
||||
Text(
|
||||
stringResource(R.string.new_badge_award_notif),
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 5.dp).weight(1f)
|
||||
)
|
||||
|
||||
Text(
|
||||
timeAgo(note.createdAt(), context = context),
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
IconButton(
|
||||
modifier = Modifier.then(Modifier.size(24.dp)),
|
||||
onClick = { popupExpanded = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
null,
|
||||
modifier = Modifier.size(15.dp),
|
||||
tint = MaterialTheme.colors.onSurface.copy(alpha = 0.32f)
|
||||
)
|
||||
|
||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
note.replyTo?.firstOrNull()?.let {
|
||||
NoteCompose(
|
||||
@@ -125,8 +153,6 @@ fun BadgeCompose(likeSetCard: BadgeCard, modifier: Modifier = Modifier, isInnerN
|
||||
)
|
||||
}
|
||||
|
||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
|
||||
@@ -32,6 +32,8 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.BoostSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -53,9 +55,11 @@ fun BoostSetCompose(boostSetCard: BoostSetCard, isInnerNote: Boolean = false, ro
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = boostSetCard) {
|
||||
isNew = boostSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = boostSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, boostSetCard.createdAt, context)
|
||||
NotificationCache.markAsRead(routeForLastRead, boostSetCard.createdAt, context)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
|
||||
@@ -52,6 +52,8 @@ import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun ChatroomCompose(
|
||||
@@ -62,9 +64,6 @@ fun ChatroomCompose(
|
||||
val noteState by baseNote.live().metadata.observeAsState()
|
||||
val note = noteState?.note
|
||||
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
|
||||
val notificationCacheState = NotificationCache.live.observeAsState()
|
||||
val notificationCache = notificationCacheState.value ?: return
|
||||
|
||||
@@ -92,9 +91,11 @@ fun ChatroomCompose(
|
||||
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = notificationCache, key2 = note) {
|
||||
note.createdAt()?.let {
|
||||
hasNewMessages =
|
||||
it > notificationCache.cache.load("Channel/${channel.idHex}", context)
|
||||
withContext(Dispatchers.IO) {
|
||||
note.createdAt()?.let {
|
||||
hasNewMessages =
|
||||
it > notificationCache.cache.load("Channel/${channel.idHex}", context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +147,7 @@ fun ChatroomCompose(
|
||||
var userToComposeOn = note.author!!
|
||||
|
||||
if (replyAuthorBase != null) {
|
||||
if (note.author == account.userProfile()) {
|
||||
if (note.author == accountViewModel.userProfile()) {
|
||||
userToComposeOn = replyAuthorBase
|
||||
}
|
||||
}
|
||||
@@ -157,11 +158,13 @@ fun ChatroomCompose(
|
||||
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = notificationCache, key2 = note) {
|
||||
noteEvent?.let {
|
||||
hasNewMessages = it.createdAt() > notificationCache.cache.load(
|
||||
"Room/${userToComposeOn.pubkeyHex}",
|
||||
context
|
||||
)
|
||||
withContext(Dispatchers.IO) {
|
||||
noteEvent?.let {
|
||||
hasNewMessages = it.createdAt() > notificationCache.cache.load(
|
||||
"Room/${userToComposeOn.pubkeyHex}",
|
||||
context
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +172,7 @@ fun ChatroomCompose(
|
||||
channelPicture = {
|
||||
UserPicture(
|
||||
userToComposeOn,
|
||||
account.userProfile(),
|
||||
accountViewModel.userProfile(),
|
||||
size = 55.dp
|
||||
)
|
||||
},
|
||||
|
||||
@@ -64,6 +64,8 @@ import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
val ChatBubbleShapeMe = RoundedCornerShape(15.dp, 15.dp, 3.dp, 15.dp)
|
||||
val ChatBubbleShapeThem = RoundedCornerShape(3.dp, 15.dp, 15.dp, 15.dp)
|
||||
@@ -131,12 +133,14 @@ fun ChatroomMessageCompose(
|
||||
|
||||
LaunchedEffect(key1 = routeForLastRead) {
|
||||
routeForLastRead?.let {
|
||||
val lastTime = NotificationCache.load(it, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
val lastTime = NotificationCache.load(it, context)
|
||||
|
||||
val createdAt = note.createdAt()
|
||||
if (createdAt != null) {
|
||||
NotificationCache.markAsRead(it, createdAt, context)
|
||||
isNew = createdAt > lastTime
|
||||
val createdAt = note.createdAt()
|
||||
if (createdAt != null) {
|
||||
NotificationCache.markAsRead(it, createdAt, context)
|
||||
isNew = createdAt > lastTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.LikeSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -53,9 +55,11 @@ fun LikeSetCompose(likeSetCard: LikeSetCard, modifier: Modifier = Modifier, isIn
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = likeSetCard) {
|
||||
isNew = likeSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = likeSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt, context)
|
||||
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt, context)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.compositeOver
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.vitorpamplona.amethyst.NotificationCache
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MessageSetCompose(messageSetCard: MessageSetCard, isInnerNote: Boolean = false, routeForLastRead: String, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val noteState by messageSetCard.note.live().metadata.observeAsState()
|
||||
val note = noteState?.note
|
||||
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
val account = accountState?.account ?: return
|
||||
|
||||
val context = LocalContext.current.applicationContext
|
||||
|
||||
val noteEvent = note?.event
|
||||
var popupExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
if (note == null) {
|
||||
BlankNote(Modifier, isInnerNote)
|
||||
} else {
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = messageSetCard) {
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew =
|
||||
messageSetCard.createdAt() > NotificationCache.load(routeForLastRead, context)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt(), context)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
MaterialTheme.colors.primary.copy(0.12f).compositeOver(MaterialTheme.colors.background)
|
||||
} else {
|
||||
MaterialTheme.colors.background
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.background(backgroundColor).combinedClickable(
|
||||
onClick = {
|
||||
if (noteEvent !is ChannelMessageEvent) {
|
||||
navController.navigate("Note/${note.idHex}") {
|
||||
launchSingleTop = true
|
||||
}
|
||||
} else {
|
||||
note.channel()?.let {
|
||||
navController.navigate("Channel/${it.idHex}")
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongClick = { popupExpanded = true }
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = if (!isInnerNote) 12.dp else 0.dp,
|
||||
end = if (!isInnerNote) 12.dp else 0.dp,
|
||||
top = 10.dp
|
||||
)
|
||||
) {
|
||||
// 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)) {
|
||||
NoteCompose(
|
||||
baseNote = note,
|
||||
routeForLastRead = null,
|
||||
isBoostedNote = true,
|
||||
addMarginTop = false,
|
||||
parentBackgroundColor = backgroundColor,
|
||||
accountViewModel = accountViewModel,
|
||||
navController = navController
|
||||
)
|
||||
|
||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.MultiSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -57,9 +59,11 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, modifier: Modifier = Modifier, r
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = multiSetCard) {
|
||||
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = multiSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt, context)
|
||||
NotificationCache.markAsRead(routeForLastRead, multiSetCard.createdAt, context)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
|
||||
@@ -60,6 +60,8 @@ import com.vitorpamplona.amethyst.ui.components.TranslateableRichTextViewer
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -71,6 +73,7 @@ fun NoteCompose(
|
||||
isQuotedNote: Boolean = false,
|
||||
unPackReply: Boolean = true,
|
||||
makeItShort: Boolean = false,
|
||||
addMarginTop: Boolean = true,
|
||||
parentBackgroundColor: Color? = null,
|
||||
accountViewModel: AccountViewModel,
|
||||
navController: NavController
|
||||
@@ -119,13 +122,15 @@ fun NoteCompose(
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = routeForLastRead) {
|
||||
routeForLastRead?.let {
|
||||
val lastTime = NotificationCache.load(it, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
routeForLastRead?.let {
|
||||
val lastTime = NotificationCache.load(it, context)
|
||||
|
||||
val createdAt = note.createdAt()
|
||||
if (createdAt != null) {
|
||||
NotificationCache.markAsRead(it, createdAt, context)
|
||||
isNew = createdAt > lastTime
|
||||
val createdAt = note.createdAt()
|
||||
if (createdAt != null) {
|
||||
NotificationCache.markAsRead(it, createdAt, context)
|
||||
isNew = createdAt > lastTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -168,7 +173,7 @@ fun NoteCompose(
|
||||
.padding(
|
||||
start = if (!isBoostedNote) 12.dp else 0.dp,
|
||||
end = if (!isBoostedNote) 12.dp else 0.dp,
|
||||
top = 10.dp
|
||||
top = if (addMarginTop) 10.dp else 0.dp
|
||||
)
|
||||
) {
|
||||
if (!isBoostedNote && !isQuotedNote) {
|
||||
@@ -414,6 +419,34 @@ fun NoteCompose(
|
||||
|
||||
ReactionsRow(note, accountViewModel)
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
)
|
||||
} else if (noteEvent is PrivateDmEvent &&
|
||||
noteEvent.recipientPubKey() != account.userProfile().pubkeyHex &&
|
||||
note.author != account.userProfile()
|
||||
) {
|
||||
val recepient = noteEvent.recipientPubKey()?.let { LocalCache.checkGetOrCreateUser(it) }
|
||||
|
||||
TranslateableRichTextViewer(
|
||||
stringResource(
|
||||
id = R.string.private_conversation_notification,
|
||||
"@${note.author?.pubkeyNpub()}",
|
||||
"@${recepient?.pubkeyNpub()}"
|
||||
),
|
||||
canPreview = !makeItShort,
|
||||
Modifier.fillMaxWidth(),
|
||||
noteEvent.tags(),
|
||||
backgroundColor,
|
||||
accountViewModel,
|
||||
navController
|
||||
)
|
||||
|
||||
if (!makeItShort) {
|
||||
ReactionsRow(note, accountViewModel)
|
||||
}
|
||||
|
||||
Divider(
|
||||
modifier = Modifier.padding(top = 10.dp),
|
||||
thickness = 0.25.dp
|
||||
|
||||
@@ -220,12 +220,12 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Text("Don't show again")
|
||||
Text(stringResource(R.string.quick_action_dont_show_again_button))
|
||||
}
|
||||
Button(
|
||||
onClick = { accountViewModel.delete(note); onDismiss() }
|
||||
) {
|
||||
Text("Delete")
|
||||
Text(stringResource(R.string.quick_action_delete_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,9 @@ import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.actions.SaveButton
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
@@ -99,7 +101,9 @@ fun ReactionsRow(baseNote: Note, accountViewModel: AccountViewModel) {
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 8.dp).fillMaxWidth(),
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
@@ -350,7 +354,12 @@ fun ZapReaction(
|
||||
.show()
|
||||
}
|
||||
} else if (account.zapAmountChoices.size == 1) {
|
||||
accountViewModel.zap(baseNote, account.zapAmountChoices.first() * 1000, "", context) {
|
||||
accountViewModel.zap(
|
||||
baseNote,
|
||||
account.zapAmountChoices.first() * 1000,
|
||||
"",
|
||||
context
|
||||
) {
|
||||
scope.launch {
|
||||
Toast
|
||||
.makeText(context, it, Toast.LENGTH_SHORT)
|
||||
@@ -405,8 +414,16 @@ fun ZapReaction(
|
||||
}
|
||||
}
|
||||
|
||||
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = zappedNote) {
|
||||
withContext(Dispatchers.IO) {
|
||||
zapAmount = zappedNote?.zappedAmount()
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
showAmount(zappedNote?.zappedAmount()),
|
||||
showAmount(zapAmount),
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.32f),
|
||||
modifier = textModifier
|
||||
|
||||
@@ -9,9 +9,13 @@ import androidx.compose.material.Divider
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -31,6 +35,8 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.UnfollowButton
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
@@ -88,15 +94,20 @@ fun ZapNoteCompose(baseNote: Pair<Note, Note>, accountViewModel: AccountViewMode
|
||||
)
|
||||
}
|
||||
|
||||
val amount =
|
||||
(noteZap.event as? LnZapEvent)?.amount
|
||||
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = noteZap) {
|
||||
withContext(Dispatchers.IO) {
|
||||
zapAmount = (noteZap.event as? LnZapEvent)?.amount
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
"${showAmount(amount)} ${stringResource(R.string.sats)}",
|
||||
"${showAmount(zapAmount)} ${stringResource(R.string.sats)}",
|
||||
color = BitcoinOrange,
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W500
|
||||
|
||||
@@ -34,6 +34,8 @@ import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||
import com.vitorpamplona.amethyst.ui.screen.ZapSetCard
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
@@ -55,9 +57,11 @@ fun ZapSetCompose(zapSetCard: ZapSetCard, modifier: Modifier = Modifier, isInner
|
||||
var isNew by remember { mutableStateOf<Boolean>(false) }
|
||||
|
||||
LaunchedEffect(key1 = zapSetCard) {
|
||||
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
withContext(Dispatchers.IO) {
|
||||
isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead, context)
|
||||
|
||||
NotificationCache.markAsRead(routeForLastRead, zapSetCard.createdAt, context)
|
||||
NotificationCache.markAsRead(routeForLastRead, zapSetCard.createdAt, context)
|
||||
}
|
||||
}
|
||||
|
||||
var backgroundColor = if (isNew) {
|
||||
|
||||
@@ -63,6 +63,14 @@ class BoostSetCard(val note: Note, val boostEvents: List<Note>) : Card() {
|
||||
override fun id() = note.idHex + "B" + createdAt
|
||||
}
|
||||
|
||||
class MessageSetCard(val note: Note) : Card() {
|
||||
override fun createdAt(): Long {
|
||||
return note.createdAt() ?: 0
|
||||
}
|
||||
|
||||
override fun id() = note.idHex
|
||||
}
|
||||
|
||||
sealed class CardFeedState {
|
||||
object Loading : CardFeedState()
|
||||
class Loaded(val feed: MutableState<List<Card>>) : CardFeedState()
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.vitorpamplona.amethyst.ui.note.BadgeCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.BoostSetCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.LikeSetCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.MessageSetCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.MultiSetCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.NoteCompose
|
||||
import com.vitorpamplona.amethyst.ui.note.ZapSetCompose
|
||||
@@ -134,6 +135,12 @@ private fun FeedLoaded(
|
||||
navController = navController,
|
||||
routeForLastRead = routeForLastRead
|
||||
)
|
||||
is MessageSetCard -> MessageSetCompose(
|
||||
messageSetCard = item,
|
||||
routeForLastRead = routeForLastRead,
|
||||
accountViewModel = accountViewModel,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.vitorpamplona.amethyst.service.model.BadgeAwardEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||
import com.vitorpamplona.amethyst.service.model.ReactionEvent
|
||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||
import com.vitorpamplona.amethyst.ui.dal.FeedFilter
|
||||
@@ -111,7 +112,9 @@ open class CardFeedViewModel(val dataSource: FeedFilter<Note>) : ViewModel() {
|
||||
}
|
||||
|
||||
val textNoteCards = notes.filter { it.event !is ReactionEvent && it.event !is RepostEvent && it.event !is LnZapEvent }.map {
|
||||
if (it.event is BadgeAwardEvent) {
|
||||
if (it.event is PrivateDmEvent) {
|
||||
MessageSetCard(it)
|
||||
} else if (it.event is BadgeAwardEvent) {
|
||||
BadgeCard(it)
|
||||
} else {
|
||||
NoteCard(it)
|
||||
|
||||
@@ -63,7 +63,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>) : Vi
|
||||
|
||||
var handlerWaiting = AtomicBoolean()
|
||||
|
||||
private fun invalidateData() {
|
||||
fun invalidateData() {
|
||||
if (handlerWaiting.getAndSet(true)) return
|
||||
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
@@ -76,7 +76,6 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>) : Vi
|
||||
handlerWaiting.set(false)
|
||||
}
|
||||
}
|
||||
handlerWaiting.set(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel() {
|
||||
|
||||
var handlerWaiting = AtomicBoolean()
|
||||
|
||||
private fun invalidateData() {
|
||||
fun invalidateData() {
|
||||
if (handlerWaiting.getAndSet(true)) return
|
||||
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
@@ -62,7 +62,6 @@ import com.vitorpamplona.amethyst.service.model.BadgeProfilesEvent
|
||||
import com.vitorpamplona.amethyst.service.model.IdentityClaim
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewUserMetadataView
|
||||
import com.vitorpamplona.amethyst.ui.components.AsyncImageProxy
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayNip05ProfileStatus
|
||||
import com.vitorpamplona.amethyst.ui.components.InvoiceRequest
|
||||
import com.vitorpamplona.amethyst.ui.components.ResizeImage
|
||||
@@ -89,6 +88,8 @@ import com.vitorpamplona.amethyst.ui.screen.UserFeedView
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Composable
|
||||
@@ -210,9 +211,20 @@ fun ProfileScreen(userId: String?, accountViewModel: AccountViewModel, navContro
|
||||
},
|
||||
{
|
||||
val userState by baseUser.live().zaps.observeAsState()
|
||||
val userZaps = userState?.user?.zappedAmount()
|
||||
val userZaps = userState?.user
|
||||
|
||||
Text(text = "${showAmount(userZaps)} ${stringResource(id = R.string.zaps)}")
|
||||
var zapAmount by remember { mutableStateOf<BigDecimal?>(null) }
|
||||
|
||||
LaunchedEffect(key1 = userState) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val tempAmount = userZaps?.zappedAmount()
|
||||
withContext(Dispatchers.Main) {
|
||||
zapAmount = tempAmount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(text = "${showAmount(zapAmount)} ${stringResource(id = R.string.zaps)}")
|
||||
},
|
||||
{
|
||||
val userState by baseUser.live().reports.observeAsState()
|
||||
@@ -610,8 +622,8 @@ private fun DrawBanner(baseUser: User) {
|
||||
var zoomImageDialogOpen by remember { mutableStateOf(false) }
|
||||
|
||||
if (!banner.isNullOrBlank()) {
|
||||
AsyncImageProxy(
|
||||
model = ResizeImage(banner, 125.dp),
|
||||
AsyncImage(
|
||||
model = banner,
|
||||
contentDescription = stringResource(id = R.string.profile_image),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
@@ -682,11 +694,13 @@ fun TabNotesConversations(user: User, accountViewModel: AccountViewModel, navCon
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TabFollows(user: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
fun TabFollows(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val feedViewModel: NostrUserProfileFollowsUserFeedViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
feedViewModel.refresh()
|
||||
val userState by baseUser.live().follows.observeAsState()
|
||||
|
||||
LaunchedEffect(userState) {
|
||||
feedViewModel.invalidateData()
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
@@ -699,11 +713,13 @@ fun TabFollows(user: User, accountViewModel: AccountViewModel, navController: Na
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TabFollowers(user: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
fun TabFollowers(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val feedViewModel: NostrUserProfileFollowersUserFeedViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
feedViewModel.refresh()
|
||||
val userState by baseUser.live().follows.observeAsState()
|
||||
|
||||
LaunchedEffect(userState) {
|
||||
feedViewModel.invalidateData()
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
@@ -716,41 +732,39 @@ fun TabFollowers(user: User, accountViewModel: AccountViewModel, navController:
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TabReceivedZaps(user: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
if (accountState != null) {
|
||||
val feedViewModel: NostrUserProfileZapsFeedViewModel = viewModel()
|
||||
fun TabReceivedZaps(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val feedViewModel: NostrUserProfileZapsFeedViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
feedViewModel.refresh()
|
||||
}
|
||||
val userState by baseUser.live().zaps.observeAsState()
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp)
|
||||
) {
|
||||
LnZapFeedView(feedViewModel, accountViewModel, navController)
|
||||
}
|
||||
LaunchedEffect(userState) {
|
||||
feedViewModel.invalidateData()
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp)
|
||||
) {
|
||||
LnZapFeedView(feedViewModel, accountViewModel, navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TabReports(user: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||
if (accountState != null) {
|
||||
val feedViewModel: NostrUserProfileReportFeedViewModel = viewModel()
|
||||
fun TabReports(baseUser: User, accountViewModel: AccountViewModel, navController: NavController) {
|
||||
val feedViewModel: NostrUserProfileReportFeedViewModel = viewModel()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
feedViewModel.refresh()
|
||||
}
|
||||
val userState by baseUser.live().reports.observeAsState()
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp)
|
||||
) {
|
||||
FeedView(feedViewModel, accountViewModel, navController, null)
|
||||
}
|
||||
LaunchedEffect(userState) {
|
||||
feedViewModel.invalidateData()
|
||||
}
|
||||
|
||||
Column(Modifier.fillMaxHeight()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 0.dp)
|
||||
) {
|
||||
FeedView(feedViewModel, accountViewModel, navController, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,11 +153,16 @@ fun LoginPage(accountViewModel: AccountStateViewModel) {
|
||||
onCheckedChange = { acceptedTerms.value = it }
|
||||
)
|
||||
|
||||
val regularText =
|
||||
SpanStyle(color = MaterialTheme.colors.onBackground)
|
||||
|
||||
val clickableTextStyle =
|
||||
SpanStyle(color = MaterialTheme.colors.primary)
|
||||
|
||||
val annotatedTermsString = buildAnnotatedString {
|
||||
append(stringResource(R.string.i_accept_the))
|
||||
withStyle(regularText) {
|
||||
append(stringResource(R.string.i_accept_the))
|
||||
}
|
||||
|
||||
withStyle(clickableTextStyle) {
|
||||
pushStringAnnotation("openTerms", "")
|
||||
|
||||
Reference in New Issue
Block a user