amethyst/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt

379 wiersze
14 KiB
Kotlin
Czysty Zwykły widok Historia

2024-01-06 15:44:32 +00:00
/**
2024-02-15 23:31:26 +00:00
* Copyright (c) 2024 Vitor Pamplona
2024-01-06 15:44:32 +00:00
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
2023-02-27 15:52:39 +00:00
package com.vitorpamplona.amethyst.ui.screen.loggedIn
import androidx.compose.foundation.ExperimentalFoundationApi
2023-09-24 19:10:50 +00:00
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
2023-09-24 19:10:50 +00:00
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
2023-09-24 19:10:50 +00:00
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
2023-09-29 17:57:10 +00:00
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
2023-09-29 17:57:10 +00:00
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
2023-09-24 19:10:50 +00:00
import androidx.compose.runtime.derivedStateOf
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
2023-09-24 19:10:50 +00:00
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.res.stringResource
2023-09-24 19:10:50 +00:00
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
2023-11-23 15:46:56 +00:00
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2023-09-24 19:10:50 +00:00
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.map
import androidx.lifecycle.viewmodel.compose.viewModel
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.note.elements.AddButton
2023-02-27 15:52:39 +00:00
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel
2023-09-24 19:10:50 +00:00
import com.vitorpamplona.amethyst.ui.screen.NostrHiddenWordsFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.NostrSpammerAccountsFeedViewModel
2024-03-02 20:57:49 +00:00
import com.vitorpamplona.amethyst.ui.screen.RefresheableBox
2023-06-01 19:16:03 +00:00
import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView
2023-09-24 19:10:50 +00:00
import com.vitorpamplona.amethyst.ui.screen.StringFeedView
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
2023-10-30 16:04:10 +00:00
import com.vitorpamplona.amethyst.ui.theme.ButtonPadding
2023-09-29 17:57:10 +00:00
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
2023-09-24 22:36:43 +00:00
import com.vitorpamplona.amethyst.ui.theme.HorzPadding
2023-09-24 19:10:50 +00:00
import com.vitorpamplona.amethyst.ui.theme.StdPadding
2023-07-06 14:22:44 +00:00
import com.vitorpamplona.amethyst.ui.theme.TabRowHeight
2023-09-24 19:10:50 +00:00
import com.vitorpamplona.amethyst.ui.theme.placeholderText
import kotlinx.coroutines.launch
@Composable
2024-01-06 15:44:32 +00:00
fun HiddenUsersScreen(
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
2024-01-06 15:44:32 +00:00
) {
val hiddenFeedViewModel: NostrHiddenAccountsFeedViewModel =
viewModel(
factory = NostrHiddenAccountsFeedViewModel.Factory(accountViewModel.account),
)
val hiddenWordsFeedViewModel: NostrHiddenWordsFeedViewModel =
viewModel(
factory = NostrHiddenWordsFeedViewModel.Factory(accountViewModel.account),
)
2023-09-24 19:10:50 +00:00
val spammerFeedViewModel: NostrSpammerAccountsFeedViewModel =
viewModel(
factory = NostrSpammerAccountsFeedViewModel.Factory(accountViewModel.account),
)
WatchAccountAndBlockList(accountViewModel = accountViewModel) {
hiddenFeedViewModel.invalidateData()
spammerFeedViewModel.invalidateData()
hiddenWordsFeedViewModel.invalidateData()
}
HiddenUsersScreen(
hiddenFeedViewModel,
hiddenWordsFeedViewModel,
spammerFeedViewModel,
accountViewModel,
nav,
)
2023-06-01 19:16:03 +00:00
}
2023-06-01 19:16:03 +00:00
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun HiddenUsersScreen(
hiddenFeedViewModel: NostrHiddenAccountsFeedViewModel,
hiddenWordsViewModel: NostrHiddenWordsFeedViewModel,
spammerFeedViewModel: NostrSpammerAccountsFeedViewModel,
accountViewModel: AccountViewModel,
nav: (String) -> Unit,
2023-06-01 19:16:03 +00:00
) {
val lifeCycleOwner = LocalLifecycleOwner.current
2023-06-01 19:16:03 +00:00
DisposableEffect(lifeCycleOwner) {
val observer =
LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_RESUME) {
println("Hidden Users Start")
hiddenWordsViewModel.invalidateData()
hiddenFeedViewModel.invalidateData()
spammerFeedViewModel.invalidateData()
}
}
lifeCycleOwner.lifecycle.addObserver(observer)
onDispose { lifeCycleOwner.lifecycle.removeObserver(observer) }
2024-01-06 15:44:32 +00:00
}
Column(Modifier.fillMaxHeight()) {
val pagerState = rememberPagerState { 3 }
val coroutineScope = rememberCoroutineScope()
var warnAboutReports by remember {
mutableStateOf(accountViewModel.account.warnAboutPostsWithReports)
}
var filterSpam by remember { mutableStateOf(accountViewModel.account.filterSpamFromStrangers) }
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = warnAboutReports,
onCheckedChange = {
warnAboutReports = it
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
},
)
Text(stringResource(R.string.warn_when_posts_have_reports_from_your_follows))
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(
checked = filterSpam,
onCheckedChange = {
filterSpam = it
accountViewModel.account.updateOptOutOptions(warnAboutReports, filterSpam)
},
)
2024-01-06 15:44:32 +00:00
Text(stringResource(R.string.filter_spam_from_strangers))
}
ScrollableTabRow(
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.onBackground,
edgePadding = 8.dp,
selectedTabIndex = pagerState.currentPage,
modifier = TabRowHeight,
divider = { HorizontalDivider(thickness = DividerThickness) },
) {
Tab(
selected = pagerState.currentPage == 0,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } },
text = { Text(text = stringResource(R.string.blocked_users)) },
)
Tab(
selected = pagerState.currentPage == 1,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } },
text = { Text(text = stringResource(R.string.spamming_users)) },
)
Tab(
selected = pagerState.currentPage == 2,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(2) } },
text = { Text(text = stringResource(R.string.hidden_words)) },
)
}
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> RefreshingFeedUserFeedView(hiddenFeedViewModel, accountViewModel, nav)
1 -> RefreshingFeedUserFeedView(spammerFeedViewModel, accountViewModel, nav)
2 -> HiddenWordsFeed(hiddenWordsViewModel, accountViewModel)
}
}
2024-01-06 15:44:32 +00:00
}
}
2023-06-01 19:16:03 +00:00
2023-09-24 19:10:50 +00:00
@Composable
private fun HiddenWordsFeed(
hiddenWordsViewModel: NostrHiddenWordsFeedViewModel,
accountViewModel: AccountViewModel,
2023-09-24 19:10:50 +00:00
) {
2024-03-02 20:57:49 +00:00
RefresheableBox(hiddenWordsViewModel, false) {
StringFeedView(
hiddenWordsViewModel,
post = { AddMuteWordTextField(accountViewModel) },
) {
MutedWordHeader(tag = it, account = accountViewModel)
}
2023-09-24 19:10:50 +00:00
}
}
@Composable
private fun AddMuteWordTextField(accountViewModel: AccountViewModel) {
Row {
val currentWordToAdd = remember { mutableStateOf("") }
val hasChanged by remember { derivedStateOf { currentWordToAdd.value != "" } }
2023-09-24 19:10:50 +00:00
OutlinedTextField(
value = currentWordToAdd.value,
onValueChange = { currentWordToAdd.value = it },
label = { Text(text = stringResource(R.string.hide_new_word_label)) },
modifier = Modifier.fillMaxWidth().padding(10.dp),
placeholder = {
Text(
text = stringResource(R.string.hide_new_word_label),
color = MaterialTheme.colorScheme.placeholderText,
)
},
keyboardOptions =
KeyboardOptions.Default.copy(
imeAction = ImeAction.Send,
capitalization = KeyboardCapitalization.Sentences,
),
keyboardActions =
KeyboardActions(
onSend = {
if (hasChanged) {
accountViewModel.hide(currentWordToAdd.value)
currentWordToAdd.value = ""
}
},
),
singleLine = true,
trailingIcon = {
AddButton(isActive = hasChanged, modifier = HorzPadding) {
accountViewModel.hide(currentWordToAdd.value)
currentWordToAdd.value = ""
}
},
2023-09-24 19:10:50 +00:00
)
}
2023-09-24 19:10:50 +00:00
}
2023-06-01 19:16:03 +00:00
@Composable
fun WatchAccountAndBlockList(
accountViewModel: AccountViewModel,
invalidate: () -> Unit,
2023-06-01 19:16:03 +00:00
) {
val accountState by accountViewModel.accountLiveData.observeAsState()
val blockListState by accountViewModel.account.flowHiddenUsers.collectAsStateWithLifecycle()
2023-06-01 19:16:03 +00:00
LaunchedEffect(accountViewModel, accountState, blockListState) {
invalidate()
}
2023-06-01 19:16:03 +00:00
}
2023-09-24 19:10:50 +00:00
@Composable
2024-01-06 15:44:32 +00:00
fun MutedWordHeader(
tag: String,
modifier: Modifier = StdPadding,
account: AccountViewModel,
2024-01-06 15:44:32 +00:00
) {
Column(
Modifier.fillMaxWidth(),
) {
Column(modifier = modifier) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Text(
tag,
fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f),
)
2024-01-06 15:44:32 +00:00
MutedWordActionOptions(tag, account)
}
}
}
2023-09-24 19:10:50 +00:00
}
@Composable
fun MutedWordActionOptions(
word: String,
accountViewModel: AccountViewModel,
2023-09-24 19:10:50 +00:00
) {
val isMutedWord by
accountViewModel.account.liveHiddenUsers
.map { word in it.hiddenWords }
.distinctUntilChanged()
.observeAsState()
2023-09-24 19:10:50 +00:00
if (isMutedWord == true) {
ShowWordButton {
if (!accountViewModel.isWriteable()) {
accountViewModel.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_show_word,
)
} else {
accountViewModel.showWord(word)
}
}
} else {
HideWordButton {
if (!accountViewModel.isWriteable()) {
accountViewModel.toast(
R.string.read_only_user,
R.string.login_with_a_private_key_to_be_able_to_hide_word,
)
} else {
accountViewModel.hideWord(word)
}
}
2024-01-06 15:44:32 +00:00
}
2023-09-24 19:10:50 +00:00
}
@Composable
fun HideWordButton(onClick: () -> Unit) {
Button(
modifier = Modifier.padding(horizontal = 3.dp),
onClick = onClick,
shape = ButtonBorder,
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
),
contentPadding = ButtonPadding,
) {
Text(text = stringResource(R.string.block_only), color = Color.White)
}
2023-09-24 19:10:50 +00:00
}
@Composable
2024-01-06 15:44:32 +00:00
fun ShowWordButton(
text: Int = R.string.unblock,
onClick: () -> Unit,
2024-01-06 15:44:32 +00:00
) {
Button(
modifier = Modifier.padding(start = 3.dp),
onClick = onClick,
shape = ButtonBorder,
colors =
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
),
contentPadding = ButtonPadding,
) {
Text(text = stringResource(text), color = Color.White, textAlign = TextAlign.Center)
}
2023-09-24 19:10:50 +00:00
}