feat: scope content

- refactor image loader
- rules
This commit is contained in:
rhunk
2023-08-20 15:27:42 +02:00
parent cb301f8a42
commit 31570694a0
10 changed files with 326 additions and 207 deletions

View File

@ -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)

View File

@ -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()
} }

View File

@ -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

View File

@ -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),

View File

@ -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()
}
}
}

View File

@ -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")
}
}
}
}

View File

@ -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)
}
}
}
}
} }
} }
} }

View File

@ -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()
}

View File

@ -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()

View File

@ -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()