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

View File

@ -25,7 +25,13 @@ class ModDatabase(
var receiveMessagingDataCallback: (friends: List<MessagingFriendInfo>, groups: List<MessagingGroupInfo>) -> Unit = { _, _ -> }
fun executeAsync(block: () -> Unit) {
executor.execute(block)
executor.execute {
runCatching {
block()
}.onFailure {
Logger.error("Failed to execute async block", it)
}
}
}
fun init() {
@ -56,7 +62,7 @@ class ModDatabase(
"userId VARCHAR PRIMARY KEY",
"notify BOOLEAN",
"expirationTimestamp BIGINT",
"count INTEGER"
"length INTEGER"
),
"analytics_config" to listOf(
"userId VARCHAR PRIMARY KEY",
@ -137,7 +143,7 @@ class ModDatabase(
)
//sync streaks
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.streakExpirationTimestamp,
friend.streakLength
@ -227,8 +233,17 @@ class ModDatabase(
userId = cursor.getStringOrNull("userId")!!,
notify = cursor.getInteger("notify") == 1,
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(
containerColor = MaterialTheme.colorScheme.background,
topBar = { navigation.TopBar() },
bottomBar = { navigation.NavBar() }
bottomBar = { navigation.NavBar() },
floatingActionButton = { navigation.Fab() }
) { innerPadding ->
navigation.NavigationHost(
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
fun NavBar() {
NavigationBar {

View File

@ -76,6 +76,9 @@ open class Section {
@Composable
open fun TopBarActions(rowScope: RowScope) {}
@Composable
open fun FloatingActionButton() {}
open fun build(navGraphBuilder: NavGraphBuilder) {
navGraphBuilder.composable(enumSection.route) {
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.Search
import androidx.compose.material.icons.rounded.Save
import androidx.compose.material3.BottomSheetScaffoldState
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
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.util.ChooseFolderHelper
@OptIn(ExperimentalMaterial3Api::class)
class FeaturesSection : Section() {
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 lateinit var rememberScaffoldState: BottomSheetScaffoldState
private val allContainers by lazy {
val containers = mutableMapOf<String, PropertyPair<*>>()
fun queryContainerRecursive(container: ConfigContainer) {
@ -458,22 +462,39 @@ class FeaturesSection : Section() {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun PropertiesView(
properties: List<PropertyPair<*>>
) {
val scope = rememberCoroutineScope()
val scaffoldState = rememberBottomSheetScaffoldState()
rememberScaffoldState = rememberBottomSheetScaffoldState()
Scaffold(
snackbarHost = { SnackbarHost(scaffoldState.snackbarHostState) },
snackbarHost = { SnackbarHost(rememberScaffoldState.snackbarHostState) },
modifier = Modifier.fillMaxSize(),
floatingActionButton = {
content = { innerPadding ->
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.padding(innerPadding),
//save button space
contentPadding = PaddingValues(top = 10.dp, bottom = 110.dp),
verticalArrangement = Arrangement.Top
) {
items(properties) {
PropertyCard(it)
}
}
}
)
}
@Composable
override fun FloatingActionButton() {
val scope = rememberCoroutineScope()
FloatingActionButton(
onClick = {
context.config.writeConfig()
scope.launch {
scaffoldState.snackbarHostState.showSnackbar("Saved")
rememberScaffoldState.snackbarHostState.showSnackbar("Saved")
}
},
modifier = Modifier.padding(10.dp),
@ -486,22 +507,6 @@ class FeaturesSection : Section() {
contentDescription = null
)
}
},
content = { innerPadding ->
LazyColumn(
modifier = Modifier
.fillMaxHeight()
.padding(innerPadding),
//save button space
contentPadding = PaddingValues(top = 10.dp, bottom = 110.dp),
verticalArrangement = Arrangement.Center
) {
items(properties) {
PropertyCard(it)
}
}
}
)
}

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.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
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.Switch
import androidx.compose.material3.Text
@ -13,13 +19,18 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
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
import me.rhunk.snapenhance.ui.util.BitmojiImage
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
class ScopeContent(
private val context: RemoteSideContext,
@ -28,7 +39,6 @@ class ScopeContent(
private val scope: SocialScope,
private val id: String
) {
@Composable
private fun DeleteScopeEntityButton() {
val coroutineScope = rememberCoroutineScope()
@ -40,7 +50,7 @@ class ScopeContent(
context.modDatabase.executeAsync {
coroutineScope.launch {
section.onResumed()
navController.popBackStack()
navController.navigate(SocialSection.MAIN_ROUTE)
}
}
}) {
@ -50,7 +60,10 @@ class ScopeContent(
@Composable
fun Content() {
Column {
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
when (scope) {
SocialScope.FRIEND -> Friend()
SocialScope.GROUP -> Group()
@ -60,15 +73,16 @@ class ScopeContent(
val scopeRules = context.modDatabase.getRulesFromId(scope, id)
Text(text = "Rules", maxLines = 1)
Spacer(modifier = Modifier.height(16.dp))
SectionTitle("Rules")
ContentCard {
//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 = {
@ -79,6 +93,66 @@ class ScopeContent(
}
}
}
}
@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
private fun Friend() {
@ -87,15 +161,74 @@ class ScopeContent(
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)
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))
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()
}
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

View File

@ -26,7 +26,6 @@ import androidx.compose.material3.TabRowDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
@ -147,7 +146,7 @@ class SocialSection : Section() {
) {
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)
BitmojiSelfie.getBitmojiSelfie(selfieId, bitmojiId, BitmojiSelfie.BitmojiSelfieType.THREE_D)
}
BitmojiImage(context = context, url = bitmojiUrl)
Column(

View File

@ -6,12 +6,12 @@ interface SyncCallback {
* @param uuid The uuid of the friend to sync
* @return The serialized friend data
*/
String syncFriend(String uuid);
@nullable String syncFriend(String uuid);
/**
* Called when the conversation data has been synced
* @param uuid The uuid of the conversation to sync
* @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")
appContext.reloadConfig()
syncRemote()
}
}
@ -131,6 +132,7 @@ class SnapEnhance {
val database = appContext.database
appContext.executeAsync {
Logger.debug("request remote sync")
appContext.bridgeClient.sync(object : SyncCallback.Stub() {
override fun syncFriend(uuid: String): String? {
return database.getFriendInfo(uuid)?.toJson()

View File

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