mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat: scope content
- refactor image loader - rules
This commit is contained in:
@ -5,6 +5,10 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.decode.VideoFrameDecoder
|
||||||
|
import coil.memory.MemoryCache
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import me.rhunk.snapenhance.bridge.BridgeService
|
import me.rhunk.snapenhance.bridge.BridgeService
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||||
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper
|
||||||
@ -35,6 +39,17 @@ class RemoteSideContext(
|
|||||||
val downloadTaskManager = DownloadTaskManager()
|
val downloadTaskManager = DownloadTaskManager()
|
||||||
val modDatabase = ModDatabase(this)
|
val modDatabase = ModDatabase(this)
|
||||||
|
|
||||||
|
//used to load bitmoji selfies and download previews
|
||||||
|
val imageLoader by lazy {
|
||||||
|
ImageLoader.Builder(androidContext)
|
||||||
|
.dispatcher(Dispatchers.IO)
|
||||||
|
.memoryCache {
|
||||||
|
MemoryCache.Builder(androidContext)
|
||||||
|
.maxSizePercent(0.25)
|
||||||
|
.build()
|
||||||
|
}.components { add(VideoFrameDecoder.Factory()) }.build()
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
runCatching {
|
runCatching {
|
||||||
config.loadFromContext(androidContext)
|
config.loadFromContext(androidContext)
|
||||||
|
@ -12,7 +12,7 @@ import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
|||||||
import me.rhunk.snapenhance.bridge.wrapper.MessageLoggerWrapper
|
import me.rhunk.snapenhance.bridge.wrapper.MessageLoggerWrapper
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingScope
|
import me.rhunk.snapenhance.core.messaging.SocialScope
|
||||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||||
import me.rhunk.snapenhance.download.DownloadProcessor
|
import me.rhunk.snapenhance.download.DownloadProcessor
|
||||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||||
@ -112,7 +112,7 @@ class BridgeService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getRules(objectType: String, uuid: String): MutableList<String> {
|
override fun getRules(objectType: String, uuid: String): MutableList<String> {
|
||||||
remoteSideContext.modDatabase.getRulesFromId(MessagingScope.valueOf(objectType), uuid)
|
remoteSideContext.modDatabase.getRulesFromId(SocialScope.valueOf(objectType), uuid)
|
||||||
.let { rules ->
|
.let { rules ->
|
||||||
return rules.map { it.toJson() }.toMutableList()
|
return rules.map { it.toJson() }.toMutableList()
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,7 @@ import me.rhunk.snapenhance.core.messaging.FriendStreaks
|
|||||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
||||||
import me.rhunk.snapenhance.core.messaging.Mode
|
import me.rhunk.snapenhance.core.messaging.SocialScope
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingScope
|
|
||||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||||
@ -50,8 +49,7 @@ class ModDatabase(
|
|||||||
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||||
"scope VARCHAR",
|
"scope VARCHAR",
|
||||||
"targetUuid VARCHAR",
|
"targetUuid VARCHAR",
|
||||||
"enabled BOOLEAN",
|
//"mode VARCHAR",
|
||||||
"mode VARCHAR",
|
|
||||||
"subject VARCHAR"
|
"subject VARCHAR"
|
||||||
),
|
),
|
||||||
"streaks" to listOf(
|
"streaks" to listOf(
|
||||||
@ -153,16 +151,14 @@ class ModDatabase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRulesFromId(type: MessagingScope, targetUuid: String): List<MessagingRule> {
|
fun getRulesFromId(type: SocialScope, targetUuid: String): List<MessagingRule> {
|
||||||
return database.rawQuery("SELECT * FROM rules WHERE objectType = ? AND targetUuid = ?", arrayOf(type.name, targetUuid)).use { cursor ->
|
return database.rawQuery("SELECT * FROM rules WHERE scope = ? AND targetUuid = ?", arrayOf(type.name, targetUuid)).use { cursor ->
|
||||||
val rules = mutableListOf<MessagingRule>()
|
val rules = mutableListOf<MessagingRule>()
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
rules.add(MessagingRule(
|
rules.add(MessagingRule(
|
||||||
id = cursor.getInteger("id"),
|
id = cursor.getInteger("id"),
|
||||||
messagingScope = MessagingScope.valueOf(cursor.getStringOrNull("scope")!!),
|
socialScope = SocialScope.valueOf(cursor.getStringOrNull("scope")!!),
|
||||||
targetUuid = cursor.getStringOrNull("targetUuid")!!,
|
targetUuid = cursor.getStringOrNull("targetUuid")!!,
|
||||||
enabled = cursor.getInteger("enabled") == 1,
|
|
||||||
mode = Mode.valueOf(cursor.getStringOrNull("mode")!!),
|
|
||||||
subject = cursor.getStringOrNull("subject")!!
|
subject = cursor.getStringOrNull("subject")!!
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -170,6 +166,24 @@ class ModDatabase(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleRuleFor(type: SocialScope, targetUuid: String, subject: String, enabled: Boolean) {
|
||||||
|
executeAsync {
|
||||||
|
if (enabled) {
|
||||||
|
database.execSQL("INSERT OR REPLACE INTO rules (scope, targetUuid, subject) VALUES (?, ?, ?)", arrayOf(
|
||||||
|
type.name,
|
||||||
|
targetUuid,
|
||||||
|
subject
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
database.execSQL("DELETE FROM rules WHERE scope = ? AND targetUuid = ? AND subject = ?", arrayOf(
|
||||||
|
type.name,
|
||||||
|
targetUuid,
|
||||||
|
subject
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getFriendInfo(userId: String): MessagingFriendInfo? {
|
fun getFriendInfo(userId: String): MessagingFriendInfo? {
|
||||||
return database.rawQuery("SELECT * FROM friends WHERE userId = ?", arrayOf(userId)).use { cursor ->
|
return database.rawQuery("SELECT * FROM friends WHERE userId = ?", arrayOf(userId)).use { cursor ->
|
||||||
if (!cursor.moveToFirst()) return@use null
|
if (!cursor.moveToFirst()) return@use null
|
||||||
|
@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.requiredWidthIn
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -49,34 +48,19 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import coil.decode.VideoFrameDecoder
|
|
||||||
import coil.memory.MemoryCache
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import coil.size.Precision
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.R
|
|
||||||
import me.rhunk.snapenhance.data.FileType
|
import me.rhunk.snapenhance.data.FileType
|
||||||
import me.rhunk.snapenhance.download.data.DownloadObject
|
import me.rhunk.snapenhance.download.data.DownloadObject
|
||||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
|
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
||||||
|
import me.rhunk.snapenhance.ui.util.ImageRequestHelper
|
||||||
|
|
||||||
class DownloadsSection : Section() {
|
class DownloadsSection : Section() {
|
||||||
private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>())
|
private val loadedDownloads = mutableStateOf(mapOf<Int, DownloadObject>())
|
||||||
private var currentFilter = mutableStateOf(MediaFilter.NONE)
|
private var currentFilter = mutableStateOf(MediaFilter.NONE)
|
||||||
|
|
||||||
private val imageLoader by lazy {
|
|
||||||
ImageLoader.Builder(context.androidContext)
|
|
||||||
.dispatcher(Dispatchers.IO)
|
|
||||||
.memoryCache {
|
|
||||||
MemoryCache.Builder(context.androidContext)
|
|
||||||
.maxSizePercent(0.25)
|
|
||||||
.build()
|
|
||||||
}.components { add(VideoFrameDecoder.Factory()) }.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResumed() {
|
override fun onResumed() {
|
||||||
super.onResumed()
|
super.onResumed()
|
||||||
loadByFilter(currentFilter.value)
|
loadByFilter(currentFilter.value)
|
||||||
@ -156,12 +140,11 @@ class DownloadsSection : Section() {
|
|||||||
Box(modifier = Modifier.height(100.dp)) {
|
Box(modifier = Modifier.height(100.dp)) {
|
||||||
Image(
|
Image(
|
||||||
painter = rememberAsyncImagePainter(
|
painter = rememberAsyncImagePainter(
|
||||||
model = ImageRequest.Builder(context.androidContext)
|
model = ImageRequestHelper.newDownloadPreviewImageRequest(
|
||||||
.data(download.outputFile)
|
context.androidContext,
|
||||||
.memoryCacheKey(download.outputFile)
|
download.outputFile
|
||||||
.crossfade(true)
|
),
|
||||||
.build(),
|
imageLoader = context.imageLoader
|
||||||
imageLoader = imageLoader
|
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.matchParentSize()
|
.matchParentSize()
|
||||||
@ -187,25 +170,7 @@ class DownloadsSection : Section() {
|
|||||||
.padding(15.dp),
|
.padding(15.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Image(
|
BitmojiImage(context = context, url = download.metadata.iconUrl, size = 48)
|
||||||
painter = rememberAsyncImagePainter(
|
|
||||||
model = ImageRequest.Builder(context.androidContext)
|
|
||||||
.data(download.metadata.iconUrl)
|
|
||||||
.fallback(R.drawable.bitmoji_blank)
|
|
||||||
.precision(Precision.INEXACT)
|
|
||||||
.crossfade(true)
|
|
||||||
.memoryCacheKey(download.metadata.iconUrl)
|
|
||||||
.build(),
|
|
||||||
imageLoader = imageLoader
|
|
||||||
),
|
|
||||||
contentDescription = null,
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.requiredWidthIn(min = 0.dp, max = 48.dp)
|
|
||||||
.height(48.dp)
|
|
||||||
.clip(MaterialTheme.shapes.medium)
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 10.dp),
|
.padding(start = 10.dp),
|
||||||
|
@ -0,0 +1,116 @@
|
|||||||
|
package me.rhunk.snapenhance.ui.manager.sections.social
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.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.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
|
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
|
||||||
|
import me.rhunk.snapenhance.core.messaging.SocialScope
|
||||||
|
|
||||||
|
class ScopeContent(
|
||||||
|
private val context: RemoteSideContext,
|
||||||
|
private val section: SocialSection,
|
||||||
|
private val navController: NavController,
|
||||||
|
private val scope: SocialScope,
|
||||||
|
private val id: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DeleteScopeEntityButton() {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
OutlinedButton(onClick = {
|
||||||
|
when (scope) {
|
||||||
|
SocialScope.FRIEND -> context.modDatabase.deleteFriend(id)
|
||||||
|
SocialScope.GROUP -> context.modDatabase.deleteGroup(id)
|
||||||
|
}
|
||||||
|
context.modDatabase.executeAsync {
|
||||||
|
coroutineScope.launch {
|
||||||
|
section.onResumed()
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(text = "Delete ${scope.key}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Content() {
|
||||||
|
Column {
|
||||||
|
when (scope) {
|
||||||
|
SocialScope.FRIEND -> Friend()
|
||||||
|
SocialScope.GROUP -> Group()
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
val scopeRules = context.modDatabase.getRulesFromId(scope, id)
|
||||||
|
|
||||||
|
Text(text = "Rules", maxLines = 1)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
//manager anti features etc
|
||||||
|
MessagingRuleType.values().forEach { feature ->
|
||||||
|
var featureEnabled by remember {
|
||||||
|
mutableStateOf(scopeRules.any { it.subject == feature.key })
|
||||||
|
}
|
||||||
|
val featureEnabledText = if (featureEnabled) "Enabled" else "Disabled"
|
||||||
|
Row {
|
||||||
|
Text(text = "${feature.key}: $featureEnabledText", maxLines = 1)
|
||||||
|
Switch(checked = featureEnabled, onCheckedChange = {
|
||||||
|
context.modDatabase.toggleRuleFor(scope, id, feature.key, it)
|
||||||
|
featureEnabled = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Friend() {
|
||||||
|
//fetch the friend from the database
|
||||||
|
val friend = remember { context.modDatabase.getFriendInfo(id) } ?: run {
|
||||||
|
Text(text = "Friend not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
Text(text = friend.displayName ?: "No display name", maxLines = 1)
|
||||||
|
Text(text = "bitmojiId: ${friend.bitmojiId ?: "No bitmojiId"}", maxLines = 1)
|
||||||
|
Text(text = "selfieId: ${friend.selfieId ?: "No selfieId"}", maxLines = 1)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
DeleteScopeEntityButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Group() {
|
||||||
|
//fetch the group from the database
|
||||||
|
val group = remember { context.modDatabase.getGroupInfo(id) } ?: run {
|
||||||
|
Text(text = "Group not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
Text(text = group.name, maxLines = 1)
|
||||||
|
Text(text = "participantsCount: ${group.participantsCount}", maxLines = 1)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
DeleteScopeEntityButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,72 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.ui.manager.sections.social
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.material3.OutlinedButton
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import me.rhunk.snapenhance.RemoteSideContext
|
|
||||||
|
|
||||||
class ScopeTab(
|
|
||||||
private val context: RemoteSideContext,
|
|
||||||
private val section: SocialSection,
|
|
||||||
private val navController: NavController,
|
|
||||||
private val id: String
|
|
||||||
) {
|
|
||||||
@Composable
|
|
||||||
fun Friend() {
|
|
||||||
//fetch the friend from the database
|
|
||||||
val friend = remember { context.modDatabase.getFriendInfo(id) } ?: run {
|
|
||||||
Text(text = "Friend not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Column {
|
|
||||||
Text(text = friend.displayName ?: "No display name", maxLines = 1)
|
|
||||||
Text(text = "bitmojiId: ${friend.bitmojiId ?: "No bitmojiId"}", maxLines = 1)
|
|
||||||
Text(text = "selfieId: ${friend.selfieId ?: "No selfieId"}", maxLines = 1)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
OutlinedButton(onClick = {
|
|
||||||
context.modDatabase.deleteFriend(id)
|
|
||||||
section.onResumed()
|
|
||||||
navController.popBackStack()
|
|
||||||
}) {
|
|
||||||
Text(text = "Delete friend")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Group() {
|
|
||||||
//fetch the group from the database
|
|
||||||
val group = remember { context.modDatabase.getGroupInfo(id) } ?: run {
|
|
||||||
Text(text = "Group not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Text(text = group.name, maxLines = 1)
|
|
||||||
Text(text = "participantsCount: ${group.participantsCount}", maxLines = 1)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
|
|
||||||
OutlinedButton(onClick = {
|
|
||||||
context.modDatabase.deleteGroup(id)
|
|
||||||
section.onResumed()
|
|
||||||
navController.popBackStack()
|
|
||||||
}) {
|
|
||||||
Text(text = "Delete group")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,9 +2,9 @@ package me.rhunk.snapenhance.ui.manager.sections.social
|
|||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
|
||||||
import androidx.compose.foundation.gestures.scrollable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Add
|
import androidx.compose.material.icons.rounded.Add
|
||||||
@ -26,12 +25,13 @@ import androidx.compose.material3.TabRow
|
|||||||
import androidx.compose.material3.TabRowDefaults
|
import androidx.compose.material3.TabRowDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -41,8 +41,11 @@ import androidx.navigation.navigation
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||||
|
import me.rhunk.snapenhance.core.messaging.SocialScope
|
||||||
import me.rhunk.snapenhance.ui.manager.Section
|
import me.rhunk.snapenhance.ui.manager.Section
|
||||||
|
import me.rhunk.snapenhance.ui.util.BitmojiImage
|
||||||
import me.rhunk.snapenhance.ui.util.pagerTabIndicatorOffset
|
import me.rhunk.snapenhance.ui.util.pagerTabIndicatorOffset
|
||||||
|
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
|
||||||
|
|
||||||
class SocialSection : Section() {
|
class SocialSection : Section() {
|
||||||
private lateinit var friendList: List<MessagingFriendInfo>
|
private lateinit var friendList: List<MessagingFriendInfo>
|
||||||
@ -54,7 +57,7 @@ class SocialSection : Section() {
|
|||||||
const val GROUP_INFO_ROUTE = "group_info/{id}"
|
const val GROUP_INFO_ROUTE = "group_info/{id}"
|
||||||
}
|
}
|
||||||
|
|
||||||
private var currentScopeTab: ScopeTab? = null
|
private var currentScopeContent: ScopeContent? = null
|
||||||
|
|
||||||
private val addFriendDialog by lazy {
|
private val addFriendDialog by lazy {
|
||||||
AddFriendDialog(context, this)
|
AddFriendDialog(context, this)
|
||||||
@ -69,23 +72,96 @@ class SocialSection : Section() {
|
|||||||
override fun canGoBack() = navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE
|
override fun canGoBack() = navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE
|
||||||
|
|
||||||
override fun build(navGraphBuilder: NavGraphBuilder) {
|
override fun build(navGraphBuilder: NavGraphBuilder) {
|
||||||
fun switchTab(id: String) = ScopeTab(context, this, navController, id).also { tab ->
|
|
||||||
currentScopeTab = tab
|
|
||||||
}
|
|
||||||
|
|
||||||
navGraphBuilder.navigation(route = enumSection.route, startDestination = MAIN_ROUTE) {
|
navGraphBuilder.navigation(route = enumSection.route, startDestination = MAIN_ROUTE) {
|
||||||
composable(MAIN_ROUTE) {
|
composable(MAIN_ROUTE) {
|
||||||
Content()
|
Content()
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(FRIEND_INFO_ROUTE) {
|
SocialScope.values().forEach { scope ->
|
||||||
val id = it.arguments?.getString("id") ?: return@composable
|
composable(scope.tabRoute) {
|
||||||
remember { switchTab(id) }.Friend()
|
val id = it.arguments?.getString("id") ?: return@composable
|
||||||
|
remember {
|
||||||
|
ScopeContent(context, this@SocialSection, navController, scope, id).also { tab ->
|
||||||
|
currentScopeContent = tab
|
||||||
|
}
|
||||||
|
}.Content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScopeList(scope: SocialScope) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(2.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
) {
|
||||||
|
//check if scope list is empty
|
||||||
|
val listSize = when (scope) {
|
||||||
|
SocialScope.GROUP -> groupList.size
|
||||||
|
SocialScope.FRIEND -> friendList.size
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(GROUP_INFO_ROUTE) {
|
if (listSize == 0) {
|
||||||
val id = it.arguments?.getString("id") ?: return@composable
|
item {
|
||||||
remember { switchTab(id) }.Group()
|
//TODO: i18n
|
||||||
|
Text(text = "No ${scope.key.lowercase()}s found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items(listSize) { index ->
|
||||||
|
val id = when (scope) {
|
||||||
|
SocialScope.GROUP -> groupList[index].conversationId
|
||||||
|
SocialScope.FRIEND -> friendList[index].userId
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(80.dp)
|
||||||
|
.clickable {
|
||||||
|
navController.navigate(
|
||||||
|
scope.tabRoute.replace("{id}", id)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
when (scope) {
|
||||||
|
SocialScope.GROUP -> {
|
||||||
|
val group = groupList[index]
|
||||||
|
Column {
|
||||||
|
Text(text = group.name, maxLines = 1)
|
||||||
|
Text(text = "participantsCount: ${group.participantsCount}", maxLines = 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SocialScope.FRIEND -> {
|
||||||
|
val friend = friendList[index]
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.fillMaxSize(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
val bitmojiUrl = (friend.selfieId to friend.bitmojiId).let { (selfieId, bitmojiId) ->
|
||||||
|
if (selfieId == null || bitmojiId == null) return@let null
|
||||||
|
BitmojiSelfie.getBitmojiSelfie(selfieId, bitmojiId, BitmojiSelfie.BitmojiSelfieType.STANDARD)
|
||||||
|
}
|
||||||
|
BitmojiImage(context = context, url = bitmojiUrl)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(10.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(text = friend.displayName ?: friend.mutableUsername, maxLines = 1)
|
||||||
|
Text(text = friend.userId, maxLines = 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,60 +221,10 @@ class SocialSection : Section() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
HorizontalPager(modifier = Modifier.padding(paddingValues), state = pagerState) { page ->
|
HorizontalPager(modifier = Modifier.padding(paddingValues), state = pagerState) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> {
|
0 -> ScopeList(SocialScope.FRIEND)
|
||||||
LazyColumn(
|
1 -> ScopeList(SocialScope.GROUP)
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
if (friendList.isEmpty()) {
|
|
||||||
item {
|
|
||||||
Text(text = "No friends found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
items(friendList.size) { index ->
|
|
||||||
val friend = friendList[index]
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(100.dp)
|
|
||||||
.clickable {
|
|
||||||
navController.navigate(
|
|
||||||
FRIEND_INFO_ROUTE.replace(
|
|
||||||
"{id}",
|
|
||||||
friend.userId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
Text(text = friend.displayName ?: friend.mutableUsername)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1 -> {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.fillMaxSize()
|
|
||||||
.scrollable(rememberScrollState(), Orientation.Vertical)
|
|
||||||
) {
|
|
||||||
groupList.forEach {
|
|
||||||
Card(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(10.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(100.dp),
|
|
||||||
) {
|
|
||||||
Text(text = it.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,53 @@
|
|||||||
|
package me.rhunk.snapenhance.ui.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.requiredWidthIn
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.size.Precision
|
||||||
|
import me.rhunk.snapenhance.R
|
||||||
|
import me.rhunk.snapenhance.RemoteSideContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BitmojiImage(context: RemoteSideContext, modifier: Modifier = Modifier, size: Int = 48, url: String?) {
|
||||||
|
Image(
|
||||||
|
painter = rememberAsyncImagePainter(
|
||||||
|
model = ImageRequestHelper.newBitmojiImageRequest(
|
||||||
|
context.androidContext,
|
||||||
|
url
|
||||||
|
),
|
||||||
|
imageLoader = context.imageLoader
|
||||||
|
),
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
modifier = Modifier
|
||||||
|
.requiredWidthIn(min = 0.dp, max = size.dp)
|
||||||
|
.height(size.dp)
|
||||||
|
.clip(MaterialTheme.shapes.medium)
|
||||||
|
.then(modifier)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ImageRequestHelper {
|
||||||
|
fun newBitmojiImageRequest(context: Context, url: String?) = ImageRequest.Builder(context)
|
||||||
|
.data(url)
|
||||||
|
.fallback(R.drawable.bitmoji_blank)
|
||||||
|
.precision(Precision.INEXACT)
|
||||||
|
.crossfade(true)
|
||||||
|
.memoryCacheKey(url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun newDownloadPreviewImageRequest(context: Context, filePath: String?) = ImageRequest.Builder(context)
|
||||||
|
.data(filePath)
|
||||||
|
.memoryCacheKey(filePath)
|
||||||
|
.crossfade(true)
|
||||||
|
.build()
|
||||||
|
}
|
@ -16,7 +16,7 @@ import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
|||||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
import me.rhunk.snapenhance.bridge.types.FileActionType
|
||||||
import me.rhunk.snapenhance.core.BuildConfig
|
import me.rhunk.snapenhance.core.BuildConfig
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
||||||
import me.rhunk.snapenhance.core.messaging.MessagingScope
|
import me.rhunk.snapenhance.core.messaging.SocialScope
|
||||||
import me.rhunk.snapenhance.data.LocalePair
|
import me.rhunk.snapenhance.data.LocalePair
|
||||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
@ -136,7 +136,7 @@ class BridgeClient(
|
|||||||
|
|
||||||
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends)
|
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends)
|
||||||
|
|
||||||
fun getRulesFromId(type: MessagingScope, targetUuid: String): List<MessagingRule> {
|
fun getRulesFromId(type: SocialScope, targetUuid: String): List<MessagingRule> {
|
||||||
return service.getRules(type.name, targetUuid).map {
|
return service.getRules(type.name, targetUuid).map {
|
||||||
SerializableDataObject.fromJson(it, MessagingRule::class.java)
|
SerializableDataObject.fromJson(it, MessagingRule::class.java)
|
||||||
}.toList()
|
}.toList()
|
||||||
|
@ -8,18 +8,21 @@ enum class Mode {
|
|||||||
WHITELIST
|
WHITELIST
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class MessagingScope {
|
enum class SocialScope(
|
||||||
FRIEND,
|
val key: String,
|
||||||
GROUP
|
val tabRoute: String,
|
||||||
|
) {
|
||||||
|
FRIEND("friend", "friend_info/{id}"),
|
||||||
|
GROUP("group", "group_info/{id}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class ConversationFeature(
|
enum class MessagingRuleType(
|
||||||
val value: String,
|
val key: String,
|
||||||
val messagingScope: MessagingScope,
|
val socialScope: SocialScope,
|
||||||
) {
|
) {
|
||||||
DOWNLOAD("download", MessagingScope.FRIEND),
|
DOWNLOAD("download", SocialScope.FRIEND),
|
||||||
STEALTH("stealth", MessagingScope.GROUP),
|
STEALTH("stealth", SocialScope.GROUP),
|
||||||
AUTO_SAVE("auto_save", MessagingScope.GROUP);
|
AUTO_SAVE("auto_save", SocialScope.GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FriendStreaks(
|
data class FriendStreaks(
|
||||||
@ -47,9 +50,8 @@ data class MessagingFriendInfo(
|
|||||||
|
|
||||||
data class MessagingRule(
|
data class MessagingRule(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val messagingScope: MessagingScope,
|
val socialScope: SocialScope,
|
||||||
val targetUuid: String,
|
val targetUuid: String,
|
||||||
val enabled: Boolean,
|
//val mode: Mode?,
|
||||||
val mode: Mode?,
|
|
||||||
val subject: String
|
val subject: String
|
||||||
) : SerializableDataObject()
|
) : SerializableDataObject()
|
Reference in New Issue
Block a user