mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-29 13:00:17 +02:00
feat: partial scope content
This commit is contained in:
parent
31570694a0
commit
16a16df51a
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
@ -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()
|
||||
|
@ -29,7 +29,7 @@ data class FriendStreaks(
|
||||
val userId: String,
|
||||
val notify: Boolean,
|
||||
val expirationTimestamp: Long,
|
||||
val count: Int
|
||||
val length: Int
|
||||
) : SerializableDataObject()
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user