Merge remote-tracking branch 'origin/HEAD' into less_memory_test_branch
This commit is contained in:
@@ -30,6 +30,8 @@ Amethyst brings the best social network to your Android phone. Just insert your
|
||||
- [x] Long-form Content (NIP-23)
|
||||
- [x] Parameterized Replaceable Events (NIP-33)
|
||||
- [x] Online Relay Search (NIP-50)
|
||||
- [x] Internationalization
|
||||
- [x] Badges (NIP-58)
|
||||
- [ ] Local Database
|
||||
- [ ] View Individual Reactions (Like, Boost, Zaps, Reports) per Post
|
||||
- [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51)
|
||||
@@ -41,9 +43,7 @@ Amethyst brings the best social network to your Android phone. Just insert your
|
||||
- [ ] Events with a Subject (NIP-14)
|
||||
- [ ] Workspaces
|
||||
- [ ] Expiration Support (NIP-40)
|
||||
- [ ] Internationalization
|
||||
- [ ] Infinity Scroll
|
||||
- [ ] Badges (NIP-58)
|
||||
- [ ] Relay List Metadata (NIP-65)
|
||||
- [ ] Signing Requests (NIP-46)
|
||||
- [ ] Delegated Event Signing (NIP-26)
|
||||
|
||||
+2
-2
@@ -12,8 +12,8 @@ android {
|
||||
applicationId "com.vitorpamplona.amethyst"
|
||||
minSdk 26
|
||||
targetSdk 33
|
||||
versionCode 90
|
||||
versionName "0.24.1"
|
||||
versionCode 91
|
||||
versionName "0.24.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
||||
@@ -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", "")
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="app_name_release" translatable="false">Amethyst</string>
|
||||
<string name="app_name_debug" translatable="false">Amethyst debug</string>
|
||||
<string name="point_to_the_qr_code">Richt naar de QR-Code</string>
|
||||
<string name="show_qr">QR tonen</string>
|
||||
<string name="profile_image">Profielafbeelding</string>
|
||||
<string name="scan_qr">Scan QR</string>
|
||||
<string name="show_anyway">Laat evengoed zien</string>
|
||||
<string name="post_was_flagged_as_inappropriate_by">Bericht was gemarkeerd als ongepast door</string>
|
||||
<string name="post_not_found">Bericht niet gevonden</string>
|
||||
<string name="channel_image">Kanaalafbeelding</string>
|
||||
<string name="referenced_event_not_found">Verwezen event niet gevonden</string>
|
||||
<string name="could_not_decrypt_the_message">Kon het bericht niet ontcijferen</string>
|
||||
<string name="group_picture">Groepafbeelding</string>
|
||||
<string name="explicit_content">Expliciete inhoud</string>
|
||||
<string name="spam">Spam</string>
|
||||
<string name="impersonation">Imitatie</string>
|
||||
<string name="illegal_behavior">Illegaal gedrag</string>
|
||||
<string name="unknown">Onbekend</string>
|
||||
<string name="relay_icon">Relay icoon</string>
|
||||
<string name="unknown_author">Onbekende auteur</string>
|
||||
<string name="copy_text">Kopieer tekst</string>
|
||||
<string name="copy_user_pubkey">Kopieer auteur ID</string>
|
||||
<string name="copy_note_id">Kopieer note ID</string>
|
||||
<string name="broadcast">Verzenden</string>
|
||||
<string name="block_hide_user"><![CDATA[Block & verberg gebruiker]]></string>
|
||||
<string name="report_spam_scam">Meld spam / scam</string>
|
||||
<string name="report_impersonation">Meld imitatie</string>
|
||||
<string name="report_explicit_content">Rapporteer expliciete inhoud</string>
|
||||
<string name="report_illegal_behaviour">Rapporteer illegaal gedrag</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_reply">Login met een privésleutel om te reageren</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_boost_posts">Login met een privésleutel om berichten te boosten</string>
|
||||
<string name="login_with_a_private_key_to_like_posts">Login met een privésleutel om berichten te liken</string>
|
||||
<string name="no_zap_amount_setup_long_press_to_change">Geen Zap bedrag. Houdt ingedrukt om te veranderen</string>
|
||||
<string name="login_with_a_private_key_to_be_able_to_send_zaps">Login met een privésleutel om Zaps te versturen</string>
|
||||
<string name="zaps">Zaps</string>
|
||||
<string name="view_count">Bekijk telling</string>
|
||||
<string name="boost">Boost</string>
|
||||
<string name="boosted">boosted</string>
|
||||
<string name="quote">Quote</string>
|
||||
<string name="new_amount_in_sats">Nieuw bedrag in sats</string>
|
||||
<string name="add">Toevoegen</string>
|
||||
<string name="replying_to">"reageren op "</string>
|
||||
<string name="and">" en "</string>
|
||||
<string name="in_channel">"in kanaal "</string>
|
||||
<string name="profile_banner">Profielbanner</string>
|
||||
<string name="following">" Volgend"</string>
|
||||
<string name="followers">" Volgers"</string>
|
||||
<string name="profile">Profiel</string>
|
||||
<string name="security_filters">Beveiligingsfilters</string>
|
||||
<string name="log_out">Uitloggen</string>
|
||||
<string name="show_more">Meer</string>
|
||||
<string name="lightning_invoice">Lightning invoice</string>
|
||||
<string name="pay">Betalen</string>
|
||||
<string name="lightning_tips">Lightning Tips</string>
|
||||
<string name="note_to_receiver">Notitie aan ontvanger</string>
|
||||
<string name="thank_you_so_much">Hartelijk bedankt!</string>
|
||||
<string name="amount_in_sats">Bedrag in sats</string>
|
||||
<string name="send_sats">Verstuur sats</string>
|
||||
<string name="never_translate_from">"Nooit vertalen van "</string>
|
||||
<string name="error_parsing_preview_for">"Foutieve parsing preview voor %1$s : %2$s"</string>
|
||||
<string name="preview_card_image_for">"Voorbeeld kaartafbeelding voor %1$s"</string>
|
||||
<string name="new_channel">Nieuw kanaal</string>
|
||||
<string name="channel_name">Kanaalnaam</string>
|
||||
<string name="my_awesome_group">Mijn geweldige groep</string>
|
||||
<string name="picture_url">Afbeelding URL</string>
|
||||
<string name="description">Omschrijving</string>
|
||||
<string name="about_us">"Over ons.. "</string>
|
||||
<string name="what_s_on_your_mind">Waar denk je aan?</string>
|
||||
<string name="post">Verzenden</string>
|
||||
<string name="save">Opslaan</string>
|
||||
<string name="create">Maken</string>
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="failed_to_upload_the_image">Het uploaden van de afbeelding is mislukt</string>
|
||||
<string name="relay_address">Relay addres</string>
|
||||
<string name="posts">Berichten</string>
|
||||
<string name="errors">Errors</string>
|
||||
<string name="home_feed">Beginfeed</string>
|
||||
<string name="private_message_feed">Privéberichten</string>
|
||||
<string name="public_chat_feed">Publieke chats</string>
|
||||
<string name="global_feed">Globale feed</string>
|
||||
<string name="search_feed">Zoeken</string>
|
||||
<string name="add_a_relay">AVoeg een relay toe</string>
|
||||
<string name="display_name">Naam</string>
|
||||
<string name="my_display_name">Mijn naam</string>
|
||||
<string name="username">Gebruikersnaam</string>
|
||||
<string name="my_username">Mijn gebruikersnaam</string>
|
||||
<string name="about_me">Over mij</string>
|
||||
<string name="avatar_url">Avatar URL</string>
|
||||
<string name="banner_url">Banner URL</string>
|
||||
<string name="website_url">Website URL</string>
|
||||
<string name="ln_address">LN Address</string>
|
||||
<string name="ln_url_outdated">LN URL (verouderd)</string>
|
||||
<string name="image_saved_to_the_gallery">Afbeelding opgeslagen in de galerij</string>
|
||||
<string name="failed_to_save_the_image">De afbeelding is niet opgeslagen</string>
|
||||
<string name="upload_image">Afbeelding uploaden</string>
|
||||
<string name="uploading">Uploaden…</string>
|
||||
<string name="user_does_not_have_a_lightning_address_setup_to_receive_sats">De gebruiker heeft geen Lightning Adress ingesteld om sats te ontvangen</string>
|
||||
<string name="reply_here">"hier reageren.. "</string>
|
||||
<string name="copies_the_note_id_to_the_clipboard_for_sharing">Kopieert de Notitie ID naar klembord om te delen</string>
|
||||
<string name="copy_channel_id_note_to_the_clipboard">Kopieer kanaal ID (Notitie) naar klembord</string>
|
||||
<string name="edits_the_channel_metadata">Past de kanaal-metadata aan</string>
|
||||
<string name="join">Lid worden</string>
|
||||
<string name="known">Bekend</string>
|
||||
<string name="new_requests">Nieuw verzoek</string>
|
||||
<string name="blocked_users">Geblokeerde gebruikers</string>
|
||||
<string name="new_threads">Nieuwe draadjes</string>
|
||||
<string name="conversations">Gesprekken</string>
|
||||
<string name="notes">Notities</string>
|
||||
<string name="replies">Reacties</string>
|
||||
<string name="follows">"Volgend"</string>
|
||||
<string name="reports">"Rapporten"</string>
|
||||
<string name="more_options">Meer opties</string>
|
||||
<string name="relays">" Relays"</string>
|
||||
<string name="website">Website</string>
|
||||
<string name="lightning_address">Lightning Address</string>
|
||||
<string name="copies_the_nsec_id_your_password_to_the_clipboard_for_backup">Kopieert het NSec ID (uw wachtwoord) naar het klembord voor back-up.</string>
|
||||
<string name="copy_private_key_to_the_clipboard">Privésleutel kopiëren naar het klembord</string>
|
||||
<string name="copies_the_public_key_to_the_clipboard_for_sharing">Kopieert de publieke sleutel naar het klembord om te delen</string>
|
||||
<string name="copy_public_key_npub_to_the_clipboard">Kopieer publieke sleutel (NPub) naar het klembord</string>
|
||||
<string name="send_a_direct_message">Stuur een privébericht</string>
|
||||
<string name="edits_the_user_s_metadata">Bewerkt de metagegevens van de gebruiker</string>
|
||||
<string name="follow">Volgen</string>
|
||||
<string name="unblock">Deblokkeren</string>
|
||||
<string name="copy_user_id">Kopieer gebruikers ID</string>
|
||||
<string name="unblock_user">Deblokkeer gebruiker</string>
|
||||
<string name="npub_hex_username">"npub, hex, gebruikersnaam "</string>
|
||||
<string name="clear">Wissen</string>
|
||||
<string name="app_logo">App logo</string>
|
||||
<string name="nsec_npub_hex_private_key">nsec / npub / hex privésleutel</string>
|
||||
<string name="show_password">Toon wachtwoord</string>
|
||||
<string name="hide_password">Verberg wachtwoord</string>
|
||||
<string name="invalid_key">Ongeldige sleutel</string>
|
||||
<string name="i_accept_the">"Ik accepteer de "</string>
|
||||
<string name="terms_of_use">gebruiksvoorwaarden</string>
|
||||
<string name="acceptance_of_terms_is_required">Accepteren van de voorwaarden is vereist</string>
|
||||
<string name="key_is_required">Sleutel is vereist</string>
|
||||
<string name="login">Inloggen</string>
|
||||
<string name="generate_a_new_key">Genereer een nieuwe sleutel</string>
|
||||
<string name="loading_feed">Feed laden</string>
|
||||
<string name="error_loading_replies">"Foutmelding bij het laden reacties: "</string>
|
||||
<string name="try_again">Opnieuw proberen</string>
|
||||
<string name="feed_is_empty">Feed is leeg.</string>
|
||||
<string name="refresh">Verversen</string>
|
||||
<string name="created">gemaakt</string>
|
||||
<string name="with_description_of">met beschrijving van</string>
|
||||
<string name="and_picture">en afbeelding</string>
|
||||
<string name="changed_chat_name_to">heeft chatnaam veranderd naar</string>
|
||||
<string name="description_to">omschrijving naar</string>
|
||||
<string name="and_picture_to">en afbeelding naar</string>
|
||||
<string name="leave">Verlaten</string>
|
||||
<string name="unfollow">Ontvolgen</string>
|
||||
<string name="channel_created">Kanaal gemaakt</string>
|
||||
<string name="channel_information_changed_to">"Kanaalinformatie veranderd naar"</string>
|
||||
<string name="public_chat">Publieke chat</string>
|
||||
<string name="posts_received">berichten ontvangen</string>
|
||||
<string name="remove">Verwijderen</string>
|
||||
<string name="sats" translatable="false">sats</string>
|
||||
<string name="auto">Auto</string>
|
||||
<string name="translated_from">vertaald van</string>
|
||||
<string name="to">naar</string>
|
||||
<string name="show_in">Laten zien in</string>
|
||||
<string name="first">eerste</string>
|
||||
<string name="always_translate_to">Altijd vertalen naar</string>
|
||||
<string name="nip_05" translatable="false">NIP-05</string>
|
||||
<string name="lnurl" translatable="false">LNURL...</string>
|
||||
<string name="never">nooit</string>
|
||||
<string name="now">nu</string>
|
||||
<string name="h">h</string>
|
||||
<string name="m">m</string>
|
||||
<string name="d">d</string>
|
||||
<string name="nudity">Naaktheid</string>
|
||||
<string name="profanity_hateful_speech">Godslastering / haatdragende taal</string>
|
||||
<string name="report_hateful_speech">Haatdragende taal rapporteren</string>
|
||||
<string name="report_nudity_porn">Naaktheid / porno rapporteren</string>
|
||||
<string name="others">anderen</string>
|
||||
<string name="mark_all_known_as_read">Alle bekende als gelezen markeren</string>
|
||||
<string name="mark_all_new_as_read">Alle nieuwe als gelezen markeren</string>
|
||||
<string name="mark_all_as_read">Alles als gelezen markeren</string>
|
||||
<string name="backup_keys">Backup sleutels</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos">
|
||||
## Sleutel back-up en veiligheidstips
|
||||
\n\nUw account is beveiligd met een privésleutel. De sleutel is een lange, willekeurige reeks die begint met **nsec1**. Iedereen die toegang heeft tot uw privésleutel kan inhoud publiceren met uw identiteit.
|
||||
\n\n- Voer uw privésleutel **nooit** in een website of software die u niet vertrouwt.
|
||||
\n- Amethyst ontwikkelaars zullen u **nooit** om uw privésleutel vragen.
|
||||
\n- **Bewaar** een veilige back-up van uw privésleutel voor accountherstel. Wij raden u aan een wachtwoordmanager te gebruiken.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Privésleutel (nsec) gekopieerd naar klembord</string>
|
||||
<string name="copy_my_secret_key">Kopieer mijn privésleutel</string>
|
||||
<string name="biometric_authentication_failed">Authenticatie mislukt</string>
|
||||
<string name="biometric_error">Error</string>
|
||||
<string name="badge_created_by">"Gemaakt door %1$s"</string>
|
||||
<string name="badge_award_image_for">"Badge award afbeelding voor %1$s"</string>
|
||||
<string name="new_badge_award_notif">Je hebt een nieuwe Badge award ontvangen</string>
|
||||
<string name="award_granted_to">Badge award gegeven aan</string>
|
||||
<string name="copied_note_text_to_clipboard">Notitie tekst gekopieerd naar klembord</string>
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">@npub auteur gekopieerd naar klembord</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Notitie ID (@note1) gekopieerd naar klembord</string>
|
||||
<string name="select_text_dialog_top">Selecteer tekst</string>
|
||||
<string name="quick_action_select">Selecteer</string>
|
||||
<string name="quick_action_share_browser_link">Deel browserlink</string>
|
||||
<string name="quick_action_share">Delen</string>
|
||||
<string name="quick_action_copy_user_id">Auteur ID</string>
|
||||
<string name="quick_action_copy_note_id">Notitie ID</string>
|
||||
<string name="quick_action_copy_text">Kopieer tekst</string>
|
||||
<string name="quick_action_delete">Verwijderen</string>
|
||||
<string name="quick_action_unfollow">Ontvolgen</string>
|
||||
<string name="quick_action_follow">Volgen</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Verzoek om te verwijderen</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst zal vragen om uw notitie te verwijderen van de relays waarmee u momenteel verbonden bent. Er is geen garantie dat uw notitie permanent wordt verwijderd van deze relays, of van andere relays waar het kan worden opgeslagen.</string>
|
||||
</resources>
|
||||
@@ -170,4 +170,41 @@
|
||||
<string name="report_hateful_speech">Denunciar discurso de ódio</string>
|
||||
<string name="report_nudity_porn">Denunciar nudez / pornografia</string>
|
||||
<string name="others">outros</string>
|
||||
<string name="mark_all_known_as_read">Marcar todos conhecidos como lidos</string>
|
||||
<string name="mark_all_new_as_read">Marcar todas novas solicitações como lidas</string>
|
||||
<string name="mark_all_as_read">Marcar todas como lidas</string>
|
||||
<string name="biometric_error">Erro</string>
|
||||
<string name="account_backup_tips_md">
|
||||
## Backup de chaves e dicas de segurança
|
||||
\n\nSua conta é protegida por uma chave secreta. A chave é uma string aleatória longa começando com **nsec1**. Qualquer pessoa que tenha acesso à sua chave secreta pode publicar conteúdo usando sua identidade.
|
||||
\n\n- **Não** coloque sua chave secreta em qualquer site ou software em que não confie.
|
||||
\n- Os desenvolvedores do Amethyst **nunca** pedirão sua chave secreta.
|
||||
\n- **Faça** um backup seguro de sua chave secreta para recuperação de conta. Recomendamos o uso de um gerenciador de senhas.
|
||||
</string>
|
||||
<string name="secret_key_copied_to_clipboard">Chave secreta (nsec) copiada</string>
|
||||
<string name="copy_my_secret_key">Copiar minha chave secreta</string>
|
||||
<string name="biometric_authentication_failed">Autenticação falhou</string>
|
||||
<string name="badge_created_by">Criado por %1$s</string>
|
||||
<string name="badge_award_image_for">Imagem da medalha para %1$s</string>
|
||||
<string name="new_badge_award_notif">Você recebeu uma nova medalha</string>
|
||||
<string name="award_granted_to">Medalha concedida para</string>
|
||||
<string name="copied_note_text_to_clipboard">Texto copiado</string>
|
||||
<string name="copied_user_id_to_clipboard">Copiado @npub do autor</string>
|
||||
<string name="copied_note_id_to_clipboard">Copiado ID da nota (@note1)</string>
|
||||
<string name="select_text_dialog_top">Selecionar texto</string>
|
||||
<string name="quick_action_select">Selecionar</string>
|
||||
<string name="quick_action_share_browser_link">Compartilhar link do navegador</string>
|
||||
<string name="quick_action_share">Compartilhar</string>
|
||||
<string name="quick_action_copy_user_id">ID do Autor</string>
|
||||
<string name="quick_action_copy_note_id">ID da Nota</string>
|
||||
<string name="quick_action_copy_text">Copiar texto</string>
|
||||
<string name="quick_action_delete">Excluir</string>
|
||||
<string name="quick_action_unfollow">Remover seguidor</string>
|
||||
<string name="quick_action_follow">Seguir</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Pedir para excluir</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst solicitará que sua nota seja excluída dos relays aos quais você está conectado no momento. Não há garantia de que sua nota será excluída permanentemente desses relays ou de outros relays onde possa estar armazenada.</string>
|
||||
<string name="backup_keys">Copia de segurança das chaves</string>
|
||||
<string name="private_conversation_notification"><Não foi possível descriptografar a mensagem privada>\\n\\nVocê foi citado em uma conversa privada/criptografada entre %1$s and %2$s.</string>
|
||||
<string name="quick_action_delete_button">Excluir</string>
|
||||
<string name="quick_action_dont_show_again_button">Não mostar novamente</string>
|
||||
</resources>
|
||||
|
||||
@@ -178,13 +178,7 @@
|
||||
<string name="mark_all_new_as_read">Mark all New as read</string>
|
||||
<string name="mark_all_as_read">Mark all as read</string>
|
||||
<string name="backup_keys">Backup Keys</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos">
|
||||
## Key Backup and Safety Tips
|
||||
\n\nYour account is secured by a secret key. The key is long random string starting with **nsec1**. Anyone who has access to your secret key can publish content using your identity.
|
||||
\n\n- Do **not** put your secret key in any website or software you do not trust.
|
||||
\n- Amethyst developers will **never** ask you for your secret key.
|
||||
\n- **Do** keep a secure backup of your secret key for account recovery. We recommend using a password manager.
|
||||
</string>
|
||||
<string name="account_backup_tips_md" tools:ignore="Typos">" ## Key Backup and Safety Tips Your account is secured by a secret key. The key is long random string starting with **nsec1**. Anyone who has access to your secret key can publish content using your identity. - Do **not** put your secret key in any website or software you do not trust. - Amethyst developers will **never** ask you for your secret key. - **Do** keep a secure backup of your secret key for account recovery. We recommend using a password manager. "</string>
|
||||
<string name="secret_key_copied_to_clipboard">Secret key (nsec) copied to clipboard</string>
|
||||
<string name="copy_my_secret_key">Copy my secret key</string>
|
||||
<string name="biometric_authentication_failed">Authentication failed</string>
|
||||
@@ -219,6 +213,8 @@
|
||||
<string name="mastodon_proof_url_template" translatable="false">https://<server>/<user>/<proof post></string>
|
||||
<string name="twitter_proof_url_template" translatable="false">https://twitter.com/<user>/status/<proof post></string>
|
||||
|
||||
|
||||
<string name="private_conversation_notification">"<Unable to decrypt private message>\n\nYou were cited in a private/encrypted conversation between %1$s and %2$s."</string>
|
||||
<string name="quick_action_delete_button">Delete</string>
|
||||
<string name="quick_action_dont_show_again_button">Don\'t show again</string>
|
||||
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user