mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-28 07:07:06 +02:00
Merge pull request #275 from maxmoney21m/feature/216-merge-reports
Add separate screen for report & block
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -68,6 +68,7 @@ render.experimental.xml
|
||||
.idea/**/caches/
|
||||
.idea/**/libraries/
|
||||
.idea/**/shelf/
|
||||
.idea/**/codeStyles
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/.name
|
||||
|
@@ -47,7 +47,8 @@ private object PrefKeys {
|
||||
const val TRANSLATE_TO = "translateTo"
|
||||
const val ZAP_AMOUNTS = "zapAmounts"
|
||||
const val LATEST_CONTACT_LIST = "latestContactList"
|
||||
const val HIDE_DELETE_REQUEST_INFO = "hideDeleteRequestInfo"
|
||||
const val HIDE_DELETE_REQUEST_DIALOG = "hide_delete_request_dialog"
|
||||
const val HIDE_BLOCK_ALERT_DIALOG = "hide_block_alert_dialog"
|
||||
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||
}
|
||||
|
||||
@@ -183,7 +184,8 @@ object LocalPreferences {
|
||||
putString(PrefKeys.TRANSLATE_TO, account.translateTo)
|
||||
putString(PrefKeys.ZAP_AMOUNTS, gson.toJson(account.zapAmountChoices))
|
||||
putString(PrefKeys.LATEST_CONTACT_LIST, Event.gson.toJson(account.backupContactList))
|
||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, account.hideDeleteRequestInfo)
|
||||
putBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, account.hideDeleteRequestDialog)
|
||||
putBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, account.hideBlockAlertDialog)
|
||||
putString(PrefKeys.DISPLAY_NAME, account.userProfile().toBestDisplayName())
|
||||
putString(PrefKeys.PROFILE_PICTURE_URL, account.userProfile().profilePicture())
|
||||
}.apply()
|
||||
@@ -230,7 +232,8 @@ object LocalPreferences {
|
||||
mapOf()
|
||||
}
|
||||
|
||||
val hideDeleteRequestInfo = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, false)
|
||||
val hideDeleteRequestDialog = getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
|
||||
val hideBlockAlertDialog = getBoolean(PrefKeys.HIDE_BLOCK_ALERT_DIALOG, false)
|
||||
|
||||
return Account(
|
||||
Persona(privKey = privKey?.toByteArray(), pubKey = pubKey.toByteArray()),
|
||||
@@ -241,7 +244,8 @@ object LocalPreferences {
|
||||
languagePreferences,
|
||||
translateTo,
|
||||
zapAmountChoices,
|
||||
hideDeleteRequestInfo,
|
||||
hideDeleteRequestDialog,
|
||||
hideBlockAlertDialog,
|
||||
latestContactList
|
||||
)
|
||||
}
|
||||
@@ -289,8 +293,8 @@ object LocalPreferences {
|
||||
stringPrefs.forEach { userPrefs.putString(it, appPrefs.getString(it, null)) }
|
||||
stringSetPrefs.forEach { userPrefs.putStringSet(it, appPrefs.getStringSet(it, null)) }
|
||||
userPrefs.putBoolean(
|
||||
PrefKeys.HIDE_DELETE_REQUEST_INFO,
|
||||
appPrefs.getBoolean(PrefKeys.HIDE_DELETE_REQUEST_INFO, false)
|
||||
PrefKeys.HIDE_DELETE_REQUEST_DIALOG,
|
||||
appPrefs.getBoolean(PrefKeys.HIDE_DELETE_REQUEST_DIALOG, false)
|
||||
)
|
||||
}.apply()
|
||||
}
|
||||
|
@@ -59,7 +59,8 @@ class Account(
|
||||
var languagePreferences: Map<String, String> = mapOf(),
|
||||
var translateTo: String = Locale.getDefault().language,
|
||||
var zapAmountChoices: List<Long> = listOf(500L, 1000L, 5000L),
|
||||
var hideDeleteRequestInfo: Boolean = false,
|
||||
var hideDeleteRequestDialog: Boolean = false,
|
||||
var hideBlockAlertDialog: Boolean = false,
|
||||
var backupContactList: ContactListEvent? = null
|
||||
) {
|
||||
var transientHiddenUsers: Set<String> = setOf()
|
||||
@@ -170,7 +171,7 @@ class Account(
|
||||
return LnZapRequestEvent.create(userPubKeyHex, userProfile().latestContactList?.relays()?.keys?.ifEmpty { null } ?: localRelays.map { it.url }.toSet(), loggedIn.privKey!!)
|
||||
}
|
||||
|
||||
fun report(note: Note, type: ReportEvent.ReportType) {
|
||||
fun report(note: Note, type: ReportEvent.ReportType, content: String = "") {
|
||||
if (!isWriteable()) return
|
||||
|
||||
if (note.hasReacted(userProfile(), "⚠️")) {
|
||||
@@ -185,7 +186,7 @@ class Account(
|
||||
}
|
||||
|
||||
note.event?.let {
|
||||
val event = ReportEvent.create(it, type, loggedIn.privKey!!)
|
||||
val event = ReportEvent.create(it, type, loggedIn.privKey!!, content = content)
|
||||
Client.send(event)
|
||||
LocalCache.consume(event, null)
|
||||
}
|
||||
@@ -553,8 +554,13 @@ class Account(
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
fun setHideDeleteRequestInfo() {
|
||||
hideDeleteRequestInfo = true
|
||||
fun setHideDeleteRequestDialog() {
|
||||
hideDeleteRequestDialog = true
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
fun setHideBlockAlertDialog() {
|
||||
hideBlockAlertDialog = true
|
||||
saveable.invalidateData()
|
||||
}
|
||||
|
||||
|
@@ -57,9 +57,13 @@ class ReportEvent(
|
||||
companion object {
|
||||
const val kind = 1984
|
||||
|
||||
fun create(reportedPost: EventInterface, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
||||
val content = ""
|
||||
|
||||
fun create(
|
||||
reportedPost: EventInterface,
|
||||
type: ReportType,
|
||||
privateKey: ByteArray,
|
||||
content: String = "",
|
||||
createdAt: Long = Date().time / 1000
|
||||
): ReportEvent {
|
||||
val reportPostTag = listOf("e", reportedPost.id(), type.name.lowercase())
|
||||
val reportAuthorTag = listOf("p", reportedPost.pubKey(), type.name.lowercase())
|
||||
|
||||
|
@@ -0,0 +1,101 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
||||
@Composable
|
||||
fun TextSpinner(label: String, placeholder: String, options: List<String>, onSelect: (Int) -> Unit, modifier: Modifier = Modifier) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
var optionsShowing by remember { mutableStateOf(false) }
|
||||
var currentText by remember { mutableStateOf(placeholder) }
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = currentText,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { Text(label) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null
|
||||
) {
|
||||
optionsShowing = true
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (optionsShowing) {
|
||||
options.isNotEmpty().also {
|
||||
SpinnerSelectionDialog(options = options, onDismiss = { optionsShowing = false }) {
|
||||
currentText = options[it]
|
||||
optionsShowing = false
|
||||
onSelect(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SpinnerSelectionDialog(options: List<String>, onDismiss: () -> Unit, onSelect: (Int) -> Unit) {
|
||||
Dialog(onDismissRequest = onDismiss) {
|
||||
Surface(
|
||||
border = BorderStroke(0.25.dp, Color.LightGray),
|
||||
shape = RoundedCornerShape(5.dp)
|
||||
) {
|
||||
LazyColumn() {
|
||||
itemsIndexed(options) { index, item ->
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp, 16.dp)
|
||||
.clickable {
|
||||
onSelect(index)
|
||||
}
|
||||
) {
|
||||
Text(text = item, color = MaterialTheme.colors.onSurface)
|
||||
}
|
||||
if (index < options.lastIndex) {
|
||||
Divider(color = Color.LightGray, thickness = 0.25.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -344,7 +344,7 @@ fun ChatroomMessageCompose(
|
||||
}
|
||||
}
|
||||
|
||||
NoteDropDownMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||
NoteQuickActionMenu(note, popupExpanded, { popupExpanded = false }, accountViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -59,6 +59,7 @@ import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
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.screen.loggedIn.ReportNoteDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.Following
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -779,6 +780,7 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val appContext = LocalContext.current.applicationContext
|
||||
val actContext = LocalContext.current
|
||||
var reportDialogShowing by remember { mutableStateOf(false) }
|
||||
|
||||
DropdownMenu(
|
||||
expanded = popupExpanded,
|
||||
@@ -832,49 +834,16 @@ fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit,
|
||||
}
|
||||
if (note.author != accountViewModel.accountLiveData.value?.account?.userProfile()) {
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = {
|
||||
note.author?.let {
|
||||
accountViewModel.hide(it)
|
||||
}; onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.block_hide_user))
|
||||
}
|
||||
Divider()
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.SPAM)
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.report_spam_scam))
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.PROFANITY)
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.report_hateful_speech))
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.IMPERSONATION)
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.report_impersonation))
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.NUDITY)
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.report_nudity_porn))
|
||||
}
|
||||
DropdownMenuItem(onClick = {
|
||||
accountViewModel.report(note, ReportEvent.ReportType.ILLEGAL)
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(stringResource(R.string.report_illegal_behaviour))
|
||||
DropdownMenuItem(onClick = { reportDialogShowing = true }) {
|
||||
Text("Block / Report")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reportDialogShowing) {
|
||||
ReportNoteDialog(note = note, accountViewModel = accountViewModel) {
|
||||
reportDialogShowing = false
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -16,6 +17,8 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonColors
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
@@ -24,11 +27,13 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AlternateEmail
|
||||
import androidx.compose.material.icons.filled.Block
|
||||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.FormatQuote
|
||||
import androidx.compose.material.icons.filled.PersonAdd
|
||||
import androidx.compose.material.icons.filled.PersonRemove
|
||||
import androidx.compose.material.icons.filled.Report
|
||||
import androidx.compose.material.icons.filled.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -57,9 +62,11 @@ import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||
import com.vitorpamplona.amethyst.ui.theme.WarningColor
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
fun lightenColor(color: Color, amount: Float): Color {
|
||||
private fun lightenColor(color: Color, amount: Float): Color {
|
||||
var argb = color.toArgb()
|
||||
val hslOut = floatArrayOf(0f, 0f, 0f)
|
||||
ColorUtils.colorToHSL(argb, hslOut)
|
||||
@@ -71,7 +78,7 @@ fun lightenColor(color: Color, amount: Float): Color {
|
||||
val externalLinkForNote = { note: Note -> "https://snort.social/e/${note.idNote()}" }
|
||||
|
||||
@Composable
|
||||
fun VerticalDivider(color: Color) =
|
||||
private fun VerticalDivider(color: Color) =
|
||||
Divider(
|
||||
color = color,
|
||||
modifier = Modifier
|
||||
@@ -88,7 +95,9 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
val scope = rememberCoroutineScope()
|
||||
var showSelectTextDialog by remember { mutableStateOf(false) }
|
||||
var showDeleteAlertDialog by remember { mutableStateOf(false) }
|
||||
val isOwnNote = note.author == accountViewModel.userProfile()
|
||||
var showBlockAlertDialog by remember { mutableStateOf(false) }
|
||||
var showReportDialog by remember { mutableStateOf(false) }
|
||||
val isOwnNote = note.author == accountViewModel.accountLiveData.value?.account?.userProfile()
|
||||
val isFollowingUser = !isOwnNote && accountViewModel.isFollowing(note.author!!)
|
||||
|
||||
val showToast = { stringResource: Int ->
|
||||
@@ -134,6 +143,19 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
showToast(R.string.copied_note_id_to_clipboard)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
if (!isOwnNote) {
|
||||
VerticalDivider(primaryLight)
|
||||
|
||||
NoteQuickActionItem(Icons.Default.Block, stringResource(R.string.quick_action_block)) {
|
||||
if (accountViewModel.hideBlockAlertDialog) {
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
} else {
|
||||
showBlockAlertDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Divider(
|
||||
color = primaryLight,
|
||||
@@ -144,7 +166,7 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
|
||||
if (isOwnNote) {
|
||||
NoteQuickActionItem(Icons.Default.Delete, stringResource(R.string.quick_action_delete)) {
|
||||
if (accountViewModel.hideDeleteRequestInfo()) {
|
||||
if (accountViewModel.hideDeleteRequestDialog) {
|
||||
accountViewModel.delete(note)
|
||||
onDismiss()
|
||||
} else {
|
||||
@@ -187,6 +209,14 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
ContextCompat.startActivity(context, shareIntent, null)
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
if (!isOwnNote) {
|
||||
VerticalDivider(primaryLight)
|
||||
|
||||
NoteQuickActionItem(Icons.Default.Report, stringResource(R.string.quick_action_report)) {
|
||||
showReportDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,36 +230,24 @@ fun NoteQuickActionMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Uni
|
||||
}
|
||||
|
||||
if (showDeleteAlertDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { onDismiss() },
|
||||
title = {
|
||||
Text(text = stringResource(R.string.quick_action_request_deletion_alert_title))
|
||||
},
|
||||
text = {
|
||||
Text(text = stringResource(R.string.quick_action_request_deletion_alert_body))
|
||||
},
|
||||
buttons = {
|
||||
Row(
|
||||
modifier = Modifier.padding(all = 8.dp).fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
TextButton(
|
||||
onClick = {
|
||||
accountViewModel.setHideDeleteRequestInfo()
|
||||
accountViewModel.delete(note)
|
||||
onDismiss()
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.quick_action_dont_show_again_button))
|
||||
}
|
||||
Button(
|
||||
onClick = { accountViewModel.delete(note); onDismiss() }
|
||||
) {
|
||||
Text(stringResource(R.string.quick_action_delete_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
DeleteAlertDialog(note, accountViewModel) {
|
||||
showDeleteAlertDialog = false
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
if (showBlockAlertDialog) {
|
||||
BlockAlertDialog(note, accountViewModel) {
|
||||
showBlockAlertDialog = false
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
if (showReportDialog) {
|
||||
ReportNoteDialog(note, accountViewModel) {
|
||||
showReportDialog = false
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,9 +263,99 @@ fun NoteQuickActionItem(icon: ImageVector, label: String, onClick: () -> Unit) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp).padding(bottom = 5.dp),
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.padding(bottom = 5.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
Text(text = label, fontSize = 12.sp, color = Color.White, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeleteAlertDialog(note: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit) =
|
||||
QuickActionAlertDialog(
|
||||
title = stringResource(R.string.quick_action_request_deletion_alert_title),
|
||||
textContent = stringResource(R.string.quick_action_request_deletion_alert_body),
|
||||
buttonIcon = Icons.Default.Delete,
|
||||
buttonText = stringResource(R.string.quick_action_delete_dialog_btn),
|
||||
onClickDoOnce = {
|
||||
accountViewModel.delete(note)
|
||||
onDismiss()
|
||||
},
|
||||
onClickDontShowAgain = {
|
||||
accountViewModel.delete(note)
|
||||
accountViewModel.dontShowDeleteRequestDialog()
|
||||
onDismiss()
|
||||
},
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun BlockAlertDialog(note: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit) =
|
||||
QuickActionAlertDialog(
|
||||
title = stringResource(R.string.report_dialog_block_hide_user_btn),
|
||||
textContent = stringResource(R.string.report_dialog_blocking_a_user),
|
||||
buttonIcon = Icons.Default.Block,
|
||||
buttonText = stringResource(R.string.quick_action_block_dialog_btn),
|
||||
buttonColors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = WarningColor,
|
||||
contentColor = Color.White
|
||||
),
|
||||
onClickDoOnce = {
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
},
|
||||
onClickDontShowAgain = {
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
accountViewModel.dontShowBlockAlertDialog()
|
||||
onDismiss()
|
||||
},
|
||||
onDismiss = onDismiss
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun QuickActionAlertDialog(
|
||||
title: String,
|
||||
textContent: String,
|
||||
buttonIcon: ImageVector,
|
||||
buttonText: String,
|
||||
buttonColors: ButtonColors = ButtonDefaults.buttonColors(),
|
||||
onClickDoOnce: () -> Unit,
|
||||
onClickDontShowAgain: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
Text(textContent)
|
||||
},
|
||||
buttons = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(all = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
TextButton(onClick = onClickDontShowAgain) {
|
||||
Text(stringResource(R.string.quick_action_dont_show_again_button))
|
||||
}
|
||||
Button(onClick = onClickDoOnce, colors = buttonColors) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
imageVector = buttonIcon,
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(buttonText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@@ -73,8 +73,8 @@ class AccountViewModel(private val account: Account) : ViewModel() {
|
||||
)
|
||||
}
|
||||
|
||||
fun report(note: Note, type: ReportEvent.ReportType) {
|
||||
account.report(note, type)
|
||||
fun report(note: Note, type: ReportEvent.ReportType, content: String = "") {
|
||||
account.report(note, type, content)
|
||||
}
|
||||
|
||||
fun report(user: User, type: ReportEvent.ReportType) {
|
||||
@@ -129,11 +129,17 @@ class AccountViewModel(private val account: Account) : ViewModel() {
|
||||
return account.userProfile().isFollowing(user)
|
||||
}
|
||||
|
||||
fun hideDeleteRequestInfo(): Boolean {
|
||||
return account.hideDeleteRequestInfo
|
||||
val hideDeleteRequestDialog: Boolean
|
||||
get() = account.hideDeleteRequestDialog
|
||||
|
||||
fun dontShowDeleteRequestDialog() {
|
||||
account.setHideDeleteRequestDialog()
|
||||
}
|
||||
|
||||
fun setHideDeleteRequestInfo() {
|
||||
account.setHideDeleteRequestInfo()
|
||||
val hideBlockAlertDialog: Boolean
|
||||
get() = account.hideBlockAlertDialog
|
||||
|
||||
fun dontShowBlockAlertDialog() {
|
||||
account.setHideBlockAlertDialog()
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,172 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.OutlinedTextField
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Block
|
||||
import androidx.compose.material.icons.filled.Report
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||
import com.vitorpamplona.amethyst.ui.theme.WarningColor
|
||||
|
||||
@Composable
|
||||
fun ReportNoteDialog(note: Note, accountViewModel: AccountViewModel, onDismiss: () -> Unit) {
|
||||
val reportTypes = listOf(
|
||||
Pair(ReportEvent.ReportType.SPAM, stringResource(R.string.report_dialog_spam)),
|
||||
Pair(ReportEvent.ReportType.PROFANITY, stringResource(R.string.report_dialog_profanity)),
|
||||
Pair(ReportEvent.ReportType.IMPERSONATION, stringResource(R.string.report_dialog_impersonation)),
|
||||
Pair(ReportEvent.ReportType.NUDITY, stringResource(R.string.report_dialog_nudity)),
|
||||
Pair(ReportEvent.ReportType.ILLEGAL, stringResource(R.string.report_dialog_illegal))
|
||||
)
|
||||
|
||||
val reasonOptions = reportTypes.map { it.second }
|
||||
var additionalReason by remember { mutableStateOf("") }
|
||||
var selectedReason by remember { mutableStateOf(-1) }
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(text = "Block and Report") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
tint = MaterialTheme.colors.onSurface
|
||||
)
|
||||
}
|
||||
},
|
||||
backgroundColor = MaterialTheme.colors.surface,
|
||||
elevation = 0.dp
|
||||
)
|
||||
}
|
||||
) { pad ->
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp, pad.calculateTopPadding(), 16.dp, pad.calculateBottomPadding()),
|
||||
verticalArrangement = Arrangement.SpaceAround
|
||||
) {
|
||||
SpacerH16()
|
||||
SectionHeader(text = "Block")
|
||||
SpacerH16()
|
||||
Text(
|
||||
text = stringResource(R.string.report_dialog_blocking_a_user)
|
||||
)
|
||||
SpacerH16()
|
||||
ActionButton(
|
||||
text = stringResource(R.string.report_dialog_block_hide_user_btn),
|
||||
icon = Icons.Default.Block,
|
||||
onClick = {
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
SpacerH16()
|
||||
|
||||
Divider(color = MaterialTheme.colors.onSurface, thickness = 0.25.dp)
|
||||
|
||||
SpacerH16()
|
||||
SectionHeader(text = stringResource(R.string.report_dialog_report_btn))
|
||||
SpacerH16()
|
||||
Text(stringResource(R.string.report_dialog_reminder_public))
|
||||
SpacerH16()
|
||||
TextSpinner(
|
||||
label = stringResource(R.string.report_dialog_select_reason_label),
|
||||
placeholder = stringResource(R.string.report_dialog_select_reason_placeholder),
|
||||
options = reasonOptions,
|
||||
onSelect = {
|
||||
selectedReason = it
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SpacerH16()
|
||||
OutlinedTextField(
|
||||
value = additionalReason,
|
||||
onValueChange = { additionalReason = it },
|
||||
placeholder = { Text(text = stringResource(R.string.report_dialog_additional_reason_placeholder)) },
|
||||
label = { Text(stringResource(R.string.report_dialog_additional_reason_label)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
SpacerH16()
|
||||
ActionButton(
|
||||
text = stringResource(R.string.report_dialog_post_report_btn),
|
||||
icon = Icons.Default.Report,
|
||||
enabled = selectedReason in 0..reportTypes.lastIndex,
|
||||
onClick = {
|
||||
accountViewModel.report(note, reportTypes[selectedReason].first, additionalReason)
|
||||
note.author?.let { accountViewModel.hide(it) }
|
||||
onDismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpacerH16() = Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
@Composable
|
||||
private fun SectionHeader(text: String) = Text(
|
||||
text = text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun ActionButton(text: String, icon: ImageVector, enabled: Boolean = true, onClick: () -> Unit) = Button(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = WarningColor),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
tint = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(text = text, color = Color.White)
|
||||
}
|
||||
}
|
@@ -12,3 +12,5 @@ val Following = Color(0xFF03DAC5)
|
||||
val Nip05 = Color(0xFF01BAFF)
|
||||
val FollowsFollow = Color.Yellow
|
||||
val NIP05Verified = Color.Blue
|
||||
|
||||
val WarningColor = Color(0xFFC62828)
|
||||
|
@@ -197,6 +197,23 @@
|
||||
<string name="copied_user_id_to_clipboard" tools:ignore="Typos">Copied author’s @npub to clipboard</string>
|
||||
<string name="copied_note_id_to_clipboard" tools:ignore="Typos">Copied note ID (@note1) to clipboard</string>
|
||||
<string name="select_text_dialog_top">Select Text</string>
|
||||
<string name="github" translatable="false">Github Gist w/ Proof</string>
|
||||
<string name="telegram" translatable="false">Telegram</string>
|
||||
<string name="mastodon" translatable="false">Mastodon Post ID w/ Proof</string>
|
||||
<string name="twitter" translatable="false">Twitter Status w/ Proof</string>
|
||||
<string name="github_proof_url_template" translatable="false">https://gist.github.com/<user>/<gist></string>
|
||||
<string name="telegram_proof_url_template" translatable="false">https://t.me/<proof post></string>
|
||||
<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="account_switch_add_account_dialog_title">Add New Account</string>
|
||||
<string name="drawer_accounts">Accounts</string>
|
||||
<string name="account_switch_select_account">Select Account</string>
|
||||
<string name="account_switch_add_account_btn">Add New Account</string>
|
||||
<string name="account_switch_active_account">Active account</string>
|
||||
<string name="account_switch_has_private_key">Has private key</string>
|
||||
<string name="account_switch_pubkey_only">Read only, no private key</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="quick_action_select">Select</string>
|
||||
<string name="quick_action_share_browser_link">Share Browser Link</string>
|
||||
<string name="quick_action_share">Share</string>
|
||||
@@ -208,24 +225,25 @@
|
||||
<string name="quick_action_follow">Follow</string>
|
||||
<string name="quick_action_request_deletion_alert_title">Request Deletion</string>
|
||||
<string name="quick_action_request_deletion_alert_body">Amethyst will request that your note be deleted from the relays you are currently connected to. There is no guarantee that your note will be permanently deleted from those relays, or from other relays where it may be stored.</string>
|
||||
<string name="github" translatable="false">Github Gist w/ Proof</string>
|
||||
<string name="telegram" translatable="false">Telegram</string>
|
||||
<string name="mastodon" translatable="false">Mastodon Post ID w/ Proof</string>
|
||||
<string name="twitter" translatable="false">Twitter Status w/ Proof</string>
|
||||
<string name="github_proof_url_template" translatable="false">https://gist.github.com/<user>/<gist></string>
|
||||
<string name="telegram_proof_url_template" translatable="false">https://t.me/<proof post></string>
|
||||
<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_block_dialog_btn">Block</string>
|
||||
<string name="quick_action_delete_dialog_btn">Delete</string>
|
||||
<string name="quick_action_block">Block</string>
|
||||
<string name="quick_action_report">Report</string>
|
||||
<string name="quick_action_delete_button">Delete</string>
|
||||
<string name="quick_action_dont_show_again_button">Don\'t show again</string>
|
||||
<string name="account_switch_add_account_dialog_title">Add New Account</string>
|
||||
<string name="drawer_accounts">Accounts</string>
|
||||
<string name="account_switch_select_account">Select Account</string>
|
||||
<string name="account_switch_add_account_btn">Add New Account</string>
|
||||
<string name="account_switch_active_account">Active account</string>
|
||||
<string name="account_switch_has_private_key">Has private key</string>
|
||||
<string name="account_switch_pubkey_only">Read only, no private key</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="report_dialog_spam">Spam or scams</string>
|
||||
<string name="report_dialog_profanity">Profanity or hateful conduct</string>
|
||||
<string name="report_dialog_impersonation">Malicious impersonation</string>
|
||||
<string name="report_dialog_nudity">Nudity or graphic content</string>
|
||||
<string name="report_dialog_illegal">Illegal Behavior</string>
|
||||
<string name="report_dialog_blocking_a_user">Blocking a user will hide their content in your app. Your notes are still publicly viewable, including to people you block. Blocked users are listed on the Security Filters screen.</string>
|
||||
<string name="report_dialog_block_hide_user_btn"><![CDATA[Block & Hide User]]></string>
|
||||
<string name="report_dialog_report_btn">Report Abuse</string>
|
||||
<string name="report_dialog_reminder_public">All reports posted will be publicly visible.</string>
|
||||
<string name="report_dialog_additional_reason_placeholder">Optionally provide additional context about your report…</string>
|
||||
<string name="report_dialog_additional_reason_label">Additional Context</string>
|
||||
<string name="report_dialog_select_reason_label">Reason</string>
|
||||
<string name="report_dialog_select_reason_placeholder">Select a reason…</string>
|
||||
<string name="report_dialog_post_report_btn">Post Report</string>
|
||||
|
||||
</resources>
|
||||
|
Reference in New Issue
Block a user