feat: partial scope content

This commit is contained in:
rhunk 2023-08-21 18:09:59 +02:00
parent 31570694a0
commit 16a16df51a
11 changed files with 234 additions and 57 deletions

View File

@ -33,13 +33,23 @@ class BridgeService : Service() {
} }
fun triggerFriendSync(friendId: String) { fun triggerFriendSync(friendId: String) {
SerializableDataObject.fromJson<FriendInfo>(syncCallback.syncFriend(friendId)).let { val syncedFriend = syncCallback.syncFriend(friendId)
if (syncedFriend == null) {
Logger.error("Failed to sync friend $friendId")
return
}
SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let {
remoteSideContext.modDatabase.syncFriend(it) remoteSideContext.modDatabase.syncFriend(it)
} }
} }
fun triggerGroupSync(groupId: String) { fun triggerGroupSync(groupId: String) {
SerializableDataObject.fromJson<MessagingGroupInfo>(syncCallback.syncGroup(groupId)).let { val syncedGroup = syncCallback.syncGroup(groupId)
if (syncedGroup == null) {
Logger.error("Failed to sync group $groupId")
return
}
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let {
remoteSideContext.modDatabase.syncGroupInfo(it) remoteSideContext.modDatabase.syncGroupInfo(it)
} }
} }

View File

@ -25,7 +25,13 @@ class ModDatabase(
var receiveMessagingDataCallback: (friends: List<MessagingFriendInfo>, groups: List<MessagingGroupInfo>) -> Unit = { _, _ -> } var receiveMessagingDataCallback: (friends: List<MessagingFriendInfo>, groups: List<MessagingGroupInfo>) -> Unit = { _, _ -> }
fun executeAsync(block: () -> Unit) { fun executeAsync(block: () -> Unit) {
executor.execute(block) executor.execute {
runCatching {
block()
}.onFailure {
Logger.error("Failed to execute async block", it)
}
}
} }
fun init() { fun init() {
@ -56,7 +62,7 @@ class ModDatabase(
"userId VARCHAR PRIMARY KEY", "userId VARCHAR PRIMARY KEY",
"notify BOOLEAN", "notify BOOLEAN",
"expirationTimestamp BIGINT", "expirationTimestamp BIGINT",
"count INTEGER" "length INTEGER"
), ),
"analytics_config" to listOf( "analytics_config" to listOf(
"userId VARCHAR PRIMARY KEY", "userId VARCHAR PRIMARY KEY",
@ -137,7 +143,7 @@ class ModDatabase(
) )
//sync streaks //sync streaks
if (friend.streakLength > 0) { if (friend.streakLength > 0) {
database.execSQL("INSERT OR REPLACE INTO streaks (userId, expirationTimestamp, count) VALUES (?, ?, ?)", arrayOf( database.execSQL("INSERT OR REPLACE INTO streaks (userId, expirationTimestamp, length) VALUES (?, ?, ?)", arrayOf(
friend.userId, friend.userId,
friend.streakExpirationTimestamp, friend.streakExpirationTimestamp,
friend.streakLength friend.streakLength
@ -227,8 +233,17 @@ class ModDatabase(
userId = cursor.getStringOrNull("userId")!!, userId = cursor.getStringOrNull("userId")!!,
notify = cursor.getInteger("notify") == 1, notify = cursor.getInteger("notify") == 1,
expirationTimestamp = cursor.getLongOrNull("expirationTimestamp") ?: 0L, expirationTimestamp = cursor.getLongOrNull("expirationTimestamp") ?: 0L,
count = cursor.getInteger("count") length = cursor.getInteger("length")
) )
} }
} }
fun setFriendStreaksNotify(userId: String, notify: Boolean) {
executeAsync {
database.execSQL("UPDATE streaks SET notify = ? WHERE userId = ?", arrayOf(
if (notify) 1 else 0,
userId
))
}
}
} }

View File

@ -44,7 +44,8 @@ class MainActivity : ComponentActivity() {
Scaffold( Scaffold(
containerColor = MaterialTheme.colorScheme.background, containerColor = MaterialTheme.colorScheme.background,
topBar = { navigation.TopBar() }, topBar = { navigation.TopBar() },
bottomBar = { navigation.NavBar() } bottomBar = { navigation.NavBar() },
floatingActionButton = { navigation.Fab() }
) { innerPadding -> ) { innerPadding ->
navigation.NavigationHost( navigation.NavigationHost(
innerPadding = innerPadding, innerPadding = innerPadding,

View File

@ -100,6 +100,15 @@ class Navigation(
}) })
} }
@Composable
fun Fab() {
val navBackStackEntry by navHostController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination ?: return
val currentSection = getCurrentSection(currentDestination)
currentSection.FloatingActionButton()
}
@Composable @Composable
fun NavBar() { fun NavBar() {
NavigationBar { NavigationBar {

View File

@ -76,6 +76,9 @@ open class Section {
@Composable @Composable
open fun TopBarActions(rowScope: RowScope) {} open fun TopBarActions(rowScope: RowScope) {}
@Composable
open fun FloatingActionButton() {}
open fun build(navGraphBuilder: NavGraphBuilder) { open fun build(navGraphBuilder: NavGraphBuilder) {
navGraphBuilder.composable(enumSection.route) { navGraphBuilder.composable(enumSection.route) {
Content() Content()

View File

@ -30,6 +30,7 @@ import androidx.compose.material.icons.filled.FolderOpen
import androidx.compose.material.icons.filled.OpenInNew import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.rounded.Save import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.BottomSheetScaffoldState
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledIconButton import androidx.compose.material3.FilledIconButton
@ -77,6 +78,7 @@ import me.rhunk.snapenhance.core.config.PropertyValue
import me.rhunk.snapenhance.ui.manager.Section import me.rhunk.snapenhance.ui.manager.Section
import me.rhunk.snapenhance.ui.util.ChooseFolderHelper import me.rhunk.snapenhance.ui.util.ChooseFolderHelper
@OptIn(ExperimentalMaterial3Api::class)
class FeaturesSection : Section() { class FeaturesSection : Section() {
private val dialogs by lazy { Dialogs(context.translation) } private val dialogs by lazy { Dialogs(context.translation) }
@ -91,6 +93,8 @@ class FeaturesSection : Section() {
private val featuresRouteName by lazy { context.translation["manager.routes.features"] } private val featuresRouteName by lazy { context.translation["manager.routes.features"] }
private lateinit var rememberScaffoldState: BottomSheetScaffoldState
private val allContainers by lazy { private val allContainers by lazy {
val containers = mutableMapOf<String, PropertyPair<*>>() val containers = mutableMapOf<String, PropertyPair<*>>()
fun queryContainerRecursive(container: ConfigContainer) { fun queryContainerRecursive(container: ConfigContainer) {
@ -458,35 +462,14 @@ class FeaturesSection : Section() {
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun PropertiesView( private fun PropertiesView(
properties: List<PropertyPair<*>> properties: List<PropertyPair<*>>
) { ) {
val scope = rememberCoroutineScope() rememberScaffoldState = rememberBottomSheetScaffoldState()
val scaffoldState = rememberBottomSheetScaffoldState()
Scaffold( Scaffold(
snackbarHost = { SnackbarHost(scaffoldState.snackbarHostState) }, snackbarHost = { SnackbarHost(rememberScaffoldState.snackbarHostState) },
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
floatingActionButton = {
FloatingActionButton(
onClick = {
context.config.writeConfig()
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Saved")
}
},
modifier = Modifier.padding(10.dp),
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
shape = RoundedCornerShape(16.dp),
) {
Icon(
imageVector = Icons.Rounded.Save,
contentDescription = null
)
}
},
content = { innerPadding -> content = { innerPadding ->
LazyColumn( LazyColumn(
modifier = Modifier modifier = Modifier
@ -494,7 +477,7 @@ class FeaturesSection : Section() {
.padding(innerPadding), .padding(innerPadding),
//save button space //save button space
contentPadding = PaddingValues(top = 10.dp, bottom = 110.dp), contentPadding = PaddingValues(top = 10.dp, bottom = 110.dp),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Top
) { ) {
items(properties) { items(properties) {
PropertyCard(it) PropertyCard(it)
@ -504,6 +487,28 @@ class FeaturesSection : Section() {
) )
} }
@Composable
override fun FloatingActionButton() {
val scope = rememberCoroutineScope()
FloatingActionButton(
onClick = {
context.config.writeConfig()
scope.launch {
rememberScaffoldState.snackbarHostState.showSnackbar("Saved")
}
},
modifier = Modifier.padding(10.dp),
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary,
shape = RoundedCornerShape(16.dp),
) {
Icon(
imageVector = Icons.Rounded.Save,
contentDescription = null
)
}
}
@Composable @Composable
private fun Container( private fun Container(

View File

@ -3,7 +3,13 @@ package me.rhunk.snapenhance.ui.manager.sections.social
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Card
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -13,13 +19,18 @@ 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.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController import androidx.navigation.NavController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.core.messaging.MessagingRuleType import me.rhunk.snapenhance.core.messaging.MessagingRuleType
import me.rhunk.snapenhance.core.messaging.SocialScope import me.rhunk.snapenhance.core.messaging.SocialScope
import me.rhunk.snapenhance.ui.util.BitmojiImage
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
class ScopeContent( class ScopeContent(
private val context: RemoteSideContext, private val context: RemoteSideContext,
@ -28,7 +39,6 @@ class ScopeContent(
private val scope: SocialScope, private val scope: SocialScope,
private val id: String private val id: String
) { ) {
@Composable @Composable
private fun DeleteScopeEntityButton() { private fun DeleteScopeEntityButton() {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@ -40,7 +50,7 @@ class ScopeContent(
context.modDatabase.executeAsync { context.modDatabase.executeAsync {
coroutineScope.launch { coroutineScope.launch {
section.onResumed() section.onResumed()
navController.popBackStack() navController.navigate(SocialSection.MAIN_ROUTE)
} }
} }
}) { }) {
@ -50,7 +60,10 @@ class ScopeContent(
@Composable @Composable
fun Content() { fun Content() {
Column { Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
when (scope) { when (scope) {
SocialScope.FRIEND -> Friend() SocialScope.FRIEND -> Friend()
SocialScope.GROUP -> Group() SocialScope.GROUP -> Group()
@ -60,26 +73,87 @@ class ScopeContent(
val scopeRules = context.modDatabase.getRulesFromId(scope, id) val scopeRules = context.modDatabase.getRulesFromId(scope, id)
Text(text = "Rules", maxLines = 1) SectionTitle("Rules")
Spacer(modifier = Modifier.height(16.dp))
//manager anti features etc ContentCard {
MessagingRuleType.values().forEach { feature -> //manager anti features etc
var featureEnabled by remember { MessagingRuleType.values().forEach { feature ->
mutableStateOf(scopeRules.any { it.subject == feature.key }) var featureEnabled by remember {
} mutableStateOf(scopeRules.any { it.subject == feature.key })
val featureEnabledText = if (featureEnabled) "Enabled" else "Disabled" }
Row { val featureEnabledText = if (featureEnabled) "Enabled" else "Disabled"
Text(text = "${feature.key}: $featureEnabledText", maxLines = 1)
Switch(checked = featureEnabled, onCheckedChange = { Row {
context.modDatabase.toggleRuleFor(scope, id, feature.key, it) Text(text = "${feature.key}: $featureEnabledText", maxLines = 1)
featureEnabled = it Switch(checked = featureEnabled, onCheckedChange = {
}) context.modDatabase.toggleRuleFor(scope, id, feature.key, it)
featureEnabled = it
})
}
} }
} }
} }
} }
@Composable
private fun ContentCard(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Card(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth()
.then(modifier)
) {
content()
}
}
}
@Composable
private fun SectionTitle(title: String) {
Text(
text = title,
maxLines = 1,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier
.offset(x = 20.dp)
.padding(bottom = 10.dp)
)
}
//need to display all units?
private fun computeStreakETA(timestamp: Long): String {
val now = System.currentTimeMillis()
val stringBuilder = StringBuilder()
val diff = timestamp - now
val seconds = diff / 1000
val minutes = seconds / 60
val hours = minutes / 60
val days = hours / 24
if (days > 0) {
stringBuilder.append("$days days ")
return stringBuilder.toString()
}
if (hours > 0) {
stringBuilder.append("$hours hours ")
return stringBuilder.toString()
}
if (minutes > 0) {
stringBuilder.append("$minutes minutes ")
return stringBuilder.toString()
}
if (seconds > 0) {
stringBuilder.append("$seconds seconds ")
return stringBuilder.toString()
}
return "Expired"
}
@Composable @Composable
private fun Friend() { private fun Friend() {
//fetch the friend from the database //fetch the friend from the database
@ -87,15 +161,74 @@ class ScopeContent(
Text(text = "Friend not found") Text(text = "Friend not found")
return 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)
val streaks = remember {
context.modDatabase.getFriendStreaks(id)
}
Column(
modifier = Modifier
.padding(10.dp)
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
val bitmojiUrl = (friend.selfieId to friend.bitmojiId).let { (selfieId, bitmojiId) ->
if (selfieId == null || bitmojiId == null) return@let null
BitmojiSelfie.getBitmojiSelfie(
selfieId,
bitmojiId,
BitmojiSelfie.BitmojiSelfieType.THREE_D
)
}
BitmojiImage(context = context, url = bitmojiUrl, size = 100)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text(
text = friend.displayName ?: friend.mutableUsername,
maxLines = 1,
fontSize = 20.sp,
fontWeight = FontWeight.Bold
)
Spacer(modifier = Modifier.height(5.dp))
Text(
text = friend.mutableUsername,
maxLines = 1,
fontSize = 12.sp,
fontWeight = FontWeight.Light
)
Spacer(modifier = Modifier.height(16.dp))
DeleteScopeEntityButton() DeleteScopeEntityButton()
} }
Spacer(modifier = Modifier.height(16.dp))
Column {
//streaks
streaks?.let {
var shouldNotify by remember { mutableStateOf(it.notify) }
SectionTitle("Streaks")
ContentCard {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f),
) {
Text(text = "Count: ${streaks.length}", maxLines = 1)
Text(text = "Expires in: ${computeStreakETA(streaks.expirationTimestamp)}", maxLines = 1)
}
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(text = "Notify Expiration", maxLines = 1)
Switch(checked = shouldNotify, onCheckedChange = {
context.modDatabase.setFriendStreaksNotify(id, it)
shouldNotify = it
})
}
}
}
}
}
} }
@Composable @Composable

View File

@ -26,7 +26,6 @@ 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.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
@ -147,7 +146,7 @@ class SocialSection : Section() {
) { ) {
val bitmojiUrl = (friend.selfieId to friend.bitmojiId).let { (selfieId, bitmojiId) -> val bitmojiUrl = (friend.selfieId to friend.bitmojiId).let { (selfieId, bitmojiId) ->
if (selfieId == null || bitmojiId == null) return@let null if (selfieId == null || bitmojiId == null) return@let null
BitmojiSelfie.getBitmojiSelfie(selfieId, bitmojiId, BitmojiSelfie.BitmojiSelfieType.STANDARD) BitmojiSelfie.getBitmojiSelfie(selfieId, bitmojiId, BitmojiSelfie.BitmojiSelfieType.THREE_D)
} }
BitmojiImage(context = context, url = bitmojiUrl) BitmojiImage(context = context, url = bitmojiUrl)
Column( Column(

View File

@ -6,12 +6,12 @@ interface SyncCallback {
* @param uuid The uuid of the friend to sync * @param uuid The uuid of the friend to sync
* @return The serialized friend data * @return The serialized friend data
*/ */
String syncFriend(String uuid); @nullable String syncFriend(String uuid);
/** /**
* Called when the conversation data has been synced * Called when the conversation data has been synced
* @param uuid The uuid of the conversation to sync * @param uuid The uuid of the conversation to sync
* @return The serialized conversation data * @return The serialized conversation data
*/ */
String syncGroup(String uuid); @nullable String syncGroup(String uuid);
} }

View File

@ -89,6 +89,7 @@ class SnapEnhance {
Logger.debug("Reloading config") Logger.debug("Reloading config")
appContext.reloadConfig() appContext.reloadConfig()
syncRemote()
} }
} }
@ -131,6 +132,7 @@ class SnapEnhance {
val database = appContext.database val database = appContext.database
appContext.executeAsync { appContext.executeAsync {
Logger.debug("request remote sync")
appContext.bridgeClient.sync(object : SyncCallback.Stub() { appContext.bridgeClient.sync(object : SyncCallback.Stub() {
override fun syncFriend(uuid: String): String? { override fun syncFriend(uuid: String): String? {
return database.getFriendInfo(uuid)?.toJson() return database.getFriendInfo(uuid)?.toJson()

View File

@ -29,7 +29,7 @@ data class FriendStreaks(
val userId: String, val userId: String,
val notify: Boolean, val notify: Boolean,
val expirationTimestamp: Long, val expirationTimestamp: Long,
val count: Int val length: Int
) : SerializableDataObject() ) : SerializableDataObject()