mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-28 20:40:13 +02:00
feat(core): compose friend feed menu
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
parent
f29e3f37cd
commit
ce2ae6ff45
@ -2,6 +2,9 @@ package me.rhunk.snapenhance.common.data
|
||||
|
||||
import android.database.Cursor
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import me.rhunk.snapenhance.common.config.FeatureNotice
|
||||
import me.rhunk.snapenhance.common.data.download.toKeyPair
|
||||
@ -38,18 +41,19 @@ enum class SocialScope(
|
||||
enum class MessagingRuleType(
|
||||
val key: String,
|
||||
val listMode: Boolean,
|
||||
val icon: ImageVector,
|
||||
val showInFriendMenu: Boolean = true,
|
||||
val defaultValue: String? = "whitelist",
|
||||
val configNotices: Array<FeatureNotice> = emptyArray()
|
||||
) {
|
||||
STEALTH("stealth", true),
|
||||
AUTO_DOWNLOAD("auto_download", true),
|
||||
AUTO_SAVE("auto_save", true, defaultValue = "blacklist"),
|
||||
AUTO_OPEN_SNAPS("auto_open_snaps", true, configNotices = arrayOf(FeatureNotice.BAN_RISK, FeatureNotice.UNSTABLE), defaultValue = null),
|
||||
UNSAVEABLE_MESSAGES("unsaveable_messages", true, configNotices = arrayOf(FeatureNotice.REQUIRE_NATIVE_HOOKS), defaultValue = null),
|
||||
HIDE_FRIEND_FEED("hide_friend_feed", false, showInFriendMenu = false),
|
||||
E2E_ENCRYPTION("e2e_encryption", false),
|
||||
PIN_CONVERSATION("pin_conversation", false, showInFriendMenu = false);
|
||||
STEALTH("stealth", true, Icons.Outlined.TrackChanges),
|
||||
AUTO_DOWNLOAD("auto_download", true, Icons.Outlined.DownloadForOffline),
|
||||
AUTO_SAVE("auto_save", true, Icons.Outlined.Save, defaultValue = "blacklist"),
|
||||
AUTO_OPEN_SNAPS("auto_open_snaps", true, Icons.Outlined.OpenInFull, configNotices = arrayOf(FeatureNotice.BAN_RISK, FeatureNotice.UNSTABLE), defaultValue = null),
|
||||
UNSAVEABLE_MESSAGES("unsaveable_messages", true, Icons.Outlined.FolderOff, configNotices = arrayOf(FeatureNotice.REQUIRE_NATIVE_HOOKS), defaultValue = null),
|
||||
HIDE_FRIEND_FEED("hide_friend_feed", false, Icons.Outlined.VisibilityOff, showInFriendMenu = false),
|
||||
E2E_ENCRYPTION("e2e_encryption", false, Icons.Outlined.Lock),
|
||||
PIN_CONVERSATION("pin_conversation", false, Icons.Outlined.PushPin, showInFriendMenu = false);
|
||||
|
||||
fun translateOptionKey(optionKey: String): String {
|
||||
return if (listMode) "rules.properties.$key.options.$optionKey" else "rules.properties.$key.name"
|
||||
|
@ -7,18 +7,29 @@ import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Switch
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CheckCircleOutline
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.NotInterested
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.material.icons.outlined.EditNote
|
||||
import androidx.compose.material.icons.outlined.RemoveRedEye
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
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.input.pointer.pointerInput
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.Font
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import me.rhunk.snapenhance.common.data.ContentType
|
||||
import me.rhunk.snapenhance.common.data.FriendLinkType
|
||||
import me.rhunk.snapenhance.common.database.impl.ConversationMessage
|
||||
@ -37,7 +48,8 @@ import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.core.ui.applyTheme
|
||||
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
|
||||
import me.rhunk.snapenhance.core.ui.triggerRootCloseTouchEvent
|
||||
import me.rhunk.snapenhance.core.util.ktx.vibrateLongPress
|
||||
import me.rhunk.snapenhance.core.util.ktx.getIdentifier
|
||||
import me.rhunk.snapenhance.core.util.ktx.isDarkTheme
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
import java.text.DateFormat
|
||||
@ -47,6 +59,22 @@ import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
class FriendFeedInfoMenu : AbstractMenu() {
|
||||
private val avenirNextMediumFont by lazy {
|
||||
FontFamily(
|
||||
Font(context.resources.getIdentifier("avenir_next_medium", "font"), FontWeight.Medium)
|
||||
)
|
||||
}
|
||||
private val sigColorTextPrimary by lazy {
|
||||
context.androidContext.theme.obtainStyledAttributes(
|
||||
intArrayOf(context.resources.getIdentifier("sigColorTextPrimary", "attr"))
|
||||
).getColor(0, 0)
|
||||
}
|
||||
private val sigColorBackgroundSurface by lazy {
|
||||
context.androidContext.theme.obtainStyledAttributes(
|
||||
intArrayOf(context.resources.getIdentifier("sigColorBackgroundSurface", "attr"))
|
||||
).getColor(0, 0)
|
||||
}
|
||||
|
||||
private fun getImageDrawable(url: String): Drawable {
|
||||
val connection = URL(url).openConnection() as HttpURLConnection
|
||||
connection.connect()
|
||||
@ -208,16 +236,54 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun createToggleFeature(viewConsumer: ((View) -> Unit), value: String, checked: () -> Boolean, toggle: (Boolean) -> Unit) {
|
||||
viewConsumer(Switch(context.androidContext).apply {
|
||||
text = this@FriendFeedInfoMenu.context.translation[value]
|
||||
isChecked = checked()
|
||||
applyTheme(hasRadius = true)
|
||||
isSoundEffectsEnabled = false
|
||||
setOnCheckedChangeListener { _, checked ->
|
||||
toggle(checked)
|
||||
@Composable
|
||||
private fun MenuElement(
|
||||
index: Int,
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
content: @Composable RowScope.() -> Unit = {}
|
||||
) {
|
||||
if (index > 0) {
|
||||
Spacer(modifier = Modifier
|
||||
.height(1.dp)
|
||||
.background(remember { if (context.androidContext.isDarkTheme()) Color(0x1affffff) else Color(0xffeeeeee) })
|
||||
.fillMaxWidth())
|
||||
}
|
||||
Surface(
|
||||
color = Color(sigColorBackgroundSurface),
|
||||
contentColor = Color(sigColorTextPrimary),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onLongPress = {
|
||||
onLongClick?.invoke()
|
||||
},
|
||||
onTap = {
|
||||
onClick()
|
||||
}
|
||||
)
|
||||
}
|
||||
.heightIn(min = 55.dp)
|
||||
.padding(start = 16.dp, end = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(icon, contentDescription = null, modifier = Modifier
|
||||
.size(32.dp)
|
||||
.padding(end = 8.dp))
|
||||
Text(
|
||||
text = text,
|
||||
modifier = Modifier.weight(1f),
|
||||
lineHeight = 18.sp,
|
||||
fontSize = 16.sp,
|
||||
)
|
||||
content()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun inject(parent: ViewGroup, view: View, viewConsumer: ((View) -> Unit)) {
|
||||
@ -228,88 +294,128 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
|
||||
val messaging = context.feature(Messaging::class)
|
||||
val conversationId = messaging.lastFocusedConversationId ?: return
|
||||
val targetUser = context.database.getDMOtherParticipant(conversationId)
|
||||
val targetUser by lazy { context.database.getDMOtherParticipant(conversationId) }
|
||||
messaging.resetLastFocusedConversation()
|
||||
|
||||
val translation = context.translation.getCategory("friend_menu_option")
|
||||
if (friendFeedMenuOptions.contains("conversation_info")) {
|
||||
viewConsumer(Button(view.context).apply {
|
||||
text = translation["preview"]
|
||||
applyTheme(view.width, hasRadius = true)
|
||||
setOnClickListener {
|
||||
showPreview(
|
||||
targetUser,
|
||||
conversationId
|
||||
|
||||
@Composable
|
||||
fun ComposeFriendFeedMenu() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
var elementIndex by remember { mutableIntStateOf(0) }
|
||||
|
||||
if (friendFeedMenuOptions.contains("conversation_info")) {
|
||||
MenuElement(
|
||||
remember { elementIndex++ },
|
||||
Icons.Outlined.RemoveRedEye,
|
||||
translation["preview"],
|
||||
onClick = {
|
||||
showPreview(targetUser, conversationId)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
modContext.features.getRuleFeatures().forEach { ruleFeature ->
|
||||
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
|
||||
modContext.features.getRuleFeatures().forEach { ruleFeature ->
|
||||
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
|
||||
|
||||
val ruleState = ruleFeature.getRuleState() ?: return@forEach
|
||||
createToggleFeature(viewConsumer,
|
||||
ruleFeature.ruleType.translateOptionKey(ruleState.key),
|
||||
{ ruleFeature.getState(conversationId) },
|
||||
{
|
||||
ruleFeature.setState(conversationId, it)
|
||||
context.inAppOverlay.showStatusToast(
|
||||
if (it) Icons.Default.CheckCircleOutline else Icons.Default.NotInterested,
|
||||
context.translation.format("rules.toasts.${if (it) "enabled" else "disabled"}", "ruleName" to context.translation[ruleFeature.ruleType.translateOptionKey(ruleState.key)]),
|
||||
durationMs = 1500
|
||||
val ruleState = ruleFeature.getRuleState() ?: return@forEach
|
||||
var state by remember { mutableStateOf(ruleFeature.getState(conversationId)) }
|
||||
|
||||
fun toggle() {
|
||||
state = !ruleFeature.getState(conversationId)
|
||||
ruleFeature.setState(conversationId, state)
|
||||
context.inAppOverlay.showStatusToast(
|
||||
if (ruleFeature.getState(conversationId)) Icons.Default.CheckCircleOutline else Icons.Default.NotInterested,
|
||||
context.translation.format("rules.toasts.${if (ruleFeature.getState(conversationId)) "enabled" else "disabled"}", "ruleName" to context.translation[ruleFeature.ruleType.translateOptionKey(ruleState.key)]),
|
||||
durationMs = 1500
|
||||
)
|
||||
context.mainActivity?.triggerRootCloseTouchEvent()
|
||||
}
|
||||
|
||||
MenuElement(
|
||||
remember { elementIndex++ },
|
||||
icon = ruleFeature.ruleType.icon,
|
||||
text = context.translation[ruleFeature.ruleType.translateOptionKey(ruleState.key)],
|
||||
onClick = {
|
||||
toggle()
|
||||
}
|
||||
) {
|
||||
Switch(
|
||||
checked = state,
|
||||
onCheckedChange = {
|
||||
state = it
|
||||
toggle()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (friendFeedMenuOptions.contains("mark_snaps_as_seen")) {
|
||||
MenuElement(
|
||||
remember { elementIndex++ },
|
||||
Icons.Outlined.EditNote,
|
||||
translation["mark_snaps_as_seen"],
|
||||
onClick = {
|
||||
context.apply {
|
||||
mainActivity?.triggerRootCloseTouchEvent()
|
||||
feature(AutoMarkAsRead::class).markSnapsAsSeen(conversationId)
|
||||
}
|
||||
}
|
||||
)
|
||||
context.mainActivity?.triggerRootCloseTouchEvent()
|
||||
}
|
||||
)
|
||||
|
||||
if (targetUser != null && friendFeedMenuOptions.contains("mark_stories_as_seen_locally")) {
|
||||
val markAsSeenTranslation = remember { context.translation.getCategory("mark_as_seen") }
|
||||
|
||||
MenuElement(
|
||||
remember { elementIndex++ },
|
||||
Icons.Outlined.RemoveRedEye,
|
||||
translation["mark_stories_as_seen_locally"],
|
||||
onClick = {
|
||||
context.apply {
|
||||
mainActivity?.triggerRootCloseTouchEvent()
|
||||
inAppOverlay.showStatusToast(
|
||||
Icons.Default.Info,
|
||||
if (database.setStoriesViewedState(targetUser!!, true)) markAsSeenTranslation["seen_toast"]
|
||||
else markAsSeenTranslation["already_seen_toast"],
|
||||
durationMs = 2500
|
||||
)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
view.post {
|
||||
context.apply {
|
||||
mainActivity?.triggerRootCloseTouchEvent()
|
||||
inAppOverlay.showStatusToast(
|
||||
Icons.Default.Info,
|
||||
if (database.setStoriesViewedState(targetUser!!, false)) markAsSeenTranslation["unseen_toast"]
|
||||
else markAsSeenTranslation["already_unseen_toast"],
|
||||
durationMs = 2500
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (friendFeedMenuOptions.contains("mark_snaps_as_seen")) {
|
||||
viewConsumer(Button(view.context).apply {
|
||||
text = translation["mark_snaps_as_seen"]
|
||||
isSoundEffectsEnabled = false
|
||||
applyTheme(view.width, hasRadius = true)
|
||||
setOnClickListener {
|
||||
this@FriendFeedInfoMenu.context.apply {
|
||||
mainActivity?.triggerRootCloseTouchEvent()
|
||||
feature(AutoMarkAsRead::class).markSnapsAsSeen(conversationId)
|
||||
}
|
||||
viewConsumer(
|
||||
createComposeView(view.context) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextStyle provides LocalTextStyle.current.merge(TextStyle(fontFamily = avenirNextMediumFont))
|
||||
) {
|
||||
ComposeFriendFeedMenu()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (targetUser != null && friendFeedMenuOptions.contains("mark_stories_as_seen_locally")) {
|
||||
viewConsumer(Button(view.context).apply {
|
||||
text = translation["mark_stories_as_seen_locally"]
|
||||
applyTheme(view.width, hasRadius = true)
|
||||
isSoundEffectsEnabled = false
|
||||
|
||||
val translations = this@FriendFeedInfoMenu.context.translation.getCategory("mark_as_seen")
|
||||
|
||||
this@FriendFeedInfoMenu.context.apply {
|
||||
setOnClickListener {
|
||||
mainActivity?.triggerRootCloseTouchEvent()
|
||||
this@FriendFeedInfoMenu.context.inAppOverlay.showStatusToast(
|
||||
Icons.Default.Info,
|
||||
if (database.setStoriesViewedState(targetUser, true)) translations["seen_toast"]
|
||||
else translations["already_seen_toast"],
|
||||
durationMs = 2500
|
||||
)
|
||||
}
|
||||
setOnLongClickListener {
|
||||
context.vibrateLongPress()
|
||||
mainActivity?.triggerRootCloseTouchEvent()
|
||||
this@FriendFeedInfoMenu.context.inAppOverlay.showStatusToast(
|
||||
Icons.Default.Info,
|
||||
if (database.setStoriesViewedState(targetUser, false)) translations["unseen_toast"]
|
||||
else translations["already_unseen_toast"],
|
||||
durationMs = 2500
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}.apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (context.config.scripting.integratedUI.get()) {
|
||||
context.scriptRuntime.eachModule {
|
||||
|
Loading…
x
Reference in New Issue
Block a user